提供: Minecraft Modding Wiki
移動先: 案内検索
(Dimension間移動について、PlayerList#transferEntityToWorldは私の方で転送に成功していない(詳しくは未検証)ので、強引な手法を記述)
(Teleporterについて追記、Entity#changeDimension利用は不具合のもとなので、PlayerList#transferEntityToWorld利用を書いたら消す。)
69行目: 69行目:
 
* net.minecraft.client.renderer.EntityRenderer#setupFog
 
* net.minecraft.client.renderer.EntityRenderer#setupFog
 
|-
 
|-
|getWelcomeMessage||ディメンション入場時のメッセージ?(未使用)||
+
|getWelcomeMessage||ディメンション入場時のメッセージ?(未使用、Forge)||
 
|-
 
|-
|getDepartMessage||ディメンション退場時のメッセージ?(未使用)||
+
|getDepartMessage||ディメンション退場時のメッセージ?(未使用、Forge)||
 
|}
 
|}
  
127行目: 127行目:
  
  
=== ポータルを作成する ===
+
=== ポータルを生成する ===
  
 
==== Teleporter ====
 
==== Teleporter ====
「net.minecraft.world.Teleporter」は移動するEntityの現在座標をもとにポータルを生成するクラスです。
+
「net.minecraft.world.Teleporter」は移動するEntityの現在座標をもとにポータルを生成し、ポータル位置にEntityを移動させるクラスです。
  
EntityがDimension移動するのに使われる「net.minecraft.entity.Entity#changeDimension」では、「net.minecraft.world.WorldServer#getDefaultTeleporter」を呼び出し、「WorldServer#worldTeleporter」を得ています。
+
EntityがDimension移動するのに使われる「net.minecraft.entity.Entity#changeDimension」では、「net.minecraft.world.WorldServer#getDefaultTeleporter」を呼び出し、Teleporter型の「WorldServer#worldTeleporter」を得ています。
  
===== PlayerList#transferEntityToWorldを利用する場合 =====
+
また、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の別のディメンションへの転送は、Entity#dimensionを変更したのち、出発地のディメンションに対応するWorldServerからEntityを削除、座標を変更してTeleporterを呼び出し、目的地のディメンションに対応するWorldServerインスタンスを渡してEntityのクローンを作成し、クローンを目的地に強制スポーンさせることで実現しています(Entity#changeDimensionの操作)。
 +
 
 +
クローンのEntity#dimensionはコンストラクタでWorldServerから取得されることになり、最初の代入は無駄にも思えますが、ポータルを生成するTeleporterに渡されるのは最初の(出発地側の)Entityであるため、Teleporter内ではコンストラクタに渡される目的地のWorldServerのディメンションIDとEntity#dimensionは一致することになります(バニラでは転送先はWorldServerから取って判定しているのだが)。
 +
 
 +
===== PlayerList#transferEntityToWorldを使用する手法 =====
 
(未検証)
 
(未検証)
  
===== 強引にEntity#changeDimensionを利用する場合 =====
+
===== 強引にEntity#changeDimensionを使用する手法 =====
 
# AccessTransformerを使って、「private final」な「WorldServer#worldTeleporter」、SRG名「field_85177_Q」のアクセス修飾子を「public」にする(下ソース参照、net.minecraftforge.fml.relauncher.ReflectionHelperでも可)。
 
# AccessTransformerを使って、「private final」な「WorldServer#worldTeleporter」、SRG名「field_85177_Q」のアクセス修飾子を「public」にする(下ソース参照、net.minecraftforge.fml.relauncher.ReflectionHelperでも可)。
 
# 転送時、元々のWorldServer#worldTeleporterを退避して、好きなTeleporterインスタンスを代入。Entity#changeDimensionを呼んでEntityを移動させ、WorldServer#worldTeleporterを復元する。
 
# 転送時、元々のWorldServer#worldTeleporterを退避して、好きなTeleporterインスタンスを代入。Entity#changeDimensionを呼んでEntityを移動させ、WorldServer#worldTeleporterを復元する。
146行目: 168行目:
 
</source>
 
</source>
  
2の例
+
2の例。ただし以下の点で不完全。
 +
* エンド以外から転送する場合、ネザーへの転送では座標が1/8倍、オーバーワールドへの転送では座標が8倍される。
 +
** オーバーワールド-ネザー間では望ましいが、追加ディメンションからオーバーワールドに帰還するときTeleporterに渡される座標が狂う。
 +
 
 
<source lang="java" line>
 
<source lang="java" line>
 
public static <T extends Entity> void transferEntity(World worldIn, BlockPos portalPos, T entityIn,
 
public static <T extends Entity> void transferEntity(World worldIn, BlockPos portalPos, T entityIn,
152行目: 177行目:
 
                                                     Consumer<T> callback) {
 
                                                     Consumer<T> callback) {
 
     entityIn.setPortal(portalPos);
 
     entityIn.setPortal(portalPos);
     entityIn.timeUntilPortal = 10;
+
     entityIn.timeUntilPortal = 10; // この2行は不要?
  
 
     if (!worldIn.isRemote && !entityIn.isDead) {
 
     if (!worldIn.isRemote && !entityIn.isDead) {

2017年3月26日 (日) 12:13時点における版

この記事は"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


ポータルを生成する

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の別のディメンションへの転送は、Entity#dimensionを変更したのち、出発地のディメンションに対応するWorldServerからEntityを削除、座標を変更してTeleporterを呼び出し、目的地のディメンションに対応するWorldServerインスタンスを渡してEntityのクローンを作成し、クローンを目的地に強制スポーンさせることで実現しています(Entity#changeDimensionの操作)。

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

PlayerList#transferEntityToWorldを使用する手法

(未検証)

強引にEntity#changeDimensionを使用する手法
  1. AccessTransformerを使って、「private final」な「WorldServer#worldTeleporter」、SRG名「field_85177_Q」のアクセス修飾子を「public」にする(下ソース参照、net.minecraftforge.fml.relauncher.ReflectionHelperでも可)。
  2. 転送時、元々のWorldServer#worldTeleporterを退避して、好きなTeleporterインスタンスを代入。Entity#changeDimensionを呼んでEntityを移動させ、WorldServer#worldTeleporterを復元する。

META-INF/<modid>_at.cfg

public-f net.minecraft.world.WorldServer field_85177_Q #worldTeleporter non-final

2の例。ただし以下の点で不完全。

  • エンド以外から転送する場合、ネザーへの転送では座標が1/8倍、オーバーワールドへの転送では座標が8倍される。
    • オーバーワールド-ネザー間では望ましいが、追加ディメンションからオーバーワールドに帰還するときTeleporterに渡される座標が狂う。
public static <T extends Entity> void transferEntity(World worldIn, BlockPos portalPos, T entityIn,
                                                     int destDimension, Function<WorldServer, Teleporter> funcTeleporter,
                                                     Consumer<T> callback) {
    entityIn.setPortal(portalPos);
    entityIn.timeUntilPortal = 10; // この2行は不要?

    if (!worldIn.isRemote && !entityIn.isDead) {
        MinecraftServer server = entityIn.getServer();
        WorldServer destServer = server.worldServerForDimension(destDimension);

        Teleporter teleporter = funcTeleporter.apply(destServer);
        Teleporter cache = destServer.worldTeleporter; // Access Transformer | private final -> public

        destServer.worldTeleporter = teleporter;
        T clone = (T) entityIn.changeDimension(destDimension);
        callback.accept(clone); // 座標設定等

        destServer.worldTeleporter = cache;

    }
}