1.3ForgeModding

目次


@Mod

FMLは以下のようなBaseModを使用しない.

public class mod_Something extends BaseMod
 

現在のFMLは以下のようにアノテーションを用いてどのような名前のクラスでも利用できる.

@Mod( modid = "ModのID, 他のModと重複しない固有の文字列", name="mod名", version="modのバージョン")
public class MyNewFMLMod
{
     //必要な処理を書くが, コンストラクタは不要
}
 

loadメソッドは不要で, アノテーションによって指定するため欲しいメソッドを定義すればよい.

import cpw.mods.fml.common.Mod;
 
@Mod( modid = "ModID", name="mod名", version="modバージョン")
public class myNewFMLMod
{
     // コンストラクタに相当
     @Mod.Init
     public void myNewLoadMethod(FMLInitializationEvent event)
     {
     }
 
     @Mod.PreInit  // ModLoaderにはなかった, 前処理に相当
     public void myNewPreLoadMethod(FMLPreInitializationEvent evt)
     {
     }
 
     @Mod.PostInit // ModLoaderにおけるmodsLoadedに相当
     public void myNewPostLoadMethod(FMLPostInitializationEvent evt)
     {
     }
}
 

@NetworkMod

NetworkModの継承の代わりに, @modの次に書く@NetworkModアノテーションを追加

@NetworkMod(clientSideRequired = true, serverSideRequired = false)
 

この場合, このModはクライアント側に必要で, 接続するユーザーがこのModを導入していない場合, サーバーがログインを許可しないことを意味する.
要素はこれだけでなく,

@NetworkMod(
     clientSideRequired = true,
     serverSideRequired = false,
     channels = {"channel1", "channel2", "channel3"}, // PacketHandlerで利用するチャンネル名, 上限は無い
     packetHandler = yourPacketHandlerClass.class, // パケット交換を行うIPacketHandlerを実装したクラスを指定
     connectionHandler = yourConnectionHandler.class,  // コネクション確立を行うIConnectionHandlerを実装したクラスを指定
     versionBounds = "[1.3]" // クライアントとサーバーのバージョン互換
     )
 

バージョンチェックは,
  • もし4.0.2.100のようにForge形式でおいた場合, ただひとつ, 4.0.2.100のみチェック
  • [4.0]とした場合, バージョンの上位2桁をチェック
  • [4.0.2]とした場合, 上位3桁をチェック, 将来的にはこれが大抵使われる.

とはいえSMPを意識しないのであればversionBoundsはなくてもいい. SMPでも無ければバージョンが完全一致しないとログインできなくなるだけ.

プロキシシステム

従来用いられていたプロキシパターンがAPI側に取り込むことで, コードの統一化を可能にした. クライアント, サーバーに共通する処理はCommonProxyに, クライアントでのみ行う処理はClientProxy(もちろんClientProxyはCommonProxyを継承している)で行う. 例として, cpw氏のironchestのソースコードがhttps://github.com/cpw/ironchestにある.

@Mod( modid = "CatapultMod", name="Catapult Mod", version="0.0.1-beta")
@NetworkMod(clientSideRequired = true, serverSideRequired = false, channels = {"cat_vehicle"}, packetHandler = PacketHandler.class)
public class CatapultMod2
{   
    // アノテーションにCommonProxyとClientProxyの場所を書くことで, サーバー側, クライアント側で異なるプロキシを生成する    
    @SidedProxy(clientSide = "shadowmage.catapult.ClientProxy", serverSide = "shadowmage.catapult.CommonProxy")
    public static CommonProxy proxy; // This object will be populated with the class that you choose for the environment
 
    // @Instanceアノテーションによって, 自身のインスタンスを生成する
    // @Instance
    @Instance("CatapultMod2") // ModIDが必要
    public static CatapultMod2 instance; // The instance of the mod that will be defined, populated, and callable
 
