提供: Minecraft Modding Wiki
2017年3月26日 (日) 15:07時点におけるSwallow794 (トーク | 投稿記録)による版 (IChunkGeneratorに「岩盤の床だけのチャンクを生成する」例を追記)
移動先: 案内検索

この記事は"Minecraft Forge Universal 13.19.0.xxx~"を前提MODとしています。

Stone pickaxe.png
中級者向けのチュートリアルです。
C world.png
Worldに関係のあるチュートリアルです。

ディメンションの追加

ディメンションを生成する

DimensionType

「net.minecraft.world.DimensionType」は、ディメンションを登録する列挙型です。

ForgeがDimensionTypeに列挙子を追加するメソッド「DimensionType#register」を用意してくれているので、これを使います。

  1. 「net.minecraftforge.common.DimensionManager#getNextFreeDimId」から、使用可能なディメンションID(int)を取得します。
  2. 「DimensionType#register」を呼び出し、「DimensionType」型の戻り値を得ます。これが追加された列挙子で、WorldProviderで使います。
    • 第一引数(String):ディメンションの名前(e.g. Overworld、Nether、The End)
    • 第二引数(String):ディメンションのセーブデータ等で使う後置詞(e.g. _nether、_end)
    • 第三引数(int):1で取得したID
    • 第四引数(Class<? extends WorldProvider>):WorldProviderのクラス(下で解説)
    • 第五引数(boolean):ディメンションをアンロードしないかどうか
  3. 「DimensionManager#registerDimension」を呼び出し、こちらにもディメンションを登録します。

WorldProvider

「net.minecraft.world.WorldProvider」は、ワールドの振る舞いを決定する抽象クラスです。

  1. 「WorldProvider」のサブクラスを作成します。
  2. コンストラクタから「WorldProvider#init」が呼ばれるので、WorldProviderのフィールド(下表参照)を好きなように変更してください。
  3. 「DimensionType」の項で得た、このWorldProviderに対応する「DimensionType」列挙子を「getDimensionType」から返します。
フィールド名 影響 参考
doesWaterVaporize 水の蒸発の有無
  • net.minecraft.item.ItemBucket
  • net.minecraft.block.BlockDynamicLiquid
  • net.minecraft.block.BlockIce
hasNoSky 地図の描画、スポーン座標
  • net.minecraft.world.WorldProvider#getActualHeight
  • net.minecraft.item.ItemMap
hasSkyLight 日光の有無
  • net.minecraft.world.chunk.Chunk
biomeProvider バイオームの決定 下で解説


メソッド名 影響 参考
calculateCelestialAngle 昼夜の移り変わり 0fで真昼(エンド)、.5fで真夜中(ネザー)
  • net.minecraft.world.World#getCelestialAngleRadians
  • net.minecraft.item.ItemClock
  • net.minecraft.block.BlockDaylightDetector
  • net.minecraft.client.renderer.RenderGlobal#renderSky
  • net.minecraft.village.VillageSiege
createChunkGenerator チャンクの地形等、生成手法の決定 net.minecraft.world.chunk.IChunkGeneratorを実装したクラスのインスタンスを作成して返す。詳しくは下で解説。
canRespawnHere リスポーンの可否
isSurfaceWorld 空の描画の有無
getVoidFogYFactor 奈落の霧(Japan Wiki)の開始Y座標
getHorizon 地平線の高さ
doesXZShowFog 霧の描画の有無
  • net.minecraft.client.renderer.EntityRenderer#setupFog
getWelcomeMessage ディメンション入場時のメッセージ?(未使用、Forge)
getDepartMessage ディメンション退場時のメッセージ?(未使用、Forge)

BiomeProvider

「net.minecraft.world.biome.BiomeProvider」は、バイオームを決定するクラスです。

サブクラス「net.minecraft.world.biome.BiomeProviderSingle」は、単一バイオームのディメンションを作成するのに使うことができます(ネザー、エンドで使用)。

オーバーワールドでは、「net.minecraft.world.gen.layer.GenLayer#initializeAllBiomeGenerators」、「net.minecraft.world.biome.BiomeProvider#getModdedBiomeGenerators」を通じて得られた「GenLayer」の「#getInts」からバイオームIDを取得しています。

IChunkGenerator

「net.minecraft.world.chunk.IChunkGenerator」は、チャンクの地形や構造物を生成するインターフェースです。

  1. 「IChunkGenerator」を実装したクラスを作成します。コンストラクタでWorldProviderからWorldのインスタンスを受け取ってください。チャンクのインスタンス化に使います。
  2. 「#provideChunk」を実装します。第一引数はチャンクのX座標、第二引数はチャンクのZ座標です。
    1. 「net.minecraft.world.chunk.ChunkPrimer」をインスタンス化します。
    2. 上のWorld、ChunkPrimerを使って「net.minecraft.world.chunk.Chunk」をインスタンス化します。
    3. Chunk#setBlockStateなどを使って、上のChunkに地形を生成します。
    4. Chunkにバイオームを割り当てます。
      • net.minecraft.world.chunk.ChunkProviderHellの手法
      1. Chunk#getBiomeArrayの戻り値としてバイオーム割り当てに使用するバイオームIDのbyte配列を得ます。
      2. 上のWorldからBiomeProviderを取得し、BiomeProvider#getBiomesから、チャンク座標に対応するバイオームのBiome配列を得ます。
      3. byte配列のindexに対応するバイオームIDをBiome配列から取り出して代入します(配列はオブジェクトなので、Chunkが持つ配列が変更されます)。
