Achtung: Dieses Tutorial ist an vielen Stellen veraltet. Ich werde es sobald ich Zeit habe updaten.
Da ich gesehen hab, dass es auf Deutsch noch (fast) keine Forge-Tutorials für die aktuelle Version gibt, erstelle ich jetzt mal eins.
Das Installieren von Forge sollte bekannt sein. Wenn nicht, ich hab im englischen Forge-Wiki den Artikel übersetzt: http://www.minecraftforge.net/wiki/I...tion/Source/de. Die Übersetzung könnte noch etwas wischi-waschi sein.
Achtung
imports werden in diesem Tutorial nicht angezeigt, da es allgemein bekannt ist, dass man eine IDE wie Eclipse oder ähnlichem zum Entwickeln nutzen sollte.
1.0 - Implementation der Annotationen
Forge benutzt mit den Versionen ab 4.x aufwährts keine Vererbung mehr von Klassen wie z.B. der ModLoader:
Code:
Klasse_XY extends BaseMod
Das funktioniert nun mit Annotationen:
Code:
@Mod(modid = "Eine Mod-ID", name = "Der Name der Mod", version = "Die Version der Mod")
public class Klasse_XY
{
}
Dabei ist der Klassenname der Mod völlig egal. Das einzig wichtige ist, dass man die @Mod-Annotation verwendet.
1.1 - Network Mod
Bei ModLoaderMP nutzt man ja die Vererbung der Klasse "BaseModMP", dies wird nun nicht mehr gebraucht. Unter Forge funktioniert das folgendermaßen:
Code:
@Mod(modid = "Eine Mod-ID", name = "Der Name der Mod", version = "Die Version der Mod")
@NetworkMod(clientSideRequired = true, serverSideRequired = false)
public class Klasse_XY
{
}
Das "clientSideRequired" bedeutet, dass wenn man auf einen Server geht, ob der Benutzer diese Mod dann auch benötigt. Das "serverSideRequired" bedeutet, dass wenn der Benutzer mit der Mod auf den Server geht, ob der Server die Mod dann auch benötigt.
1.2 - PreInit, Init, PostInit
Bei ModLoader(MP) gab es ja immer die "load"-Methode (und die unwichtige "getVersion"-Methode). In Forge heißt dies nun:
Code:
@Mod(modid = "Eine Mod-ID", name = "Der Name der Mod", version = "Die Version der Mod")
@NetworkMod(clientSideRequired = true, serverSideRequired = false)
public class Klasse_XY
{
@PreInit
public void preInit(FMLPreInitializationEvent event) {
}
@Init
public void init(FMLInitializationEvent event) {
}
@PostInit
public void postInit(FMLPostInitializationEvent event) {
}
}
Natürlich könnte man die Methoden auch anderst nennen, sie muss nur immer den Return-Typ "void" haben und den entsprechenden Parameter, ansonsten wird die Methode nicht geladen.
1.3 - Instance
Da die Mods von Forge geladen werden, kann man sich die Instanze von der Mod übergeben lassen. Dies geschieht wie folgt:
Code:
@Mod(modid = "Eine Mod-ID", name = "Der Name der Mod", version = "Die Version der Mod")
@NetworkMod(clientSideRequired = true, serverSideRequired = false)
public class Klasse_XY
{
@Instance("Eine Mod-ID")
public static Klasse_XY instance;
@PreInit
public void preInit(FMLPreInitializationEvent event) {
}
@Init
public void init(FMLInitializationEvent event) {
}
@PostInit
public void postInit(FMLPostInitializationEvent event) {
}
}
1.4 - Proxy-System
Da bei Forge ab 4.x die Mods für Server & Client die gleichen sind, gibt es ein sog. Proxy-System. Dieses kann man benutzen um etwa nur Texturen im Client zu laden.
Dabei nutzt man die von Forge gegebene Proxy-Annotation:
Code:
@Mod(modid = "Eine Mod-ID", name = "Der Name der Mod", version = "Die Version der Mod")
@NetworkMod(clientSideRequired = true, serverSideRequired = false)
public class Klasse_XY
{
@Instance("Eine Mod-ID")
public static Klasse_XY instance;
@Side dProxy(clientSide = "mod.ClientProxy", serverSide = "mod.CommonProxy")
public static CommonProxy proxy;
@PreInit
public void preInit(FMLPreInitializationEvent event) {
}
@Init
public void init(FMLInitializationEvent event) {
}
@PostInit
public void postInit(FMLPostInitializationEvent event) {
}
}
Wie man hier sieht braucht man nun noch zwei Klassen, wie der Eine oder Andere schon vermutet, nutzt man hier jetzt die Klassen-Vererbung.
Die Klasse CommonProxy (in diesem Fall im Packet "mod.CommonProxy" im Ordner "common") enthält folgende standart Methode:
Code:
public class CommonProxy {
public static String ITEMS_PNG = "/Pfad/Zu/Deiner/Texture/Datei.png";
public static String BLOCK_PNG = "/Pfad/Zu/Deiner/Texture/Datei.png";
public void registerRenderInformation() {}
}
Die Klasse ClientProxy (in diesem Fall im Packet "mod.ClientProxy" im Ordner "client") enthält folgende standart Methode:
Code:
public class ClientProxy extends CommonProxy {
public void registerRenderInformation() {
MinecraftForge.preloadTexture(ITEMS_PNG);
MinecraftForge.preloadTexture(BLOCK_PNG);
}
}
Um die "registerRenderInformation"-Methode aufzurufen genügt folgender Code:
Code:
@Mod(modid = "Eine Mod-ID", name = "Der Name der Mod", version = "Die Version der Mod")
@NetworkMod(clientSideRequired = true, serverSideRequired = false)
public class Klasse_XY
{
@Instance("Eine Mod-ID")
public static Klasse_XY instance;
@Side dProxy(clientSide = "mod.ClientProxy", serverSide = "mod.CommonProxy")
public static CommonProxy proxy;
@PreInit
public void preInit(FMLPreInitializationEvent event) {
}
@Init
public void init(FMLInitializationEvent event) {
proxy.registerRenderInformation();
}
@PostInit
public void postInit(FMLPostInitializationEvent event) {
}
}
1.5 - Blöcke und Items
In der Mod-Datei folgendes:
Code:
@Mod(modid = "Eine Mod-ID", name = "Der Name der Mod", version = "Die Version der Mod")
@NetworkMod(clientSideRequired = true, serverSideRequired = false)
public class Klasse_XY
{
@Instance("Eine Mod-ID")
public static Klasse_XY instance;
@Side dProxy(clientSide = "mod.ClientProxy", serverSide = "mod.CommonProxy")
public static CommonProxy proxy;
public static Block copperOre;
public static Item copperIngot;
@PreInit
public void preInit(FMLPreInitializationEvent event) {
}
@Init
public void init(FMLInitializationEvent event) {
proxy.registerRenderInformation();
copperOre = new BlockCopperOre(200, 0, Material.rock).setResistance(1.0F).setHardness(1.5F).setBlockName("copperOre");
copperIngot = new ItemCopperIngot(5000).setItemName("copperIngot");
GameRegistry.registerBlock(copperOre);
LanguageRegistry.addName(copperOre, "Copper Ore");
LanguageRegistry.addName(copperIngot, "Copper Ingot");
LanguageRegistry.instance().addNameForObject(copperOre, "de_DE", "Kupfer Erz");
LanguageRegistry.instance().addNameForObject(copperIngot, "de_DE", "Kupfer Barren");
GameRegistry.addRecipe(new ItemStack(copperOre, 1), new Object[] {
"#X#", "#X#", "###", Character.valueOf('#'), Item.stick, Character.valueOf('X'), Block.goldBlock
});
GameRegistry.addSmelting(copperOre.blockID, new ItemStack(copperIngot, 8), 1.0F);
}
@PostInit
public void postInit(FMLPostInitializationEvent event) {
}
}
Die Methoden sollten eigentlich bekannt sein. Nun müssen wir noch das Item und den Block erstellen:
Code:
public class BlockCopperOre extends Block {
public BlockCopperOre(int par1, int par2, Material par3Material) {
super(par1, par2, par3Material);
}
public String getTextureFile() {
return CommonProxy.BLOCK_PNG;
}
}
Code:
public class ItemCopperIngot extends Item {
public ItemCopperIngot(int par1) {
super(par1);
}
public String getTextureFile() {
return CommonProxy.ITEMS_PNG;
}
}
1.6 - Konfigurationsdatei
Eine Konfiguration erstellt man folgendermaßen:
Code:
@Mod(modid = "Eine Mod-ID", name = "Der Name der Mod", version = "Die Version der Mod")
@NetworkMod(clientSideRequired = true, serverSideRequired = false)
public class Klasse_XY
{
@Instance("Eine Mod-ID")
public static Klasse_XY instance;
@Side dProxy(clientSide = "mod.ClientProxy", serverSide = "mod.CommonProxy")
public static CommonProxy proxy;
public static Block copperOre;
public static Item copperIngot;
private static int copperOreID;
private static int copperIngotID;
@PreInit
public void preInit(FMLPreInitializationEvent event) {
Configuration config = new Configuration(event.getSuggestedConfigurationFile());
config.load();
copperOreID = config.getBlock("CopperOre", 200).getInt();
copperIngotID = config.getItem("CopperIngot", 5000).getInt();
config.save();
}
@Init
public void init(FMLInitializationEvent event) {
proxy.registerRenderInformation();
copperOre = new BlockCopperOre(copperOreID, 0, Material.rock).setResistance(1.0F).setHardness(1.5F).setBlockName("copperOre");
copperIngot = new ItemCopperIngot(copperIngotID).setItemName("copperIngot");
GameRegistry.registerBlock(copperOre);
LanguageRegistry.addName(copperOre, "Copper Ore");
LanguageRegistry.addName(copperIngot, "Copper Ingot");
LanguageRegistry.instance().addNameForObject(copperOre, "de_DE", "Kupfer Erz");
LanguageRegistry.instance().addNameForObject(copperIngot, "de_DE", "Kupfer Barren");
GameRegistry.addRecipe(new ItemStack(copperOre, 1), new Object[] {
"#X#", "#X#", "###", Character.valueOf('#'), Item.stick, Character.valueOf('X'), Block.goldBlock
});
GameRegistry.addSmelting(copperOre.blockID, new ItemStack(copperIngot, 8), 1.0F);
}
@PostInit
public void postInit(FMLPostInitializationEvent event) {
}
}
1.7 - Entitys
Entitys muss man jetzt registrieren, da sonst die Entitys nicht auf beiden Seiten existieren. Bei nicht LivingEntiys läuft das so:
Code:
@Mod(modid = "Eine Mod-ID", name = "Der Name der Mod", version = "Die Version der Mod")
@NetworkMod(clientSideRequired = true, serverSideRequired = false)
public class Klasse_XY
{
@Instance("Eine Mod-ID")
public static Klasse_XY instance;
@Side dProxy(clientSide = "mod.ClientProxy", serverSide = "mod.CommonProxy")
public static CommonProxy proxy;
public static Block copperOre;
public static Item copperIngot;
private static int copperOreID;
private static int copperIngotID;
@PreInit
public void preInit(FMLPreInitializationEvent event) {
Configuration config = new Configuration(event.getSuggestedConfigurationFile());
config.load();
copperOreID = config.getBlock("CopperOre", 200).getInt();
copperIngotID = config.getItem("CopperIngot", 5000).getInt();
config.save();
}
@Init
public void init(FMLInitializationEvent event) {
proxy.registerRenderInformation();
EntityRegistry.registerGlobalEntityID(EntityTest.class, "EntityName", ModLoader.getUniqueEntityId());
copperOre = new BlockCopperOre(copperOreID, 0, Material.rock).setResistance(1.0F).setHardness(1.5F).setBlockName("copperOre");
copperIngot = new ItemCopperIngot(copperIngotID).setItemName("copperIngot");
GameRegistry.registerBlock(copperOre);
LanguageRegistry.addName(copperOre, "Copper Ore");
LanguageRegistry.addName(copperIngot, "Copper Ingot");
LanguageRegistry.instance().addNameForObject(copperOre, "de_DE", "Kupfer Erz");
LanguageRegistry.instance().addNameForObject(copperIngot, "de_DE", "Kupfer Barren");
GameRegistry.addRecipe(new ItemStack(copperOre, 1), new Object[] {
"#X#", "#X#", "###", Character.valueOf('#'), Item.stick, Character.valueOf('X'), Block.goldBlock
});
GameRegistry.addSmelting(copperOre.blockID, new ItemStack(copperIngot, 8), 1.0F);
}
@PostInit
public void postInit(FMLPostInitializationEvent event) {
}
}
Das läuft ähnlich wie beim ModLoader ab.
Für LivingEntitys funktioniert das ganze so:
Code:
@Mod(modid = "Eine Mod-ID", name = "Der Name der Mod", version = "Die Version der Mod")
@NetworkMod(clientSideRequired = true, serverSideRequired = false)
public class Klasse_XY
{
@Instance("Eine Mod-ID")
public static Klasse_XY instance;
@Side dProxy(clientSide = "mod.ClientProxy", serverSide = "mod.CommonProxy")
public static CommonProxy proxy;
public static Block copperOre;
public static Item copperIngot;
private static int copperOreID;
private static int copperIngotID;
@PreInit
public void preInit(FMLPreInitializationEvent event) {
Configuration config = new Configuration(event.getSuggestedConfigurationFile());
config.load();
copperOreID = config.getBlock("CopperOre", 200).getInt();
copperIngotID = config.getItem("CopperIngot", 5000).getInt();
config.save();
}
@Init
public void init(FMLInitializationEvent event) {
proxy.registerRenderInformation();
EntityRegistry.registerGlobalEntityID(EntityTest.class, "EntityName", ModLoader.getUniqueEntityId());
copperOre = new BlockCopperOre(copperOreID, 0, Material.rock).setResistance(1.0F).setHardness(1.5F).setBlockName("copperOre");
copperIngot = new ItemCopperIngot(copperIngotID).setItemName("copperIngot");
GameRegistry.registerBlock(copperOre);
LanguageRegistry.addName(copperOre, "Copper Ore");
LanguageRegistry.addName(copperIngot, "Copper Ingot");
LanguageRegistry.instance().addNameForObject(copperOre, "de_DE", "Kupfer Erz");
LanguageRegistry.instance().addNameForObject(copperIngot, "de_DE", "Kupfer Barren");
GameRegistry.addRecipe(new ItemStack(copperOre, 1), new Object[] {
"#X#", "#X#", "###", Character.valueOf('#'), Item.stick, Character.valueOf('X'), Block.goldBlock
});
GameRegistry.addSmelting(copperOre.blockID, new ItemStack(copperIngot, 8), 1.0F);
}
@PostInit
public void postInit(FMLPostInitializationEvent event) {
}
}
Nun muss im ClientProxy noch der Renderer registriert werden:
Code:
public class ClientProxy extends CommonProxy {
public void registerRenderInformation() {
MinecraftForge.preloadTexture(ITEMS_PNG);
MinecraftForge.preloadTexture(BLOCK_PNG);
RenderingRegistry.instance().registerEntityRenderingHandler(EntityTest.class, new RenderText(new ModelTest(), 0.5F));
}
}
Achtung
Auch TileEntity müssen wie beim ModLoaderMp registriert werden!
1.8 - Guis
Für Guis benötigen wir eine Art sonder Proxy, dazu registrieren wir erste einmal eine beliebige leere Klasse in unserer Hauptmod-Klasse:
Code:
@Mod(modid = "Eine Mod-ID", name = "Der Name der Mod", version = "Die Version der Mod")
@NetworkMod(clientSideRequired = true, serverSideRequired = false)
public class Klasse_XY
{
@Instance("Eine Mod-ID")
public static Klasse_XY instance;
@Side dProxy(clientSide = "mod.ClientProxy", serverSide = "mod.CommonProxy")
public static CommonProxy proxy;
public static Block copperOre;
public static Item copperIngot;
private static int copperOreID;
private static int copperIngotID;
@PreInit
public void preInit(FMLPreInitializationEvent event) {
Configuration config = new Configuration(event.getSuggestedConfigurationFile());
config.load();
copperOreID = config.getBlock("CopperOre", 200).getInt();
copperIngotID = config.getItem("CopperIngot", 5000).getInt();
config.save();
}
@Init
public void init(FMLInitializationEvent event) {
proxy.registerRenderInformation();
EntityRegistry.registerGlobalEntityID(EntityTest.class, "EntityName", ModLoader.getUniqueEntityId());
NetworkRegistry.instance().registerGuiHandler(this, new GuiHandlerMod());
copperOre = new BlockCopperOre(copperOreID, 0, Material.rock).setResistance(1.0F).setHardness(1.5F).setBlockName("copperOre");
copperIngot = new ItemCopperIngot(copperIngotID).setItemName("copperIngot");
GameRegistry.registerBlock(copperOre);
LanguageRegistry.addName(copperOre, "Copper Ore");
LanguageRegistry.addName(copperIngot, "Copper Ingot");
LanguageRegistry.instance().addNameForObject(copperOre, "de_DE", "Kupfer Erz");
LanguageRegistry.instance().addNameForObject(copperIngot, "de_DE", "Kupfer Barren");
GameRegistry.addRecipe(new ItemStack(copperOre, 1), new Object[] {
"#X#", "#X#", "###", Character.valueOf('#'), Item.stick, Character.valueOf('X'), Block.goldBlock
});
GameRegistry.addSmelting(copperOre.blockID, new ItemStack(copperIngot, 8), 1.0F);
}
@PostInit
public void postInit(FMLPostInitializationEvent event) {
}
}
In die leere Klasse (im Beispiel "GuiHandlerMod") kommt nun folgender Inhalt:
Code:
public class GuiHandlerMod implements IGuiHandler {
@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;
}
}
Wenn man nun einen Block erstellt, der eine Gui öffnen soll, so braucht man einen Block-Container (wird hier nicht gezeigt), eine Block-Gui (wird hier nicht gezeigt), eine Entity (wird hier nicht gezeigt) und einen Block (wurde hier gezeigt), der BlockContainer geerbt hat.
Im Block sollte dann das Rechtsklick-Event folgenden Code aufrufen:
Code:
player.openGui(Klasse_XY.instance, 0, world, x, y, z);
In der GuiHandleMod-Klasse steht nun folgendes:
Code:
public class GuiHandlerMod implements IGuiHandler {
@Override
public Object getServerGuiElement(int id, EntityPlayer player, World world, int x, int y, int z) {
if(id == 0) {
return new BlockContainerXY(player.inventory, (TileEntityXY)world.getBlockTileEntity(x, y, z));
}
return null;
}
@Override
public Object getClientGuiElement(int id, EntityPlayer player, World world, int x, int y, int z) {
if(id == 0) {
return new BlockGuiXY(player.inventory, (TileEntityXY)world.getBlockTileEntity(x, y, z));
}
return null;
}
}
1.9 - Erzgenerierung
In Forge gibts nun keine Vererbung mehr und auch keine Annotation mit der man Erze generieren lassen kann. Allerdings kann man einen neuen Weltgenerator hinzufügen und damit die Erze generieren lassen.
Erst mal das registrieren einer neuen Klasse:
Code:
@Mod(modid = "Eine Mod-ID", name = "Der Name der Mod", version = "Die Version der Mod")
@NetworkMod(clientSideRequired = true, serverSideRequired = false)
public class Klasse_XY
{
@Instance("Eine Mod-ID")
public static Klasse_XY instance;
@Side dProxy(clientSide = "mod.ClientProxy", serverSide = "mod.CommonProxy")
public static CommonProxy proxy;
public static Block copperOre;
public static Item copperIngot;
private static int copperOreID;
private static int copperIngotID;
@PreInit
public void preInit(FMLPreInitializationEvent event) {
Configuration config = new Configuration(event.getSuggestedConfigurationFile());
config.load();
copperOreID = config.getBlock("CopperOre", 200).getInt();
copperIngotID = config.getItem("CopperIngot", 5000).getInt();
config.save();
}
@Init
public void init(FMLInitializationEvent event) {
proxy.registerRenderInformation();
EntityRegistry.registerGlobalEntityID(EntityTest.class, "EntityName", ModLoader.getUniqueEntityId());
NetworkRegistry.instance().registerGuiHandler(this, new GuiHandlerMod());
GameRegistry.registerWorldGenerator(new ModWorldGenerator());
copperOre = new BlockCopperOre(copperOreID, 0, Material.rock).setResistance(1.0F).setHardness(1.5F).setBlockName("copperOre");
copperIngot = new ItemCopperIngot(copperIngotID).setItemName("copperIngot");
GameRegistry.registerBlock(copperOre);
LanguageRegistry.addName(copperOre, "Copper Ore");
LanguageRegistry.addName(copperIngot, "Copper Ingot");
LanguageRegistry.instance().addNameForObject(copperOre, "de_DE", "Kupfer Erz");
LanguageRegistry.instance().addNameForObject(copperIngot, "de_DE", "Kupfer Barren");
GameRegistry.addRecipe(new ItemStack(copperOre, 1), new Object[] {
"#X#", "#X#", "###", Character.valueOf('#'), Item.stick, Character.valueOf('X'), Block.goldBlock
});
GameRegistry.addSmelting(copperOre.blockID, new ItemStack(copperIngot, 8), 1.0F);
}
@PostInit
public void postInit(FMLPostInitializationEvent event) {
}
}
In die "ModWorldGenerator"-Klasse kommt nun folgender Source:
Code:
public class ModWorldGenerator implements IWorldGenerator {
public void generate(Random rand, int chunkX, int chunkZ, World world, IChunkProvider chunkGenerator, IChunkProvider chunkProvider) {
if(world.provider.dimensionId == 0) {
for(int i = 0; i < GenerationenProChunk; i++) {
int randPosX = random.nextInt(16) + chunkX;
int randPosY = random.nextInt(maximaleVorkommensHöhe);
int randPosZ = random.nextInt(16) + chunkZ;
new WorldGenMinable(Klasse_XY.copperOre.blockID, Adergröße)).generate(world, rand,
randPosX, randPosY, randPosZ);
}
}
}
}
Das Code-Stück "if(world.provider.dimensionId == 0)" heißt, wenn auf der normalen Welt (Surface) generiert wird, soll dieses Erz auch genereriert werden.
Es gibt folgende Dimensionen:- Nether (-1)
- Oberwelt (0)
- End (1)
2.0 - Events
Seit Forge 4.x kann man nun Events verwenden und dadurch APIs vermeiden. Dazu erstellt man eine universälle Event-Klasse:
Code:
public class ModEvents {
}
Nun kann man mit der Annotation "@ForgeSubscribe" Events mit dem Listener verknüpfen. Alle Events kann man auf der Seite von MinecraftForge finden: http://www.minecraftforge.net/wiki/Forge_Events
Als letztes muss man nun noch die Event-Klasse registrieren, dies funktioniert folgendermaßen:
Code:
@Mod(modid = "Eine Mod-ID", name = "Der Name der Mod", version = "Die Version der Mod")
@NetworkMod(clientSideRequired = true, serverSideRequired = false)
public class Klasse_XY
{
@Instance("Eine Mod-ID")
public static Klasse_XY instance;
@Side dProxy(clientSide = "mod.ClientProxy", serverSide = "mod.CommonProxy")
public static CommonProxy proxy;
public static Block copperOre;
public static Item copperIngot;
private static int copperOreID;
private static int copperIngotID;
@PreInit
public void preInit(FMLPreInitializationEvent event) {
Configuration config = new Configuration(event.getSuggestedConfigurationFile());
config.load();
copperOreID = config.getBlock("CopperOre", 200).getInt();
copperIngotID = config.getItem("CopperIngot", 5000).getInt();
config.save();
}
@Init
public void init(FMLInitializationEvent event) {
proxy.registerRenderInformation();
EntityRegistry.registerGlobalEntityID(EntityTest.class, "EntityName", ModLoader.getUniqueEntityId());
NetworkRegistry.instance().registerGuiHandler(this, new GuiHandlerMod());
GameRegistry.registerWorldGenerator(new ModWorldGenerator());
MinecraftForge.EVENT_BUS.register(new ModEvents());
copperOre = new BlockCopperOre(copperOreID, 0, Material.rock).setResistance(1.0F).setHardness(1.5F).setBlockName("copperOre");
copperIngot = new ItemCopperIngot(copperIngotID).setItemName("copperIngot");
GameRegistry.registerBlock(copperOre);
LanguageRegistry.addName(copperOre, "Copper Ore");
LanguageRegistry.addName(copperIngot, "Copper Ingot");
LanguageRegistry.instance().addNameForObject(copperOre, "de_DE", "Kupfer Erz");
LanguageRegistry.instance().addNameForObject(copperIngot, "de_DE", "Kupfer Barren");
GameRegistry.addRecipe(new ItemStack(copperOre, 1), new Object[] {
"#X#", "#X#", "###", Character.valueOf('#'), Item.stick, Character.valueOf('X'), Block.goldBlock
});
GameRegistry.addSmelting(copperOre.blockID, new ItemStack(copperIngot, 8), 1.0F);
}
@PostInit
public void postInit(FMLPostInitializationEvent event) {
}
}
2.1 - Sound-Events
Sound-Events sind etwas besonderes, denn wenn z.B. ein Mob ein geräusch macht, wird z.B. folgende Funktion von einem Mob aufgerufen:
Code:
public String getLivingSound()
{
return "main.sound";
}
Da dieser Sound nicht in Minecraft registriert ist, wird Forge aufgerufen, der alle registrierten Events dannach absucht. Nun kann man in unseren Event-Listener folgendes hinzu fügen:
Code:
public class ModEvents {
@ForgeSubscribe
public void onSound(SoundLoadEvent event) {
try {
event.manager.soundPoolSounds.addSound("mein/sound.wav", Klasse_XY.class.getResource("/Pfad/zum/sound.wav"));
}
catch(Exception e) { }
}
}
Der erste Parameter in der "addSound"-Methode ist immer der Name mit der man den Sound aufruft. So wird aus "mein/sound.wav" -> "mein.sound". Man kann die Sounds auch anderweitig starten:
Code:
world.playSoundEffect(x, y, z, "mein.sound", lautstärkeInFloat, tonhöheInFloat);
2.2 - Packet Handling
Wer mit ModLoaderMp schon gearbeitet hat, weiß, dass man mit sog. Packeten Daten versenden kann. Seit Minecraft 1.3.2 gibt es dazu das "Payload"-Packet.
Dies kann man wie folgt benutzen:
Code:
Packet250CustomPayload packet = new Packet250CustomPayload();
packet.channel = "MeineMod";
ByteArrayOutputStream bos = new ByteArrayOutputStream(8);
DataOutputStream outputStream = new DataOutputStream(bos);
try {
outputStream.writeInt(300);
outputStream.writeInt(9);
} catch (Exception ex) {
ex.printStackTrace();
}
packet.data = bos.toByteArray();
packet.length = bos.size();
EntityClientPlayerMP player = (EntityClientPlayerMP) playerEntity;
player.sendQueue.addToSendQueue(packet);
Somit kann man dann zum Server (hier ist es von der Client-Seite aus) Daten versenden. Dabei sollte man wissen, dass man die Instanzen "Bos" und "outputStream" wieder beenden sollte:
Code:
try {
bos.close();
outputStream.close();
} catch(Exception e) {}
Dies hat zur Folge, dass die Instanzen sauber beendet werden und keine Überreste der Instanzen, Methoden, usw. der Klassen noch im RAM liegen. Dies kann unter Umständen eine "StackOverflowException" beheben/verhindern.
In der Mod-Klasse muss man nun noch einen Handler implementieren, der die Pakete entgegen nimmt und die entsprechenden Funktionen ausführt:
Code:
@Mod(modid = "Eine Mod-ID", name = "Der Name der Mod", version = "Die Version der Mod")
@NetworkMod(clientSideRequired = true, serverSideRequired = false, channels={"MeineMod"}, packetHandler = PacketHandler.class)
public class Klasse_XY
{
@Instance("Eine Mod-ID")
public static Klasse_XY instance;
@Side dProxy(clientSide = "mod.ClientProxy", serverSide = "mod.CommonProxy")
public static CommonProxy proxy;
public static Block copperOre;
public static Item copperIngot;
private static int copperOreID;
private static int copperIngotID;
@PreInit
public void preInit(FMLPreInitializationEvent event) {
Configuration config = new Configuration(event.getSuggestedConfigurationFile());
config.load();
copperOreID = config.getBlock("CopperOre", 200).getInt();
copperIngotID = config.getItem("CopperIngot", 5000).getInt();
config.save();
}
@Init
public void init(FMLInitializationEvent event) {
proxy.registerRenderInformation();
EntityRegistry.registerGlobalEntityID(EntityTest.class, "EntityName", ModLoader.getUniqueEntityId());
NetworkRegistry.instance().registerGuiHandler(this, new GuiHandlerMod());
GameRegistry.registerWorldGenerator(new ModWorldGenerator());
MinecraftForge.EVENT_BUS.register(new ModEvents());
copperOre = new BlockCopperOre(copperOreID, 0, Material.rock).setResistance(1.0F).setHardness(1.5F).setBlockName("copperOre");
copperIngot = new ItemCopperIngot(copperIngotID).setItemName("copperIngot");
GameRegistry.registerBlock(copperOre);
LanguageRegistry.addName(copperOre, "Copper Ore");
LanguageRegistry.addName(copperIngot, "Copper Ingot");
LanguageRegistry.instance().addNameForObject(copperOre, "de_DE", "Kupfer Erz");
LanguageRegistry.instance().addNameForObject(copperIngot, "de_DE", "Kupfer Barren");
GameRegistry.addRecipe(new ItemStack(copperOre, 1), new Object[] {
"#X#", "#X#", "###", Character.valueOf('#'), Item.stick, Character.valueOf('X'), Block.goldBlock
});
GameRegistry.addSmelting(copperOre.blockID, new ItemStack(copperIngot, 8), 1.0F);
}
@PostInit
public void postInit(FMLPostInitializationEvent event) {
}
}
Jetzt muss in die PacketHandler Klasse folgender Code:
Code:
public class PacketHandler implements IPacketHandler {
@Override
public void onPacketData(NetworkManager manager,
Packet250CustomPayload packet, Player player) {
if (packet.channel.equals("MeineMod")) {
sayInteger(packet);
}
}
private void sayInteger(Packet250CustomPayload packet) {
DataInputStream inputStream = new DataInputStream(new ByteArrayInputStream(packet.data));
int int1;
int int2;
try {
int1 = inputStream.readInt();
int2 = inputStream.readInt();
} catch (IOException e) {
e.printStackTrace();
return;
}
System.out.println("Int: " + int1 + " Int2: " + int2);
}
}
Wenn nun das Packet beim Server ankommt, wird in der Konsole der Wert ausgegeben. Natürlich gibt es auch noch andere Verarbeitungsmöglichkeiten für die ankommenden Daten. 
2.3 - Metawerte (Items)
Es gibt ja nur eine begrentzte Anzahl an Items und Blöcken, die maximal registriert werden dürfen. Deshalb verwenden viele Modder die so genannten Metawerte. Mit diesen lassen sich Blöcke und Items noch einmal "zerspalten". Beim Item geht dies über den Damagewert, der eigentlich dafür geschaffen wurde den Schaden von Schwerter und Werkzeugen zu bekommen.
Item-Klasse:
Code:
public class ItemCopperIngot extends Item {
public ItemCopperIngot(int par1) {
super(par1);
}
public String getTextureFile() {
return CommonProxy.ITEMS_PNG;
}
public int getIconFromDamage(int par1)
{
return this.iconIndex + par1;
}
public void getSubItems(int par1, CreativeTabs par2CreativeTabs, List par3List)
{
for (int var4 = 0; var4 < 7; ++var4)
{
par3List.add(new ItemStack(par1, 1, var4));
}
}
public String getItemNameIS(ItemStack par1ItemStack)
{
if(par1ItemStack.getItemDamage() == 0) {
return "stonedust";
}
if(par1ItemStack.getItemDamage() == 1) {
return "coaldust";
}
if(par1ItemStack.getItemDamage() == 2) {
return "irondust";
}
if(par1ItemStack.getItemDamage() == 3) {
return "golddust";
}
if(par1ItemStack.getItemDamage() == 4) {
return "diamonddust";
}
if(par1ItemStack.getItemDamage() == 5) {
return "emeralddust";
}
if(par1ItemStack.getItemDamage() == 6) {
return "lapisdust";
}
return null;
}
}
Nun zu der Bedeutung:
Code:
public int getIconFromDamage(int par1)
{
return this.iconIndex + par1;
}
Dies sagt Minecraft, dass es unterschiedliche Texturen für das Item laden soll. par1 ist dabei die Variable in der der Metawert steht. Bei meinem Code würde das bedeuten, dass die Texturen der Items mit dem gleichen shiftedIndex und unterschiedlichen Metawerten im Sprite neben einander liegen. Dies kann man natürlich auch mit if-Blöcken lösen.
Code:
public void getSubItems(int par1, CreativeTabs par2CreativeTabs, List par3List)
{
for (int var4 = 0; var4 < 7; ++var4)
{
par3List.add(new ItemStack(par1, 1, var4));
}
}
Dies implementiert die Meta-Items im CreativeTab. Ansonsten würde nur das Item mit dem Metawert 0 angezeigt werden.
Achtung: Die 7 muss durch die Anzahl der Metawerte hinzugefügt werden. Wenn also das Item z.B. 14 Metawerte hätte müsste die 7 durch 14 ersetzt werden.
Code:
public String getItemNameIS(ItemStack par1ItemStack)
{
if(par1ItemStack.getItemDamage() == 0) {
return "stonedust";
}
if(par1ItemStack.getItemDamage() == 1) {
return "coaldust";
}
if(par1ItemStack.getItemDamage() == 2) {
return "irondust";
}
if(par1ItemStack.getItemDamage() == 3) {
return "golddust";
}
if(par1ItemStack.getItemDamage() == 4) {
return "diamonddust";
}
if(par1ItemStack.getItemDamage() == 5) {
return "emeralddust";
}
if(par1ItemStack.getItemDamage() == 6) {
return "lapisdust";
}
return null;
}
Dies tut verschiedene Namen für das Itemregistrieren. Diese funktion ist equivalent mit der Funktion "setItemName(String)".
Dies braucht man um unterschiedliche Namen für die Metawerte fest zu legen.
Die Namen erstellt man so:
Code:
LanguageRegistry.instance().addStringLocalization("stonedust.name", "Stonedust");
LanguageRegistry.instance().addStringLocalization("coaldust.name", "Coaldust");
LanguageRegistry.instance().addStringLocalization("irondust.name", "Irondust");
LanguageRegistry.instance().addStringLocalization("diamonddust.name", "Diamonddust");
LanguageRegistry.instance().addStringLocalization("emeralddust.name", "Emeralddust");
LanguageRegistry.instance().addStringLocalization("lapisdust.name", "Lapisdust");
LanguageRegistry.instance().addStringLocalization("golddust.name", "Golddust");
(Dies muss in den Init-Teil der Mod!)
Bei mehreren Sprachen kann man auch noch folgendes aufrufen:
Code:
LanguageRegistry.instance().addStringLocalization("stonedust.name", "de_DE", "Steinstaub");
LanguageRegistry.instance().addStringLocalization("coaldust.name", "de_DE", "Kohlestaub");
LanguageRegistry.instance().addStringLocalization("irondust.name", "de_DE", "Eisenstaub");
LanguageRegistry.instance().addStringLocalization("diamonddust.name", "de_DE", "Diamantstaub");
LanguageRegistry.instance().addStringLocalization("emeralddust.name", "de_DE", "Smaragdstaub");
LanguageRegistry.instance().addStringLocalization("lapisdust.name", "de_DE", "Lapislazulistaub");
LanguageRegistry.instance().addStringLocalization("golddust.name", "de_DE", "Goldstaub");
"de_DE" ist der dazu gehörige Kürzel der Sprache.
Weitere Kürzel findet man hier:
Samelthread (1. Spoiler)
FAQ - Bei Fragen und Problemen
Falls ihr Fragen oder Probleme habt, die häufiger auftretten, könnten sie hier gelöst sein:
1. Wenn ich Minecraft mit meiner Mod starte, werden alle meine Blöcke grau angezeigt.
Du hast offensichtlich entweder deine Texture nicht mit in die Mod gepackt oder die hast die Texturen nicht "pregeloaded". "preloaden" heißt, dass du deine Texture schon mal in den RAM lädst. Die Lösung findest du oben unter 1.4. Bitte beachte allerdings die "BLOCK_PNG" und "ITEMS_PNG" mit den Pfaden zu den Texturen relativ deiner Mod-Datei zu initialisieren.
2. Wenn ich meine Mod in Minecraft starte, kommt ein Crash-Screen das mein Forge zu alt/neu ist.
Das liegt daran, dass wenn du deine Mod erstellst, dass du dann auch am besten die gleiche Forge-Version benutzt wie zum Modden.
3. Kann ich ModLoader-Mods unter Forge erstellen?
Jein, eigentlich kann man, aber man sollte nicht. MinecraftForge wurde eigentlich nicht dazu entwickelt die alte Mod-Architektur zu laden. (Ist wie bei den Prozessoren...) Allerdings ist es dazu in der Lage. Es gibt allerdings auch ein paar Mods (z.B. xRay), die sich mit dem neuen Forge nicht verstehen, da z.B. xRay Mod-Dateien von Forge überschreibt. Es ist empfohlen und wahrscheinlich auch die "Zukunft", dass man die neue Funktion zum Erstellen nutzt, da diese meiner Meinung nach einfacher ist.
4. Wie installiere ich Forge richtig?
Dies ist auch eine häufige Frage. Wie auch schon beim ModLoader genügt es einfach den gesammten Inhalt des Zip-Packets in die "minecraft.jar" unter %appdata% (bei Windows) oder .minecraft (bei Linux) zu kopieren. Dabei empfehlen sich Programme wie WinRar oder 7zip. Es werden zur Installation kein ModLoader, ModLoaderMp oder andere Mods benötigt.
5. Wie verwende ich Texturen richtig?
Ein wichtiges Merkmal von MinecraftForge ist, dass es im gegensatz zum ModLoader keine 16x16 Pixel große Texturen sondern ganze Bilder voller Block-Texturen nimmt, diese sind 256x256 Pixel groß (sprich, man kann 256 Texturen drauf ziehen). Nun nimmt man seine ModLoader-Texturen und zieht jeder Texture einzeln in die Texturen-Datei, dabei sollte man beachten, dass jede Texture einen Platz von 16x16 Pixeln hat, den keine anderen Block-Texturen überlappen dürfen, da ansonsten die Texturen im Spiel später dann nicht richtig aussehen.
6. Ich bekomme die Fehlermeldung "StackOverflowException", was soll ich tun?
Die StackOverflowException hat die Bedeutung, dass Minecraft zu wenig RAM für die Arbeiten hat, dass kann mit Forge ab 4.x schon mal vorkommen, da Minecraft von Natur aus Speicher fressend entwickelt wurde und weil MinecraftForge auch ziemlich groß ist. Es gibt hierbei eigentlich nur zwei Lösungsarten, die eine ist wohl für die Anfänger mit viel eingebautem RAM am geeignetsten:- Man downloaded sich von minecraft.net die minecraft.jar (Achtung: nicht .exe) und führt sie mit folgenden Kommandos in der cmd (unter Windows) oder in der Shell (unter Linux) aus: java -Xmx*M -Xms**M -cp minecraft.jar net.minecraft.LauncherFrame
- Wenn man gut Java programmieren kann, sollte man wissen wie man ressourcensparend programmiert, dieses Wissen sollte man dann anwenden.

* Bei dem Parameter -Xmx*M kann * variieren, dabei sollte man einen Wert zwischen 1024 und dem max. eingebauten RAM - 1 GB einfügen, da Windows/Linux/Macintosh mind. 1 GB für die GUI (und andere im Hintergrundlaufenden Dienste) benötigt.
** Bei dem Parameter -Xms**M kann man ** variieren, dabei sollte man einen Wert zwischen 512 und dem max. eingebaute RAM - 1GB einfügen.
Bei weiteren Fragen kann mir jeder eine PN schicken. Einzige Kriterien dafür sind: die Deutschesprache, eine relativ gute Rechtschreibung, kein/-e Werbung/Spam.