    @Init
    public void load(FMLInitializationEvent evt)
    {
        ItemLoader.INSTANCE.loadItems(); 
        proxy.registerRenderInformation(); //You have to call the methods in your proxy class
        EntityRegistry.registerModEntity(EntityTest.class, "Catapult", 1, this, 250, 5, false);
        ModLoader.registerEntityID(EntityTest.class, "Catapult", 220);  
    }
}
 

CommonProxyはクライアントとサーバーの共通の処理, たとえばGUIを追加する場合, CommonProxyにIGuiHandlerを実装する. CommonProxyでは何もしないが, ClientProxyでは何かするメソッド, たとえばクライアント側ではMinecraftForgeClient.preloadTexture()をする場合, CommonProxyではregiserTexturesでは何もせず, ClientProxyでregisterTexturesをオーバーライドすればよい. 基本的にClientProxyはクライアント側特有の処理, カスタムレンダリング, キーボードイベントなどを行う.

サンプル

GUIについては後述.

CommonProxy

package shadowmage.catapult;
 
import net.minecraft.src.EntityPlayer;
import net.minecraft.src.World;
import cpw.mods.fml.common.Mod.Instance;
import cpw.mods.fml.common.network.IGuiHandler;
 
public class CommonProxy implements IGuiHandler
{
    // クライアント側のみの処理, Entityのレンダーの登録などの描画関連
    public void registerRenderInformation() 
    {
        // サーバー側では何もしない
    }
 
    @Override
    public Object getServerGuiElement(int ID, EntityPlayer player, World world, int x, int y, int z)
    {
        // TODO Auto-generated method stub
        return null;
    }
 
    @Override
    public Object getClientGuiElement(int ID, EntityPlayer player, World world, int x, int y, int z)
    {
        // TODO Auto-generated method stub
        return null;
    }
}
 

ClientProxy

ClientProxyはCommonProxyを継承して作ることを忘れてはならない.

package shadowmage.catapult;
 
import net.minecraftforge.client.MinecraftForgeClient;
import cpw.mods.fml.client.registry.RenderingRegistry;
import cpw.mods.fml.common.Mod.Instance;
 
public class ClientProxy extends CommonProxy
{
    @Override
    public void registerRenderInformation() 
    {  
        MinecraftForgeClient.preloadTexture("/Catapult_Mod/gui/items.png");
        MinecraftForgeClient.preloadTexture("/Catapult_Mod/gui/catapult.png");
        RenderingRegistry.instance().registerEntityRenderingHandler(EntityTest.class, new RenderCatTest());
    }
    // クライアント側でサーバー側とふるまいが異なるものは適宜オーバーライドする
}
 

GUIの追加

基本的にCommonProxyにIGuiHandlerを実装して, @InitでNetworkRegistryに登録するだけ.

public class CommonProxy implements IGuiHandler
{
    @Override
    public Object getServerGuiElement(int ID, EntityPlayer player, World world, int x, int y, int z)
    {
        // ContainerHogeHogeを返す;
    }
 
    @Override
    public Object getClientGuiElement(int ID, EntityPlayer player, World world, int x, int y, int z)
    {
        // GuiHogeHogeを返す
    }
}
 

あとは@Initで登録. instanceは@Modのインスタンス

@Init
public void init(FMLInitializationEvent event)
{
    // いろんな処理
    // NetworkRegistry
    NetworkRegistry.instance().registerGuiHandler(instance, proxy);
}
 

実際にGUIを呼ぶのは, 今までのModLoader.openGUIのところを以下のようにする.

entityPlayer.openGui(@Mod.insntace, @Mod.guiId, world, x, y, z)
 

GuiIDは重複のない固有の数値でなければらないが, getUniqueGuiIDのようなメソッドはないので, configで設定可能にする.

ブロックのカスタムレンダリング

ブロックのカスタムレンダリングを行う従来のメソッド.
BaseMod.renderInvBlock()
BaseMod.renderWorldBlock()
 

この2つはインタフェースISimpleBlockRenderingHandlerによって実装される.