メソッド名 操作 参考
provideChunk チャンクのインスタンス化、地形生成
populate 湖、鉱石、ダンジョン等の生成
generateStructures 構造物の追加生成(海底遺跡で使用)
getPossibleCreatures EntityCreatureの自然スポーン設定
  • 通常スポーン
    • Biome#getSpawnableListから取得
  • ダンジョン内等の特殊スポーン
    • 好きなリストを返す
  • net.minecraft.world.WorldServer#getSpawnListEntryForTypeAt
  • net.minecraft.world.WorldEntitySpawner
getStrongholdGen ダンジョンを生成するブロック座標を返す。なければnull。 オーバーワールドの要塞を見つけるエンダーアイで使用。
  • net.minecraft.world.WorldServer#findNearestStructure
  • net.minecraft.item.ItemEnderEye
recreateStructures ダンジョン等MapGenBaseのサブクラスのインスタンスがあればMapGenBase#generateを呼ぶ。

チャンクをファイルから読み込む時に呼ばれる模様。ブロック生成以外の構造物のデータを読み込む?

  • net.minecraft.world.gen.ChunkProviderServer#loadChunkFromFile
  • net.minecraftforge.common.chunkio.ChunkIOProvider#syncCallback
  • net.minecraft.world.gen.MapGenBase#generate
  • net.minecraft.world.gen.structure#MapGenStructure#recursiveGenerate
例:岩盤の床だけのチャンクを生成する

Y=0に岩盤の床があるだけのチャンクを生成します。

package samplemod.world.empty;

import net.minecraft.entity.EnumCreatureType;
import net.minecraft.init.Blocks;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import net.minecraft.world.biome.Biome;
import net.minecraft.world.chunk.Chunk;
import net.minecraft.world.chunk.ChunkPrimer;
import net.minecraft.world.chunk.IChunkGenerator;

import javax.annotation.Nullable;
import java.util.List;

public class ChunkGeneratorEmpty implements IChunkGenerator {
    private World world;

    public ChunkGeneratorEmpty(World worldIn) {
        world = worldIn;
    }

    public void genSurface(Chunk chunk) {
        for (int x = 0; x < 16; x++)
            for (int z = 0; z < 16; z++)
                chunk.setBlockState(new BlockPos(x, 0, z), Blocks.BEDROCK.getDefaultState());
    }

    @Override
    public Chunk provideChunk(int x, int z) {
        ChunkPrimer chunkPrimer = new ChunkPrimer();

        Chunk chunk = new Chunk(world, chunkPrimer, x, z);
        genSurface(chunk);

        Biome[] abiome = world.getBiomeProvider().getBiomes(null, x * 16, z * 16, 16, 16);
        byte[] abyte = chunk.getBiomeArray();

        for (int i = 0; i < abiome.length; i++)
            abyte[i] = (byte) Biome.getIdForBiome(abiome[i]);

        chunk.resetRelightChecks();
        return chunk;
    }

    @Override
    public void populate(int x, int z) {
    }

    @Override
    public boolean generateStructures(Chunk chunkIn, int x, int z) {
        return false;
    }

    @Override
    public List<Biome.SpawnListEntry> getPossibleCreatures(EnumCreatureType creatureType, BlockPos pos) {
        return world.getBiome(pos).getSpawnableList(creatureType);
    }

    @Nullable
    @Override
    public BlockPos getStrongholdGen(World worldIn, String structureName, BlockPos position, boolean p_180513_4_) {
        return null;
    }

    @Override
    public void recreateStructures(Chunk chunkIn, int x, int z) {

    }
}


ポータルを生成する

Teleporter

「net.minecraft.world.Teleporter」は移動するEntityの現在座標をもとにポータルを生成し、ポータル位置にEntityを移動させるクラスです。

EntityがDimension移動するのに使われる「net.minecraft.entity.Entity#changeDimension」では、「net.minecraft.world.WorldServer#getDefaultTeleporter」を呼び出し、Teleporter型の「WorldServer#worldTeleporter」を得ています。

