Swallow794 (トーク | 投稿記録) (Dimension間移動について、PlayerList#transferEntityToWorldは私の方で転送に成功していない(詳しくは未検証)ので、強引な手法を記述) |
Swallow794 (トーク | 投稿記録) 細 (wikitable) |
||
(同じ利用者による、間の5版が非表示) | |||
19行目: | 19行目: | ||
#* 第五引数(boolean):ディメンションをアンロードしないかどうか | #* 第五引数(boolean):ディメンションをアンロードしないかどうか | ||
# 「DimensionManager#registerDimension」を呼び出し、こちらにもディメンションを登録します。 | # 「DimensionManager#registerDimension」を呼び出し、こちらにもディメンションを登録します。 | ||
+ | |||
+ | 例 | ||
+ | <source lang="java" line> | ||
+ | package samplemod; | ||
+ | |||
+ | import net.minecraft.world.DimensionType; | ||
+ | import net.minecraftforge.common.DimensionManager; | ||
+ | import net.minecraftforge.fml.common.Mod; | ||
+ | import net.minecraftforge.fml.common.event.FMLPreInitializationEvent; | ||
+ | import samplemod.world.sample.WorldProviderSample; | ||
+ | |||
+ | @Mod(modid = SampleMod.MOD_ID, name = SampleMod.MOD_NAME, version = SampleMod.MOD_VERSION) | ||
+ | public class SampleMod { | ||
+ | public static final String MOD_ID = "samplemod"; | ||
+ | public static final String MOD_NAME = "Sample Mod"; | ||
+ | public static final String MOD_VERSION = "0.1.0"; | ||
+ | |||
+ | public static DimensionType SAMPLE_DIMENSION; | ||
+ | |||
+ | @Mod.EventHandler | ||
+ | public void preInit(FMLPreInitializationEvent event) { | ||
+ | SAMPLE_DIMENSION = DimensionType.register("Sample Dimension", "_sample", DimensionManager.getNextFreeDimId(), WorldProviderSample.class, false); | ||
+ | DimensionManager.registerDimension(SAMPLE_DIMENSION.getId(), SAMPLE_DIMENSION); | ||
+ | |||
+ | } | ||
+ | |||
+ | } | ||
+ | |||
+ | </source> | ||
==== WorldProvider ==== | ==== WorldProvider ==== | ||
27行目: | 56行目: | ||
# 「DimensionType」の項で得た、このWorldProviderに対応する「DimensionType」列挙子を「getDimensionType」から返します。 | # 「DimensionType」の項で得た、このWorldProviderに対応する「DimensionType」列挙子を「getDimensionType」から返します。 | ||
− | {| | + | {| class="wikitable" |
!フィールド名!!影響!!参考 | !フィールド名!!影響!!参考 | ||
|- | |- | ||
46行目: | 75行目: | ||
− | {| | + | {| class="wikitable" |
!メソッド名!!影響!!参考 | !メソッド名!!影響!!参考 | ||
|- | |- | ||
69行目: | 98行目: | ||
* net.minecraft.client.renderer.EntityRenderer#setupFog | * net.minecraft.client.renderer.EntityRenderer#setupFog | ||
|- | |- | ||
− | |getWelcomeMessage|| | + | |getWelcomeMessage||ディメンション入場時のメッセージ?(未使用、Forge)|| |
|- | |- | ||
− | |getDepartMessage|| | + | |getDepartMessage||ディメンション退場時のメッセージ?(未使用、Forge)|| |
|} | |} | ||
95行目: | 124行目: | ||
### byte配列のindexに対応するバイオームIDをBiome配列から取り出して代入します(配列はオブジェクトなので、Chunkが持つ配列が変更されます)。 | ### byte配列のindexに対応するバイオームIDをBiome配列から取り出して代入します(配列はオブジェクトなので、Chunkが持つ配列が変更されます)。 | ||
− | {| | + | {| class="wikitable" |
!メソッド名!!操作!!参考 | !メソッド名!!操作!!参考 | ||
|- | |- | ||
126行目: | 155行目: | ||
|} | |} | ||
+ | ===== 例:岩盤の床だけのチャンクを生成する ===== | ||
+ | Y=0に岩盤の床があるだけのチャンクを生成します。 | ||
+ | |||
+ | ChunkGeneratorEmpty.java | ||
+ | <source lang="java" line> | ||
+ | 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) { | ||
+ | |||
+ | } | ||
+ | } | ||
+ | |||
+ | </source> | ||
+ | |||
+ | |||
+ | === ポータルを生成する === | ||
==== Teleporter ==== | ==== Teleporter ==== | ||
− | 「net.minecraft.world. | + | 「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によりデッドコード化されているため、座標は変更されない)。 | ||
+ | |||
+ | # 「Teleporter」のサブクラスを作成します。コンストラクタで目的地のWorldServerを受け取ってください。 | ||
+ | # 「makePortal」、「placeInExistingPortal」を再実装します。「placeInPortal」はエンドへの転送時の足場生成を変更する場合のみ、再実装するとよいでしょう。 | ||
+ | |||
+ | {| class="wikitable" | ||
+ | !メソッド名!!操作 | ||
+ | |- | ||
+ | |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」を経由して呼ばれています。 | ||
+ | |||
+ | <small> | ||
+ | バニラの「PlayerList#changePlayerDimension」は、ForgeによってTeleporter引数を追加したメソッド「#transferPlayerToDimension」に処理を移されています([https://github.com/MinecraftForge/MinecraftForge/blob/1.11.x/patches/minecraft/net/minecraft/server/management/PlayerList.java.patch#L151 GitHub/MinecraftForge/PlayerList.java.patch#L151])。「#transferPlayerToDimension」は、出発地のWorldServerからEntityPlayerMPを削除したのち、「#transferEntityToWorld」を呼び出しています。 | ||
+ | </small> | ||
− | + | <small> | |
+ | バニラの「PlayerList#transferEntityToWorld」は、ForgeによってTeleporter引数を追加した同名メソッドに処理を移されています([https://github.com/MinecraftForge/MinecraftForge/blob/1.11.x/patches/minecraft/net/minecraft/server/management/PlayerList.java.patch#L179 GitHub/MinecraftForge/PlayerList.java.patch#L179])。 | ||
+ | </small> | ||
− | + | 「PlayerList#transferEntityToWorld」のおおよその処理は「Entity#changeDimension」と同じですが、新しくEntityのインスタンスを作り直さず、「Entity#setWorld」を呼んで出発地のWorldに属するEntityインスタンスをそのまま目的地のWorldで再利用します。 | |
− | |||
− | + | また注意すべき点として、「PlayerList#transferEntityToWorld」は、出発地のWorldからEntityを削除しません。自分で出発地のWorldに対して「World#removeEntityDangerously」を呼んで、World及びChunkからEntityを削除する必要があります。 | |
− | # | ||
− | |||
− | + | ===== 例:「触れるとディメンションを移動するブロック」を追加する ===== | |
− | |||
− | |||
− | |||
− | + | BlockTeleporter.java | |
<source lang="java" line> | <source lang="java" line> | ||
− | public | + | 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 | |
− | MinecraftServer server = | + | 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); | ||
+ | } | ||
+ | } | ||
} | } | ||
} | } | ||
+ | |||
</source> | </source> |
2017年3月28日 (火) 23:09時点における最新版
この記事は"Minecraft Forge Universal 13.19.0.xxx~"を前提MODとしています。 |
目次
ディメンションの追加[編集]
ディメンションを生成する[編集]
DimensionType[編集]
「net.minecraft.world.DimensionType」は、ディメンションを登録する列挙型です。
ForgeがDimensionTypeに列挙子を追加するメソッド「DimensionType#register」を用意してくれているので、これを使います。
- 「net.minecraftforge.common.DimensionManager#getNextFreeDimId」から、使用可能なディメンションID(int)を取得します。
- 「DimensionType#register」を呼び出し、「DimensionType」型の戻り値を得ます。これが追加された列挙子で、WorldProviderで使います。
- 第一引数(String):ディメンションの名前(e.g. Overworld、Nether、The End)
- 第二引数(String):ディメンションのセーブデータ等で使う後置詞(e.g. _nether、_end)
- 第三引数(int):1で取得したID
- 第四引数(Class<? extends WorldProvider>):WorldProviderのクラス(下で解説)
- 第五引数(boolean):ディメンションをアンロードしないかどうか
- 「DimensionManager#registerDimension」を呼び出し、こちらにもディメンションを登録します。
例
package samplemod; import net.minecraft.world.DimensionType; import net.minecraftforge.common.DimensionManager; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.event.FMLPreInitializationEvent; import samplemod.world.sample.WorldProviderSample; @Mod(modid = SampleMod.MOD_ID, name = SampleMod.MOD_NAME, version = SampleMod.MOD_VERSION) public class SampleMod { public static final String MOD_ID = "samplemod"; public static final String MOD_NAME = "Sample Mod"; public static final String MOD_VERSION = "0.1.0"; public static DimensionType SAMPLE_DIMENSION; @Mod.EventHandler public void preInit(FMLPreInitializationEvent event) { SAMPLE_DIMENSION = DimensionType.register("Sample Dimension", "_sample", DimensionManager.getNextFreeDimId(), WorldProviderSample.class, false); DimensionManager.registerDimension(SAMPLE_DIMENSION.getId(), SAMPLE_DIMENSION); } }
WorldProvider[編集]
「net.minecraft.world.WorldProvider」は、ワールドの振る舞いを決定する抽象クラスです。
- 「WorldProvider」のサブクラスを作成します。
- コンストラクタから「WorldProvider#init」が呼ばれるので、WorldProviderのフィールド(下表参照)を好きなように変更してください。
- 「DimensionType」の項で得た、このWorldProviderに対応する「DimensionType」列挙子を「getDimensionType」から返します。
フィールド名 | 影響 | 参考 |
---|---|---|
doesWaterVaporize | 水の蒸発の有無 |
|
hasNoSky | 地図の描画、スポーン座標 |
|
hasSkyLight | 日光の有無 |
|
biomeProvider | バイオームの決定 | 下で解説 |
メソッド名 | 影響 | 参考 |
---|---|---|
calculateCelestialAngle | 昼夜の移り変わり | 0fで真昼(エンド)、.5fで真夜中(ネザー)
|
createChunkGenerator | チャンクの地形等、生成手法の決定 | net.minecraft.world.chunk.IChunkGeneratorを実装したクラスのインスタンスを作成して返す。詳しくは下で解説。 |
canRespawnHere | リスポーンの可否 | |
isSurfaceWorld | 空の描画の有無 | |
getVoidFogYFactor | 奈落の霧(Japan Wiki)の開始Y座標 | |
getHorizon | 地平線の高さ | |
doesXZShowFog | 霧の描画の有無 |
|
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」は、チャンクの地形や構造物を生成するインターフェースです。
- 「IChunkGenerator」を実装したクラスを作成します。コンストラクタでWorldProviderからWorldのインスタンスを受け取ってください。チャンクのインスタンス化に使います。
- 「#provideChunk」を実装します。第一引数はチャンクのX座標、第二引数はチャンクのZ座標です。
- 「net.minecraft.world.chunk.ChunkPrimer」をインスタンス化します。
- 上のWorld、ChunkPrimerを使って「net.minecraft.world.chunk.Chunk」をインスタンス化します。
- Chunk#setBlockStateなどを使って、上のChunkに地形を生成します。
- Chunkにバイオームを割り当てます。
- net.minecraft.world.chunk.ChunkProviderHellの手法
- Chunk#getBiomeArrayの戻り値としてバイオーム割り当てに使用するバイオームIDのbyte配列を得ます。
- 上のWorldからBiomeProviderを取得し、BiomeProvider#getBiomesから、チャンク座標に対応するバイオームのBiome配列を得ます。
- byte配列のindexに対応するバイオームIDをBiome配列から取り出して代入します(配列はオブジェクトなので、Chunkが持つ配列が変更されます)。
メソッド名 | 操作 | 参考 |
---|---|---|
provideChunk | チャンクのインスタンス化、地形生成 | |
populate | 湖、鉱石、ダンジョン等の生成 | |
generateStructures | 構造物の追加生成(海底遺跡で使用) | |
getPossibleCreatures | EntityCreatureの自然スポーン設定
|
|
getStrongholdGen | ダンジョンを生成するブロック座標を返す。なければnull。 | オーバーワールドの要塞を見つけるエンダーアイで使用。
|
recreateStructures | ダンジョン等MapGenBaseのサブクラスのインスタンスがあればMapGenBase#generateを呼ぶ。
チャンクをファイルから読み込む時に呼ばれる模様。ブロック生成以外の構造物のデータを読み込む? |
|
例:岩盤の床だけのチャンクを生成する[編集]
Y=0に岩盤の床があるだけのチャンクを生成します。
ChunkGeneratorEmpty.java
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によりデッドコード化されているため、座標は変更されない)。
- 「Teleporter」のサブクラスを作成します。コンストラクタで目的地のWorldServerを受け取ってください。
- 「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); } } } }