public class yourBlockRenderClass implements ISimpleBlockRenderingHandler
{
    public void renderInventoryBlock(Block block, int metadata, int modelID, RenderBlocks renderer)
    {
         return false;
    }
 
    public boolean renderWorldBlock(IBlockAccess world, int x, int y, int z, Block block, int modelId, RenderBlocks renderer);
    {
         return false;
    }
 
    public boolean shouldRender3DInInventory();
    {
         return false;
    }
 
    public int getRenderId();
    {
         return 0;
    }
}
 

レンダリングを行うクラスを作る. 新しいmodelIDの取得と, ハンドラの登録はcpw.mods.fml.client.registry.RenderingRegistryが行う.

RenderingRegistry.getNextAvailableRenderId(); // 新しいmodelIDを取得
RenderingRegistry.registerBlockHandler(ISimpleBlockRenderingHandler); // ハンドラの登録
 

新しいmodelIDはClientProxyで取得し, @Modクラスで従来どおりpublic staticなメンバとして保持しておくと, Blockからも参照しやすい.

レシピと精錬の追加


ModLoader.addRecipe()
ModLoader.addSmelting()
 

などはGameRegistryが行う.

GameRegistry.addRecipe()
GameRegistry.addSmelting()
 

名前付け, ModLoader.addNameに相当

アイテムやブロックに名前をつけるメソッド.

ModLoader.addName()
 

これらはLanguageRegistryが行う.

LaunguageRegistry.instance().addStringLocalization(key, lang, name)
LaunguageRegistry.instance().addNameForObject(object, lang, name)
 

燃料の追加

従来のBaseMod.addFuelはIFuelHandlerに置き換えられた.

public interface IFuelHandler
{
    int getBurnTime(ItemStack fuel);
}
 

あとはGameRegistry.registerFuelHandler()で登録すればよい.

GameRegistry.registerFuelHandler(IFuelHandler handler);
 

EntityとMob

非EntityLiving


@Init
public void load(FMLInitializationEvent evt)
{
  proxy.registerRenderInformation(); // Client Proxyでrenderer(s)呼び出し
 
  // Entityの登録(サーバーとの同期用)
  // 引数: entClass, entName, ID, mod, trackingRange, updateFrequency, sendVelocityUpdates // Just like before, just called
  EntityRegistry.registerModEntity(EntityTest.class, "entName", 1, this, 250, 5, false);
 
  // ModLoader.registerEntityに相当するメソッド
  EntityRegistry.registerGlobalEntityID(EntityTest.class, "entName", 220);//最後の引数はEntityID, 重複のない固有の値
  // 0-110まではバニラのEntityIDが使っている
  // ModLoader.getUniqueEntityId()で取得してもよい
  // EntityRegistry.registerGlobalEntityID(EntityTest.class, "entName", ModLoader.getUniqueEntityId());
  // もしスポーンエッグを追加するなら最後の引数に卵の背景色, 卵の斑の色
  // EntityRegistry.registerGlobalEntityID(EntityTest.class, "entName", ModLoader.getUniqueEntityId(), 0x00000, 0x000000);
}
 

ClientProxyでEntityのRenderを登録する

public class ClientProxy extends CommonProxy
{
 
    @Override
    public void registerRenderInformation() 
    {
        // BaseModのaddRenderer(Map map)でやるのと一緒
        RenderingRegistry.instance().registerEntityRenderingHandler(EntityTest.class, new RenderCatTest());   
    }
}
 

テクスチャの参照場所は今までと同じ, bin以下

EntityLiving

非EntityLivingとほぼ同じ

@Init
public void load(FMLInitializationEvent event)
{
    // ClientProxyでレンダーを登録
    ClientProxy.registerRenderInformation();
    // Entityの名前を登録
    LanguageRegistry.instance().addStringLocalization("entity.Elephant.name", "en_US", "Elephant");
    // Entityを登録
    EntityRegistry.registerGlobalEntityID(EntityElephant.class, "Elephant", ModLoader.getUniqueEntityId());
}
 