また、Entityがエンド以外からネザーへ転送される時はTeleporter呼び出し前に座標が1/8倍され、エンド以外からオーバーワールドへ転送されるときは8倍されます(PlayerList#transferEntityToWorldではForgeによりデッドコード化されているため、座標は変更されない)。

  1. 「Teleporter」のサブクラスを作成します。コンストラクタで目的地のWorldServerを受け取ってください。
  2. 「makePortal」、「placeInExistingPortal」を再実装します。「placeInPortal」はエンドへの転送時の足場生成を変更する場合のみ、再実装するとよいでしょう。
メソッド名 操作
makePortal バニラでは、ネザーポータルの生成位置決定と生成を行う。
placeInPortal PlayerList#transferEntityToWorldから呼ばれる。バニラでは、エンド以外への転送の場合、placeInExistingPortalを呼び出し、falseが返されたらmakePortalを呼び、もう一度placeInExistingPortalを呼び出している。つまり、ポータルの存在の有無によってmakePortalが呼ばれるかどうかが変わり、makePortalで適切にポータルを生成していれば、既存のポータルまたは新しいポータルで転送できる。エンドへの転送の場合、足場となる黒曜石を生成している。
placeInExistingPortal Entity#changeDimensionから呼ばれる。バニラでは、既存の「ポータルブロック」(Blocks.PORTAL)を探索し、見つからない場合falseを返す。見つかった場合、Entityをポータル座標に移動させてtrueを返す。また、キャッシュされていないポータル(座標)のキャッシュもここで行っている。
removeStalePortalLocations worldTimeをもとに、古いポータル(座標)のキャッシュを削除する。

エンティティを転送する

Entityの別のディメンションへの転送方法について解説します。バニラ+Forge環境で利用可能な転送用メソッドは二種類あります。

一つ目の「net.minecraft.entity.Entity#changeDimension」は、ネザーポータル、エンドポータルによるディメンション間移動に使用されています。これは、まずEntity#dimensionを変更したのち、出発地のディメンションに対応するWorldServerからEntityを削除、座標を変更してTeleporterを呼び出し、目的地のディメンションに対応するWorldServerインスタンスを渡してEntityのクローンを作成し、クローンを目的地に強制スポーンさせることでディメンション間移動を実現しています。

クローンのEntity#dimensionはコンストラクタでWorldServerから取得されることになり、最初の代入は無駄にも思えますが、ポータルを生成するTeleporterに渡されるのは最初の(出発地側の)Entityであるため、Teleporter内ではコンストラクタに渡される目的地のWorldServerのディメンションIDとEntity#dimensionは一致することになります(バニラでは転送先はWorldServerから取って判定しているのだが)。

二つ目の「net.minecraft.server.management.PlayerList#transferEntityToWorld」は、「net.minecraft.entity.player.EntityPlayerMP」で再実装された「#changeDimension」から「PlayerList#changePlayerDimension」を経由して呼ばれています。

バニラの「PlayerList#changePlayerDimension」は、ForgeによってTeleporter引数を追加したメソッド「#transferPlayerToDimension」に処理を移されています(GitHub/MinecraftForge/PlayerList.java.patch#L151)。「#transferPlayerToDimension」は、出発地のWorldServerからEntityPlayerMPを削除したのち、「#transferEntityToWorld」を呼び出しています。

バニラの「PlayerList#transferEntityToWorld」は、ForgeによってTeleporter引数を追加した同名メソッドに処理を移されています(GitHub/MinecraftForge/PlayerList.java.patch#L179)。

「PlayerList#transferEntityToWorld」のおおよその処理は「Entity#changeDimension」と同じですが、新しくEntityのインスタンスを作り直さず、「Entity#setWorld」を呼んで出発地のWorldに属するEntityインスタンスをそのまま目的地のWorldで再利用します。

また注意すべき点として、「PlayerList#transferEntityToWorld」は、出発地のWorldからEntityを削除しません。自分で出発地のWorldに対して「World#removeEntityDangerously」を呼んで、World及びChunkからEntityを削除する必要があります。

例:「触れるとディメンションを移動するブロック」を追加する

BlockTeleporter.java

package samplemod.block;

import net.minecraft.block.Block;
import net.minecraft.block.material.Material;
import net.minecraft.block.state.IBlockState;
import net.minecraft.entity.Entity;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.management.PlayerList;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.*;
import samplemod.SampleMod;
import samplemod.world.sample.TeleporterSample;

import javax.annotation.Nullable;

public class BlockTeleporter extends Block {

    public BlockTeleporter() {
        super(Material.ROCK);
    }

    @Nullable
    @Override
    public AxisAlignedBB getCollisionBoundingBox(IBlockState blockState, IBlockAccess worldIn, BlockPos pos) {
        return FULL_BLOCK_AABB.expandXyz(-.05d);
    }

    @Override
    public void onEntityCollidedWithBlock(World worldIn, BlockPos pos, IBlockState state, Entity entityIn) {
        MinecraftServer server = worldIn.getMinecraftServer();
        if (server != null) {
            PlayerList playerList = server.getPlayerList();
            int dest = entityIn.dimension == DimensionType.OVERWORLD.getId() ? SampleMod.SAMPLE_DIMENSION.getId() : DimensionType.OVERWORLD.getId();

            Teleporter teleporter = new TeleporterSample(server.worldServerForDimension(dest));

            if (entityIn instanceof EntityPlayerMP) {
                playerList.transferPlayerToDimension((EntityPlayerMP) entityIn, dest, teleporter);
            }
            else {
                int origin = entityIn.dimension;
                entityIn.dimension = dest;
                worldIn.removeEntityDangerously(entityIn);

                entityIn.isDead = false;

                playerList.transferEntityToWorld(entityIn, origin, server.worldServerForDimension(origin), server.worldServerForDimension(dest), teleporter);
            }
        }
    }
}