なお, CommonProxyにも中身が空のregisterRenderInformation()を用意しておく必要がある.

{
public class CommonProxy implements IGuiHandler
{
    public static void registerRenderInformation()
    {
        // 何もしない
    }
 
    @Override
    public Object getServerGuiElement(int ID, EntityPlayer player, World world, int x, int y, int z)
    {
        return null;
    }
 
    @Override
    public Object getClientGuiElement(int ID, EntityPlayer player, World world, int x, int y, int z)
    {
        return null;
    }
}
 



鉱石の生成など

BaseMod.generateSurfaceに相当する機能はcpw.mods.fml.common.IWorldGeneratorによって実現する.

public interface IWorldGenerator
{
    /**
     * Generate some world
     *
     * @param random the chunk specific {@link Random}.
     * @param chunkX the chunk X coordinate of this chunk.
     * @param chunkZ the chunk Z coordinate of this chunk.
     * @param world : additionalData[0] The minecraft {@link World} we're generating for.
     * @param generator : additionalData[1] The {@link IChunkProvider} that is generating.
     * @param chunkProvider : additionalData[2] {@link IChunkProvider} that is requesting the world generation.
     *
     */
    public void generate(Random random, int chunkX, int chunkZ, World world, IChunkProvider chunkGenerator, IChunkProvider chunkProvider);
}
 

このインタフェースを適当なクラスで実装する際, BaseMod.generateSurfaceとの差異をなくすためには, 以下のようにする.

@Override
    public void generate(Random random, int chunkX, int chunkZ, World world, IChunkProvider chunkGenerator, IChunkProvider chunkProvider)
    {
         switch (world.provider.worldType)
         {
             case -1 :
                 this.generateNether(world, random, chunkX << 4, chunkZ << 4);
                 break;
 
             case 0 :
                 this.generateSurface(world, random, chunkX << 4, chunkZ << 4);
                 break;
 
             default :	
         }
    }
 
    private void generateSurface(World world, Random random, int chunkX, int chunkZ)
    {
        // 生成処理
    }
 
    private void generateNether(World world, Random random, int chunkX, int chunkZ)
    {
        // 生成処理
    }
 

ModLoaderにおけるchunkX, chunkZとForgeにおけるchunkX, chunkZの扱いが異なるため, 4bit左シフト(=16倍)する必要がある. あとはBaseMod.generateSurfaceのときと同じようにやればよい.
このインタフェースを実装したクラスを, modの主要クラス(@Modクラス)の@Init内で, GameRegistryに登録すると実行時に呼び出される.

GameRegistry.registerWorldGenerator(IWorldGenerator)
 

以下は1.3.2時点のバニラのバイオーム一覧
  • Ocean
  • Plains
  • Desert
  • Extreme Hills
  • Forest
  • Taiga
  • Swampland
  • River
  • Hell // This is the biome for the Nether
  • Sky // This is the biome for the sky world (now removed)
  • FrozenOcean
  • FrozenRiver
  • Ice Plains
  • Ice Mountains
  • MushroomIsland
  • MushroomIslandShore
  • Beach
  • DesertHills
  • ForestHills
  • TaigaHills
  • Extreme Hills Edge
  • Jungle
  • JungleHills

カスタムパケット

@NetworkModの引数で,

@NetworkMod(
     clientSideRequired = true,
     serverSideRequired = false,
     channels = {"channel1", "channel2", "channel3"}, // PacketHandlerで利用するチャンネル名, 上限は無い
     packetHandler = yourPacketHandlerClass.class, // パケット交換を行うIPacketHandlerを実装したクラスを指定
     connectionHandler = yourConnectionHandler.class,  // コネクション確立を行うIConnectionHandlerを実装したクラスを指定
     versionBounds = "[1.3]" // バージョンのチェック
     )
 
packetHandlerに指定したクラスでカスタムパケットを実装すると, 適切なタイミングで呼ばれる.
TileEntityのパケット交換が行われえるのはSSPならゲームの開始時と終了時?NBTで保存してあるデータもパケットで飛ばして拾えるようにしておくとちゃんと保存される.

public interface IPacketHandler
{
    /**
     * Recieve a packet from one of the registered channels for this packet handler
     * @param manager The network manager this packet arrived from
     * @param packet The packet itself
     * @param player A dummy interface representing the player - it can be cast into a real player instance if needed
     */
    public void onPacketData(NetworkManager manager, Packet250CustomPayload packet, Player player);
}
 

TileEntityに関しては, TileEntityを継承したクラスで以下のメソッドを書くと, パケットが送られる.

@Override
public Packet getAuxillaryInfoPacket() {
    return PacketHandler.getPacket(this);
}
 

サンプル(IronChestのPacketHander.java)

public class PacketHandler implements IPacketHandler
{
    @Override
    public void onPacketData(NetworkManager network, Packet250CustomPayload packet, Player player)
    {
        ByteArrayDataInput dat = ByteStreams.newDataInput(packet.data);
        int x = dat.readInt();
        int y = dat.readInt();
        int z = dat.readInt();
        byte typ = dat.readByte();
        boolean hasStacks = dat.readByte() != 0;
        int[] items = new int[0];
        if (hasStacks)
        {
            items = new int[24];
            for (int i = 0; i < items.length; i++)
            {
                items[i] = dat.readInt();
            }
        }
 
        World world = IronChest.proxy.getClientWorld();
        TileEntity te = world.getBlockTileEntity(x, y, z);
        if (te instanceof TileEntityIronChest)
        {
            TileEntityIronChest icte = (TileEntityIronChest) te;
            icte.handlePacketData(typ, items);
        }
    }
 
    public static Packet getPacket(TileEntityIronChest tileEntityIronChest)
    {
        ByteArrayOutputStream bos = new ByteArrayOutputStream(140);
        DataOutputStream dos = new DataOutputStream(bos);
        int x = tileEntityIronChest.xCoord;
        int y = tileEntityIronChest.yCoord;
        int z = tileEntityIronChest.zCoord;
        int typ = tileEntityIronChest.getType().ordinal();
        int[] items = tileEntityIronChest.buildIntDataList();
        boolean hasStacks = (items != null);
 
        try
        {
            dos.writeInt(x);
            dos.writeInt(y);
            dos.writeInt(z);
            dos.writeByte(typ);
            dos.writeByte(hasStacks ? 1 : 0);
            if (hasStacks)
            {
                for (int i = 0; i < 24; i++)
                {
                    dos.writeInt(items[i]);
                }
            }
        }
        catch (IOException e)
        {
            // UNPOSSIBLE?
        }
        Packet250CustomPayload pkt = new Packet250CustomPayload();
        pkt.channel = "IronChest";
        pkt.data = bos.toByteArray();
        pkt.length = bos.size();
        pkt.isChunkDataPacket = true;
        return pkt;
    }
}
 

Ticking

TickingとはBaseMod.onTickInGUI()やBaseMod.onTickGame()に相当する処理のことである.
触ったことがないので参考サイトをそのまま.

インタフェースITickHandlerを実装することで代替にする.

public interface ITickHandler
{
 
    /**
     * Called at the "start" phase of a tick
     * 
     * Multiple ticks may fire simultaneously- you will only be called once with all the firing ticks
     * 
     * @param type
     * @param tickData
     */
    public void tickStart(EnumSet<TickType> type, Object... tickData);
 
    /**
     * Called at the "end" phase of a tick
     * 
     * Multiple ticks may fire simultaneously- you will only be called once with all the firing ticks
     * 
     * @param type
     * @param tickData
     */
    public void tickEnd(EnumSet<TickType> type, Object... tickData);
 
    /**
     * Returns the list of ticks this tick handler is interested in receiving at the minute
     * 
     * @return
     */
    public EnumSet<TickType> ticks();
 
    /**
     * A profiling label for this tick handler
     * @return
     */
    public String getLabel();
}
 

クラスの登録はClientProxyで行う.
TickRegistry.registerTickHandler(new ClientTickHandler(), Side.CLIENT);
 

ITickHandlerを実装したクラス
public class ClientTickHandler implements ITickHandler
{
    @Override
    public void tickStart(EnumSet<TickType> type, Object... tickData) {}
 
    @Override
    public void tickEnd(EnumSet<TickType> type, Object... tickData)
    {
        if (type.equals(EnumSet.of(TickType.RENDER)))
        {
            onRenderTick();
        }
        else if (type.equals(EnumSet.of(TickType.CLIENT)))
        {
            GuiScreen guiscreen = Minecraft.getMinecraft().currentScreen;
            if (guiscreen != null)
            {
                onTickInGUI(guiscreen);
            } else {
                onTickInGame();
            }
        }
    }
 
    @Override
    public EnumSet<TickType> ticks()
    {
        return EnumSet.of(TickType.RENDER, TickType.CLIENT);
        // In my testing only RENDER, CLIENT, & PLAYER did anything on the client side.
        // Read 'cpw.mods.fml.common.TickType.java' for a full list and description of available types
    }
 
    @Override
    public String getLabel() { return null; }
 
 
    public void onRenderTick()
    {
        //System.out.println("onRenderTick");
        //TODO: Your Code Here
    }
 
    public void onTickInGUI(GuiScreen guiscreen)
    {
        //System.out.println("onTickInGUI");
        //TODO: Your Code Here
    }
 
    public void onTickInGame()
    {
        //System.out.println("onTickInGame");
        //TODO: Your Code Here
    }
}
 
やったことがないので本当にそのまま. ただITickHandlerもTickRegistryもcommonパッケージにあるので, サーバー側でも実装可能である.

キーバインド

こちらも触れたことがないのでそのまま.

登録はクライアント側のみ
KeyBindingRegistry.registerKeyBinding(new MyKeyHandler());
 

MyKeyHandler
public class MyKeyHandler extends KeyHandler
{
    static KeyBinding myBinding = new KeyBinding("MyBind", Keyboard.KEY_M);
 
    public MyKeyHandler()
    {
        //the first value is an array of KeyBindings, the second is whether or not the call 
        //keyDown should repeat as long as the key is down
        super(new KeyBinding[]{myBinding}, new boolean[]{false});
    }
 
    @Override
    public String getLabel()
    {
        return "mykeybindings";
    }
 
    @Override
    public void keyDown(EnumSet<TickType> types, KeyBinding kb, boolean tickEnd, boolean isRepeat)
    {
        //do whatever
    }
 
    @Override
    public void keyUp(EnumSet<TickType> types, KeyBinding kb, boolean tickEnd)
    {
        //do whatever
    }
 
    @Override
    public EnumSet<TickType> ticks()
    {
        return EnumSet.of(TickType.CLIENT);
        //I am unsure if any different TickTypes have any different effects.
    }
 
KeyHandlerクラス自体マイクラ本体にもFMLにもForgeにもなかったので詳細不明. このクラスではMキーが押されたときの処理らしい. 注意すべきはスーパークラスの引数の配列の要素数が同じじゃないとダメ.


Forgeの機能

スプライトIDの無制限化

利用したいテクスチャファイルはブロックでもアイテムでも256×256, 16×16のスプライトが16×16個あると考える(terrain.pngやgui/items.pngと同じ).
以前まではITextureProviderによって提供されていた機能だが, 4xからは削除され, より容易に変更できるようになった.
利用するにはBlockないしItemを継承したクラスで以下のメソッドをオーバーライドする.
@Override
String getTextureFile()
{
    return "my_terrain.png";
}
 
画像ファイルmy_terrain.pngはMCP環境下ではbin/minecraftフォルダ以下に置く.

セッタも用意されているが, サーバー側では呼ばれない?ようで, リモートサーバー環境ではテクスチャが適応されなかった(4.0.0.200時点).
setTextureFile(filename);
 

コンフィグファイルの読み込み

FMLにもModLoaderとの互換性向けにMLPropが存在するが, ForgeのConfigurationを利用するほうがより柔軟なコンフィグファイルを利用できる.
以下はForge4x以降のコンフィグファイルの設定例.

@Mod(modid = "Sample", name = "Sample", version = "1.0")
@NetworkMod(channels = { "Sample" }, clientSideRequired = true, serverSideRequired = false, packetHandler = PacketHandler.class, versionBounds = "[4.0]")
public classs Sample
{
    @PreInit
    public void preInit(FMLPreInitializationEvent event)
    {
        Configuration cfg = new Configuration(event.getSuggestedConfigurationFile());
        try
        {
            cfg.load();
            blockId  = cfg.getOrCreateIntProperty("blockid", Configuration.CATEGORY_BLOCK, 1000).getInt();
            //blockId = cfg.getOrCreateBlockIdProperty("blockid", 1000).getInt();
            itemId   = cfg.getOrCreateIntProperty("itemid",  Configuration.CATEGORY_ITEM, 4000).getInt();
            isRotate = cfg.getOrCreateBooleanProperty("isRotate", Configuration.CATEGORY_GENERAL, true).getBoolean(true);
        }
        catch (Exception e)
        {
            FMLLog.log(Level.SEVERE, e, "エラーメッセージ");
        }
        finally
        {
            cfg.save();
        }
    }
}
 

event.getSuggestedConfigurationFile()でconfig/@Mod.name.cfgが返ってくる.
Configurationのカテゴリは以下の3つ.
  • CATEGORY_BLOCK
  • CATEGORY_ITEM
  • CATEGORY_GENERAL
利用できる型はint, boolean Stringの3つ. 詳細はForgeAPI/Configurationに.
getInt(), getBoolean()はPropertyクラスなので注意. これはJavaのPropertiesとは別の, Forgeが用意しているコレクション.

getOrCreateBlockPropertyを利用した場合, ブロックIDが重複している場合, 自動でブロックIDの末尾(4095)から逆順に割り振ってくれる.
しかし, SMPを考慮する場合自動割り当ては避けたほうがよい.

コンフィグファイルにコメントを書く場合は, Property.commentに書き込めばよい.

Property blockProperty = cfg.getOrCreateIntProperty("blockid", CATEGORY_BLOCK, 1000);
blockProperty.comment = "comment...";
int blockid = blockProperty.getInt();
 

鉱石辞書

IC, RP, FFMなどでよく利用される, 追加鉱石の共有を可能とする.
基本的な利用方法は以下の2点.
  • 鉱石辞書への登録
  • 鉱石辞書を参照したレシピの作成

鉱石辞書への登録

public static void registerOre(String name, Item      ore)
public static void registerOre(String name, Block     ore)
public static void registerOre(String name, ItemStack ore)
public static void registerOre(int    id,   Item      ore)
public static void registerOre(int    id,   Block     ore)
public static void registerOre(int    id,   ItemStack ore)
 
鉱石の名前ないし重複の無いIDと, Item, Block, (ダメージ値, メタデータを利用しているなら)ItemStackを紐付けする.
たとえば銀鉱石と銀インゴットを追加したとして, それらを鉱石辞書に登録するには以下のようにする.
OreDictionary.registerOre("oreSilver", blockOreSilver);
OreDictionary.registerOre("ingotSilver", itemIngotSilver);
 
他のmodとの連携する場合, 文字列だけ提供すればよいことになる.
IC2, FFM, RP2が登録している鉱石は以下のとおり.
  • oreTin, oreCopper, oreSilver, oreUranium
  • ingotTin, ingotCopper, ingotSilver, ingotBrass, ingotBronze
  • ingotUranium, ingotRefinedIron
  • dyeBlue
  • gemRuby, gemEmerald, gemSapphire
  • itemDropUranium
  • woodRubber
  • itemRubber

鉱石辞書を利用したレシピの作成

GameRegistry.addRecipe(
    new ShapedOreRecipe(
        new ItemStack(itemSilverShovel, 1),
            new Object[]
            {
                "X", "Y", "Y",
                Character.valueOf('X'), "ingotSilver",
                Character.valueOf('Y'), Item.stick
            }));
 
GameRegistry.addRecipe(
    new ShapelessOreRecipe(
        new ItemStack(ingotSilverIron, 1),
            new Object[]
            {
                "ingotSilver", Item.ingotIron
            }));
 
前者はaddRecipeに, 後者はaddShapelessRecipeに対応する. 通常のレシピと異なり, 文字列でする.
ShapedOreRecile, ShapelessOreRecipeはどちらもIRecipeを継承しているので, GameRegistry.addRecipe()の引数でnewしてやればよい.

Event

従来のハンドラに代わる機能で, 将来的にbukkitやMODAPIのプラグインに対応するため, 作られたらしい.
イベントを使うには, 適当なクラスでイベントを処理するメソッドを, 特定のイベントクラスを引数として, @ForgeSubscribeアノテーションによって指定する.
例として, 以下はツールが破壊したときに呼び出されるイベントである.

public class EventHooks
{
    @ForgeSubscribe
    public void toolDestroyed(PlayerDestroyItemEvent event)
    {
        EntityPlayer player = event.entityPlayer;
        ItemStack destoryedItem = event.original;
 
        if (event.isCancelable())
        {
            event.setCanceled(true);
        }
 
        // 処理
    }
}
 

このイベントの処理において, 特定のツールが壊れたらそのツールを別のアイテムにする, という処理が出来るようになる.
このイベントクラスを@Modクラスの@Initメソッドなどでイベント登録をすると, 適切なタイミングで設定したメソッドが呼ばれる.

MinecraftForge.EVENT_BUS.register(new EventHooks());
 

なお, イベントは過去のハンドラとほとんど差し替えられている. 骨粉による追加アイテムの成長促進もイベントになっている.

イベント

minecraftforge.event

  • Event
  • CommandEvent

minecraftforge.event.world

  • ChunkEvent
  • ChunkDataEvent
  • WorldEvent

minecraftforge.event.entity

  • EntityEvent
  • PlaySoundAtEntityEvent
  • UseHoeEvent

minecraftforge.event.entity.living

  • LivingEvent
  • LivingAttackEvent (EntityLivingが攻撃してくるときのイベント)
  • LivingDeathEvent (EntityLivingが死ぬときのイベント)
  • LivingDropsEvent (EntityLivingがアイテムをドロップするイベント)
  • LivingFallEvent (EntityLivingが落下するとき?のイベント)
  • LivingHurtEvent (EntityLivingがダメージを受けたときのイベント)
  • LivingSetAttackTargetEvent (EntityLivingがターゲットを決定するときのイベント)
  • LivingSpecialSpawnEvent (EntityLivingが特定の条件でスポーンするときのイベント)

minecraftforge.event.entity.minecart

  • MinecartEvent
  • MinecartCollisionEvent
  • MinecartInteractEvent
  • MinecartUpdateEvent

minecraftforge.event.entity.player

  • PlayerEvent
  • AttackEntityEvent (プレイヤーがEntityに対して攻撃したときのイベント)
  • ArrowNockEvent (プレイヤーが矢を持った状態で弓を右クリックしたときのイベント)
  • ArrowLooseEvent (プレイヤーが矢を撃つイベント)
  • EntityInteractEvent (プレイヤーがEntityに対してクリックしたときのイベント)
  • BonemealEvent (プレイヤーが骨粉を使用したときのイベント)
  • EntityItemPickupEvent (プレイヤーがEntityItemを拾ったときのイベント)
  • FillBucketEvent (バケツでブロックを掬う, こぼすイベント)
  • PlayerDestroyItemEvent (プレイヤーが使っていたアイテムが壊れるときのイベント)
  • PlayerSleepInBedEvent (プレイヤーがベッドで寝ているときのイベント)
最終更新:2012年09月22日 19:25