├── .gitattributes ├── .gitignore ├── AddingCustomVillagerTrades.java ├── ArmorTutorial ├── README.md └── src │ └── main │ ├── java │ └── armortutorial │ │ ├── ArmorTutorial.java │ │ ├── ClientProxy.java │ │ ├── CommonProxy.java │ │ ├── client │ │ └── model │ │ │ └── ModelSkirt.java │ │ └── item │ │ └── ItemCustomArmor.java │ └── resources │ ├── assets │ └── armortutorial │ │ ├── lang │ │ └── en_US.lang │ │ └── textures │ │ ├── armor │ │ ├── armor_bandit_layer_1.png │ │ ├── armor_bandit_layer_2.png │ │ └── armor_sample_layer_2.png │ │ └── items │ │ ├── armor_bandit_boots.png │ │ ├── armor_bandit_chest.png │ │ ├── armor_bandit_helm.png │ │ ├── armor_bandit_legs.png │ │ └── armor_sample_legs.png │ └── mcmod.info ├── CraftingWithPotionsTutorial.java ├── CustomPlayerInventory.java ├── ErrorPrevention-UsingModInfo.java ├── EventHandlerTutorial.java ├── IExtendedEntityPropertiesTutorial.java ├── InventoryItemTutorial.java ├── ModdingWithAPIs.java ├── MultiInputFurnaceTutorial.java ├── README.md ├── RenderingCustomItemEntity.java ├── StructureArrayTutorialPart1.java ├── StructureArrayTutorialPart2.java ├── supplementary └── StructureArrayTutorialSupplement.java └── textures └── gui ├── custom_inventory.png ├── eye.png ├── inventoryitem.png ├── mana_bar.png └── mana_bar2.png /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ################# 2 | ## Eclipse 3 | ################# 4 | 5 | *.pydevproject 6 | .project 7 | .metadata 8 | bin/ 9 | tmp/ 10 | *.tmp 11 | *.bak 12 | *.swp 13 | *~.nib 14 | local.properties 15 | .classpath 16 | .settings/ 17 | .loadpath 18 | 19 | # External tool builders 20 | .externalToolBuilders/ 21 | 22 | # Locally stored "Eclipse launch configurations" 23 | *.launch 24 | 25 | # CDT-specific 26 | .cproject 27 | 28 | # PDT-specific 29 | .buildpath 30 | 31 | 32 | ################# 33 | ## Visual Studio 34 | ################# 35 | 36 | ## Ignore Visual Studio temporary files, build results, and 37 | ## files generated by popular Visual Studio add-ons. 38 | 39 | # User-specific files 40 | *.suo 41 | *.user 42 | *.sln.docstates 43 | 44 | # Build results 45 | 46 | [Dd]ebug/ 47 | [Rr]elease/ 48 | x64/ 49 | build/ 50 | [Bb]in/ 51 | [Oo]bj/ 52 | 53 | # MSTest test Results 54 | [Tt]est[Rr]esult*/ 55 | [Bb]uild[Ll]og.* 56 | 57 | *_i.c 58 | *_p.c 59 | *.ilk 60 | *.meta 61 | *.obj 62 | *.pch 63 | *.pdb 64 | *.pgc 65 | *.pgd 66 | *.rsp 67 | *.sbr 68 | *.tlb 69 | *.tli 70 | *.tlh 71 | *.tmp 72 | *.tmp_proj 73 | *.log 74 | *.vspscc 75 | *.vssscc 76 | .builds 77 | *.pidb 78 | *.log 79 | *.scc 80 | 81 | # Visual C++ cache files 82 | ipch/ 83 | *.aps 84 | *.ncb 85 | *.opensdf 86 | *.sdf 87 | *.cachefile 88 | 89 | # Visual Studio profiler 90 | *.psess 91 | *.vsp 92 | *.vspx 93 | 94 | # Guidance Automation Toolkit 95 | *.gpState 96 | 97 | # ReSharper is a .NET coding add-in 98 | _ReSharper*/ 99 | *.[Rr]e[Ss]harper 100 | 101 | # TeamCity is a build add-in 102 | _TeamCity* 103 | 104 | # DotCover is a Code Coverage Tool 105 | *.dotCover 106 | 107 | # NCrunch 108 | *.ncrunch* 109 | .*crunch*.local.xml 110 | 111 | # Installshield output folder 112 | [Ee]xpress/ 113 | 114 | # DocProject is a documentation generator add-in 115 | DocProject/buildhelp/ 116 | DocProject/Help/*.HxT 117 | DocProject/Help/*.HxC 118 | DocProject/Help/*.hhc 119 | DocProject/Help/*.hhk 120 | DocProject/Help/*.hhp 121 | DocProject/Help/Html2 122 | DocProject/Help/html 123 | 124 | # Click-Once directory 125 | publish/ 126 | 127 | # Publish Web Output 128 | *.Publish.xml 129 | *.pubxml 130 | 131 | # NuGet Packages Directory 132 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line 133 | #packages/ 134 | 135 | # Windows Azure Build Output 136 | csx 137 | *.build.csdef 138 | 139 | # Windows Store app package directory 140 | AppPackages/ 141 | 142 | # Others 143 | sql/ 144 | *.Cache 145 | ClientBin/ 146 | [Ss]tyle[Cc]op.* 147 | ~$* 148 | *~ 149 | *.dbmdl 150 | *.[Pp]ublish.xml 151 | *.pfx 152 | *.publishsettings 153 | 154 | # RIA/Silverlight projects 155 | Generated_Code/ 156 | 157 | # Backup & report files from converting an old project file to a newer 158 | # Visual Studio version. Backup files are not needed, because we have git ;-) 159 | _UpgradeReport_Files/ 160 | Backup*/ 161 | UpgradeLog*.XML 162 | UpgradeLog*.htm 163 | 164 | # SQL Server files 165 | App_Data/*.mdf 166 | App_Data/*.ldf 167 | 168 | ############# 169 | ## Windows detritus 170 | ############# 171 | 172 | # Windows image file caches 173 | Thumbs.db 174 | ehthumbs.db 175 | 176 | # Folder config file 177 | Desktop.ini 178 | 179 | # Recycle Bin used on file shares 180 | $RECYCLE.BIN/ 181 | 182 | # Mac crap 183 | .DS_Store 184 | 185 | 186 | ############# 187 | ## Python 188 | ############# 189 | 190 | *.py[co] 191 | 192 | # Packages 193 | *.egg 194 | *.egg-info 195 | dist/ 196 | build/ 197 | eggs/ 198 | parts/ 199 | var/ 200 | sdist/ 201 | develop-eggs/ 202 | .installed.cfg 203 | 204 | # Installer logs 205 | pip-log.txt 206 | 207 | # Unit test / coverage reports 208 | .coverage 209 | .tox 210 | 211 | #Translations 212 | *.mo 213 | 214 | #Mr Developer 215 | .mr.developer.cfg 216 | -------------------------------------------------------------------------------- /AddingCustomVillagerTrades.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Adding Custom Trades to Villagers 3 | */ 4 | /* 5 | Adding trades to villagers is easy. All you need to do is create a class that 6 | implements IvillageTradeHandler and register it to the VillagerRegistry. 7 | 8 | I will also show you how to add a variety of trades, including using metadata items, 9 | adding variable stacksizes and adding trades for more than one item. 10 | */ 11 | 12 | /** 13 | * Step 1: Registering your custom TradeHandler for each villager type 14 | */ 15 | @EventHandler 16 | public void preInit(FMLPreInitializationEvent event) 17 | { 18 | // iterate through all the villager types and add their new trades 19 | for (int i = 0; i < 5; ++i) { 20 | VillagerRegistry.instance().registerVillageTradeHandler(i, new TradeHandler()); 21 | } 22 | } 23 | 24 | /** 25 | * Step 2: Writing your TradeHandler class 26 | * 27 | */ 28 | public class TradeHandler implements IVillageTradeHandler 29 | { 30 | @Override 31 | public void manipulateTradesForVillager(EntityVillager villager, MerchantRecipeList recipeList, Random random) 32 | { 33 | switch(villager.getProfession()) { 34 | case 0: // FARMER 35 | // standard trade 36 | recipeList.add(new MerchantRecipe(new ItemStack(Item.emerald, 2), new ItemStack(YourMod.youritem, 1))); 37 | break; 38 | case 1: // LIBRARIAN 39 | // use metadata in either case 40 | recipeList.add(new MerchantRecipe(new ItemStack(Item.dye, 4, 15), // dye of metadata 15 is bonemeal, so we need 4 bonemeals 41 | new ItemStack(YourMod.youritem, 1, 6))); // to buy 1 mod item of metadata value 6 42 | 43 | // use the vanilla Item method to easily construct an ItemStack containing an enchanted book of any level 44 | recipeList.add(new MerchantRecipe(new ItemStack(Item.diamond, 1), Item.enchantedBook.getEnchantedItemStack(new EnchantmentData(Enchantment.flame, 1)))); 45 | break; 46 | case 2: // PRIEST 47 | // trading two itemstacks for one itemstack in return 48 | recipeList.add(new MerchantRecipe(new ItemStack(Item.emerald, 6), new ItemStack(YourMod.youritem1, 2), new ItemStack(YourMod.youritem2, 2))); 49 | break; 50 | case 3: // BLACKSMITH 51 | // using the passed in Random to randomize amounts; nextInt(value) returns an int between 0 and value (non-inclusive) 52 | recipeList.add(new MerchantRecipe(new ItemStack(Item.emerald, 6 + random.nextInt(6)), new ItemStack(YourMod.youritem1, 5 + random.nextInt(4)), new ItemStack(YourMod.youritem2, 1))); 53 | break; 54 | case 4: // BUTCHER 55 | // You can also add directly to the villager with 2 different methods: 56 | 57 | // Method 1: takes the list, an item ID that may be bought OR sold, rand, and a float value that 58 | // determines how common the trade is. The price of the item is determined in the HashMap 59 | // blacksmithSellingList, which we'll add our custom Item to first: 60 | villager.blacksmithSellingList.put(Integer.valueOf(YourMod.yourItem.itemID), new Tuple(Integer.valueOf(4), Integer.valueOf(8))); 61 | // Then add the trade, which will buy or sell for between 4 and 8 emeralds 62 | villager.addBlacksmithItem(recipeList, ItemToTrade.itemID, random, 0.5F); 63 | 64 | // Method 2: Basically the same as above, but only for selling items and at a fixed price of 1 emerald 65 | // However, the stack sold will have a variable size determined by the HashMap villagerStockList, 66 | // to which we first need to add our custom Item: 67 | villager.villagerStockList.put(Integer.valueOf(YourMod.YourItem.itemID), new Tuple(Integer.valueOf(16), Integer.valueOf(24))); 68 | // Then add the trade, which will sell between 16 and 24 of our Item for 1 emerald 69 | villager.addMerchantItem(recipeList, ItemToSell.itemID, random, 0.5F); 70 | break; 71 | default: 72 | break; 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /ArmorTutorial/README.md: -------------------------------------------------------------------------------- 1 | After seeing quite a few mods that have 20+ classes for armor, basically a whole new class for every single piece 2 | of armor, I decided it was time to write a tutorial on how to code in an Object-Oriented Programming-friendly 3 | fashion, and put together this demo armor mod. 4 | 5 | I have used this exact code to add dozens of new armors to the game, using several custom and vanilla armor 6 | materials, all using the exact same ItemCustomArmor class. One class, dozens of armor sets. There are lots 7 | of comments in the code; please read them. 8 | 9 | As a bonus, I threw in a mini-tutorial on how to effectively and efficiently add armors with new models, 10 | and although the model I had on hand does have some flaws that you can see in game, the basic premise of 11 | adding models to a map is still extremely useful. If your models are otherwise working correctly, you can 12 | use the method I show to streamline their addition into the game, rather than using lots of clunky special 13 | cases. 14 | 15 | Anyway, I hope this little example proves useful, and hope to see mods with far fewer classes in the future :P 16 | -------------------------------------------------------------------------------- /ArmorTutorial/src/main/java/armortutorial/ArmorTutorial.java: -------------------------------------------------------------------------------- 1 | package armortutorial; 2 | 3 | import java.util.logging.Logger; 4 | 5 | import net.minecraft.item.EnumArmorMaterial; 6 | import net.minecraft.item.Item; 7 | import net.minecraft.item.ItemStack; 8 | import net.minecraftforge.common.EnumHelper; 9 | import armortutorial.item.ItemCustomArmor; 10 | import cpw.mods.fml.common.Mod; 11 | import cpw.mods.fml.common.Mod.EventHandler; 12 | import cpw.mods.fml.common.Mod.Instance; 13 | import cpw.mods.fml.common.SidedProxy; 14 | import cpw.mods.fml.common.event.FMLPreInitializationEvent; 15 | import cpw.mods.fml.common.registry.GameRegistry; 16 | 17 | @Mod(modid = ArmorTutorial.MODID, name = "Armor Tutorial", version = ArmorTutorial.VERSION) 18 | public class ArmorTutorial 19 | { 20 | public static final String MODID = "armortutorial"; 21 | public static final String VERSION = "1.0"; 22 | 23 | @Instance(MODID) 24 | public static ArmorTutorial instance; 25 | 26 | @SidedProxy(clientSide = MODID + ".ClientProxy", serverSide = MODID + ".CommonProxy") 27 | public static CommonProxy proxy; 28 | 29 | public static Logger logger = Logger.getLogger("ARMOR TUTORIAL"); 30 | 31 | // Creating a new armor material is done using EnumHelper 32 | // Creating a custom cloth armor material is necessary because using vanilla CLOTH 33 | // expects an overlay layer, without which your game will crash :\ 34 | // 1.7.2: EnumArmorMaterial renamed to ArmorMaterial, otherwise exactly the same 35 | public static final EnumArmorMaterial CLOTH_CUSTOM = EnumHelper.addArmorMaterial("Cloth", 5, new int[]{1, 3, 2, 1}, 15); 36 | 37 | /** Example armor with a model: a skirt */ 38 | public static Item sampleArmor; 39 | /** A set of leather bandit / cowboy armor */ 40 | public static Item banditHelm, banditChest, banditLegs, banditBoots; 41 | 42 | @EventHandler 43 | public void preInit(FMLPreInitializationEvent event) { 44 | // I'm using this id as an expedient for the tutorial; if you are coding in 1.6.4, 45 | // you should at least get the starting item id index from config, or provide 46 | // individual config settings for each item id 47 | // 1.7.2: Don't need to worry about IDs! Hooray! 48 | int id = 17500; 49 | 50 | // this value seems to be unimportant when using custom textures and/or models 51 | // but we'll get a value unique from vanilla render indices anyway: 52 | int renderIndex = proxy.addArmor("custom_armor"); 53 | 54 | // note that the unlocalized name format may be very important for textures, 55 | // depending how you set it up; in this example, it is a key point for simplifying 56 | // texture registration. See {@link ItemCustomArmor} for more information. 57 | // 1.7.2: Remove the "id++" from the constructors 58 | sampleArmor = new ItemCustomArmor(id++, CLOTH_CUSTOM, renderIndex, 2).setUnlocalizedName("armor_sample_legs"); 59 | GameRegistry.registerItem(sampleArmor, sampleArmor.getUnlocalizedName().substring(5)); 60 | 61 | // Here we have a full armor set, using the SAME class as the other armor! 62 | // This is how proper Object-Oriented Programming works - there is usually no 63 | // need to create a new class for each armor piece or even set that you add. 64 | // Because we are using the unlocalized name to register textures, return armor texture, 65 | // etc., we are able to easily add many armors with one class and no special cases. 66 | banditHelm = new ItemCustomArmor(id++, CLOTH_CUSTOM, renderIndex, 0).setUnlocalizedName("armor_bandit_helm"); 67 | banditChest = new ItemCustomArmor(id++, CLOTH_CUSTOM, renderIndex, 1).setUnlocalizedName("armor_bandit_chest"); 68 | banditLegs = new ItemCustomArmor(id++, CLOTH_CUSTOM, renderIndex, 2).setUnlocalizedName("armor_bandit_legs"); 69 | banditBoots = new ItemCustomArmor(id++, CLOTH_CUSTOM, renderIndex, 3).setUnlocalizedName("armor_bandit_boots"); 70 | GameRegistry.registerItem(banditHelm, banditHelm.getUnlocalizedName().substring(5)); 71 | GameRegistry.registerItem(banditChest, banditChest.getUnlocalizedName().substring(5)); 72 | GameRegistry.registerItem(banditLegs, banditLegs.getUnlocalizedName().substring(5)); 73 | GameRegistry.registerItem(banditBoots, banditBoots.getUnlocalizedName().substring(5)); 74 | 75 | // Be sure to register any special renderers/models you may have 76 | // We need to do this after the Items are initialized, since we will 77 | // be using each Item with a model as a key in a map. 78 | proxy.registerRenderers(); 79 | 80 | // Add recipes after all blocks and items have been initialized 81 | // otherwise, you may get a null pointer if you try to use one before it exists 82 | // Note that "new Object[]{...}" is not necessary, just list everything directly: 83 | GameRegistry.addRecipe(new ItemStack(banditHelm), "RRR", "C C",'R', Item.leather, 'C', Item.silk); 84 | GameRegistry.addRecipe(new ItemStack(banditChest), "R R", "CRC","CCC", 'R', Item.leather, 'C', Item.silk); 85 | GameRegistry.addRecipe(new ItemStack(banditLegs), "RRR", "C C","C C", 'R', Item.leather, 'C', Item.silk); 86 | GameRegistry.addRecipe(new ItemStack(banditBoots), "C C", "R R", 'R', Item.leather, 'C', Item.silk); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /ArmorTutorial/src/main/java/armortutorial/ClientProxy.java: -------------------------------------------------------------------------------- 1 | package armortutorial; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | import java.util.logging.Level; 6 | 7 | import armortutorial.client.model.ModelSkirt; 8 | import net.minecraft.client.model.ModelBiped; 9 | import net.minecraft.item.Item; 10 | import cpw.mods.fml.client.registry.RenderingRegistry; 11 | 12 | public class ClientProxy extends CommonProxy 13 | { 14 | /** A Map allows for easy handling of many armor models */ 15 | public static final Map armorModels = new HashMap(); 16 | 17 | @Override 18 | public void registerRenderers() { 19 | // Model classes are all client-side only, so we must register them on the client side 20 | // Be sure to add a model for each Armor Item that requires one: 21 | addArmorModel(ArmorTutorial.sampleArmor, new ModelSkirt()); 22 | } 23 | 24 | @Override 25 | public int addArmor(String armor) { 26 | return RenderingRegistry.addNewArmourRendererPrefix(armor); 27 | } 28 | 29 | /** 30 | * Adds a mapping for an ItemArmor to a ModelBase for rendering 31 | * @param armor The armor Item 32 | * @param model The model should not be null and must extend ModelBiped 33 | */ 34 | private void addArmorModel(Item armor, ModelBiped model) { 35 | if (model == null) { 36 | // technically, you CAN add a null model, but the default is already to return null, so it would be redundant 37 | ArmorTutorial.logger.log(Level.WARNING, String.format("Error adding model for %s: Cannot add a NULL armor model!", armor.getUnlocalizedName())); 38 | return; 39 | } 40 | // better let yourself / users know when overwriting an entry, as it is likely to be a mistake! 41 | if (armorModels.containsKey(armor)) { 42 | ArmorTutorial.logger.log(Level.WARNING, String.format("A model for %s has already been registered! It will now be overwritten.", armor.getUnlocalizedName())); 43 | } 44 | armorModels.put(armor, model); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /ArmorTutorial/src/main/java/armortutorial/CommonProxy.java: -------------------------------------------------------------------------------- 1 | package armortutorial; 2 | 3 | public class CommonProxy 4 | { 5 | public void registerRenderers() {} 6 | 7 | // this just lets us override the method in the ClientProxy, 8 | // since the RenderingRegistry only needs to be done client-side 9 | public int addArmor(String string) { 10 | return 0; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /ArmorTutorial/src/main/java/armortutorial/client/model/ModelSkirt.java: -------------------------------------------------------------------------------- 1 | package armortutorial.client.model; 2 | 3 | import net.minecraft.client.model.ModelBiped; 4 | import net.minecraft.client.model.ModelRenderer; 5 | import net.minecraft.entity.Entity; 6 | 7 | /** 8 | * 9 | * This model is a cute skirt, done by RazzleberryFox. 10 | * 11 | * Unfortunately, the rotation angles are slightly off, so the 12 | * skirt renders at an angle rather than straight up and down, 13 | * and I don't feel like messing with it at the moment, but it 14 | * suffices to demonstrate the main points. 15 | * 16 | */ 17 | public class ModelSkirt extends ModelBiped 18 | { 19 | /** An array of all the skirt model parts */ 20 | private ModelRenderer[] parts; 21 | 22 | /** 23 | * Since I'm too lazy to try and derive a formula from these, I've 24 | * decided to put them in an array instead. The y rotation is the 25 | * only value that differs between each shape, so we can use this 26 | * to make an array of shapes, which is compact and handy to use. 27 | */ 28 | private static final float[] ry = { 29 | 0.122173F, 30 | -3.089233F, 31 | 0.7853982F, 32 | -2.443461F, 33 | 1.413717F, 34 | -1.815142F, 35 | 2.042035F, 36 | -1.186824F, 37 | 2.670354F, 38 | -0.5585054F 39 | }; 40 | 41 | public ModelSkirt() 42 | { 43 | textureWidth = 64; 44 | textureHeight = 32; 45 | 46 | // assign a new array based on the number of parts we should have 47 | // i.e. one for each y rotation 48 | parts = new ModelRenderer[ry.length]; 49 | for (int i = 0; i < parts.length; ++i) { 50 | parts[i] = new ModelRenderer(this, 56, 16); 51 | // The final parameter of addBox is scale: 52 | // typical armor uses 1.0F, boots use 0.5F, but it can be anything you need 53 | // In this case, the skirt is a bit small, so I've scaled it to 1.8F 54 | parts[i].addBox(0.1F, 0F, -2.1F, 2, 6, 2, 1.8F); 55 | parts[i].setRotationPoint(0F, 10F, 0F); 56 | parts[i].setTextureSize(64, 32); 57 | parts[i].mirror = true; 58 | 59 | // and here we use our rotation y array: 60 | setRotation(parts[i], -0.4363323F, ry[i], -0.4363323F); 61 | } 62 | } 63 | 64 | @Override 65 | public void render(Entity entity, float f, float f1, float f2, float f3, float f4, float f5) { 66 | super.render(entity, f, f1, f2, f3, f4, f5); 67 | setRotationAngles(f, f1, f2, f3, f4, f5, entity); 68 | // see how nice arrays are? 69 | for (int i = 0; i < parts.length; ++i) { 70 | parts[i].render(f5); 71 | } 72 | } 73 | 74 | @Override 75 | public void setRotationAngles(float par1, float par2, float par3, float par4, float par5, float par6, Entity entity) { 76 | super.setRotationAngles(par1, par2, par3, par4, par5, par6, entity); 77 | // TODO set rotation angles of model based on riding, sneaking, etc. 78 | // See {@link ModelBiped} for examples 79 | } 80 | 81 | private void setRotation(ModelRenderer model, float x, float y, float z) { 82 | model.rotateAngleX = x; 83 | model.rotateAngleY = y; 84 | model.rotateAngleZ = z; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /ArmorTutorial/src/main/java/armortutorial/item/ItemCustomArmor.java: -------------------------------------------------------------------------------- 1 | package armortutorial.item; 2 | 3 | import net.minecraft.client.model.ModelBiped; 4 | import net.minecraft.client.renderer.texture.IconRegister; 5 | import net.minecraft.creativetab.CreativeTabs; 6 | import net.minecraft.entity.Entity; 7 | import net.minecraft.entity.EntityLivingBase; 8 | import net.minecraft.entity.player.EntityPlayer; 9 | import net.minecraft.item.EnumArmorMaterial; 10 | import net.minecraft.item.ItemArmor; 11 | import net.minecraft.item.ItemStack; 12 | import net.minecraft.world.World; 13 | import armortutorial.ArmorTutorial; 14 | import armortutorial.ClientProxy; 15 | import cpw.mods.fml.relauncher.Side; 16 | import cpw.mods.fml.relauncher.SideOnly; 17 | 18 | /** 19 | * 20 | * Armors that only need to specify a material and texture can ALL use one class. 21 | * 22 | * The texture can be returned based on the unlocalized name; in this example, I 23 | * specify that each armor name should be suffixed by "_type", where "type" is 24 | * "helm", "chest", "legs", or "boots". 25 | * 26 | * There is no need to make separate classes for each armor piece or even set unless 27 | * the armor set is very unique and requires special handling. 28 | * 29 | */ 30 | public class ItemCustomArmor extends ItemArmor 31 | { 32 | /** 33 | * Armor types as used on player: 0 boots, 1 legs, 2 chest, 3 helm 34 | * Armor types as used in armor class: 0 helm, 1 chest, 2 legs, 3 boots 35 | */ 36 | public ItemCustomArmor(int id, EnumArmorMaterial material, int renderIndex, int type) { 37 | super(id, material, renderIndex, type); 38 | // TODO if you want different armors to have different tabs, set the creative tab 39 | // during item initialization to override the one set here 40 | // it's not a bad idea to have a default one, though, just in case you forget 41 | setCreativeTab(CreativeTabs.tabCombat); 42 | } 43 | 44 | @Override 45 | public void onArmorTickUpdate(World world, EntityPlayer player, ItemStack stack) { 46 | // this can be used to add a variety of effects while the armor is worn 47 | // but since this is supposed to be a generic armor class for all kinds 48 | // of armor, we will not be using it 49 | } 50 | 51 | @Override 52 | @SideOnly(Side.CLIENT) 53 | public ModelBiped getArmorModel(EntityLivingBase entity, ItemStack stack, int armorSlot) { 54 | // adding all the armor models to a Map allows this method to be handled cleanly 55 | // if the Item is not in the map, get(this) returns null, which is perfect, since 56 | // returning null defaults to using the vanilla armor model 57 | return ClientProxy.armorModels.get(this); 58 | } 59 | 60 | @Override 61 | public String getArmorTexture(ItemStack stack, Entity entity, int slot, int layer) { 62 | // Assuming all armors are named as "armor_name_chest", "armor_name_legs", etc. 63 | // then using the following format, the armor texture files should be named 64 | // "armor_name_layer_1" and "armor_name_layer_2"; layer 2 is for the legs 65 | String name = getUnlocalizedName().substring(5, getUnlocalizedName().lastIndexOf("_")); 66 | return String.format("%s:textures/armor/%s_layer_%d.png", ArmorTutorial.MODID, name, (slot == 2 ? 2 : 1)); 67 | // If you are not familiar with String.format, the above statement is equivalent to: 68 | /* 69 | if (slot == 2) { 70 | return ArmorTutorial.MODID + ":textures/armor/" + name + "_layer_2.png"; 71 | } else { 72 | return ArmorTutorial.MODID + ":textures/armor/" + name + "_layer_1.png"; 73 | } 74 | 75 | // which could also be written as: 76 | 77 | return ArmorTutorial.MODID + ":textures/armor/" + name + (slot == 2 ? "_layer_2.png" : "_layer_1.png"); 78 | */ 79 | } 80 | 81 | @Override 82 | @SideOnly(Side.CLIENT) 83 | public void registerIcons(IconRegister register) { 84 | itemIcon = register.registerIcon(ArmorTutorial.MODID + ":" + getUnlocalizedName().substring(5)); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /ArmorTutorial/src/main/resources/assets/armortutorial/lang/en_US.lang: -------------------------------------------------------------------------------- 1 | 2 | item.armor_sample_legs.name=Skirt 3 | 4 | item.armor_bandit_helm.name=Bandana 5 | item.armor_bandit_chest.name=Leather Vest 6 | item.armor_bandit_legs.name=Riding Pants 7 | item.armor_bandit_boots.name=Sturdy Boots 8 | -------------------------------------------------------------------------------- /ArmorTutorial/src/main/resources/assets/armortutorial/textures/armor/armor_bandit_layer_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolAlias/Forge_Tutorials/124a0d756a9839ca0c2f737e2be5d8917563707c/ArmorTutorial/src/main/resources/assets/armortutorial/textures/armor/armor_bandit_layer_1.png -------------------------------------------------------------------------------- /ArmorTutorial/src/main/resources/assets/armortutorial/textures/armor/armor_bandit_layer_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolAlias/Forge_Tutorials/124a0d756a9839ca0c2f737e2be5d8917563707c/ArmorTutorial/src/main/resources/assets/armortutorial/textures/armor/armor_bandit_layer_2.png -------------------------------------------------------------------------------- /ArmorTutorial/src/main/resources/assets/armortutorial/textures/armor/armor_sample_layer_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolAlias/Forge_Tutorials/124a0d756a9839ca0c2f737e2be5d8917563707c/ArmorTutorial/src/main/resources/assets/armortutorial/textures/armor/armor_sample_layer_2.png -------------------------------------------------------------------------------- /ArmorTutorial/src/main/resources/assets/armortutorial/textures/items/armor_bandit_boots.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolAlias/Forge_Tutorials/124a0d756a9839ca0c2f737e2be5d8917563707c/ArmorTutorial/src/main/resources/assets/armortutorial/textures/items/armor_bandit_boots.png -------------------------------------------------------------------------------- /ArmorTutorial/src/main/resources/assets/armortutorial/textures/items/armor_bandit_chest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolAlias/Forge_Tutorials/124a0d756a9839ca0c2f737e2be5d8917563707c/ArmorTutorial/src/main/resources/assets/armortutorial/textures/items/armor_bandit_chest.png -------------------------------------------------------------------------------- /ArmorTutorial/src/main/resources/assets/armortutorial/textures/items/armor_bandit_helm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolAlias/Forge_Tutorials/124a0d756a9839ca0c2f737e2be5d8917563707c/ArmorTutorial/src/main/resources/assets/armortutorial/textures/items/armor_bandit_helm.png -------------------------------------------------------------------------------- /ArmorTutorial/src/main/resources/assets/armortutorial/textures/items/armor_bandit_legs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolAlias/Forge_Tutorials/124a0d756a9839ca0c2f737e2be5d8917563707c/ArmorTutorial/src/main/resources/assets/armortutorial/textures/items/armor_bandit_legs.png -------------------------------------------------------------------------------- /ArmorTutorial/src/main/resources/assets/armortutorial/textures/items/armor_sample_legs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolAlias/Forge_Tutorials/124a0d756a9839ca0c2f737e2be5d8917563707c/ArmorTutorial/src/main/resources/assets/armortutorial/textures/items/armor_sample_legs.png -------------------------------------------------------------------------------- /ArmorTutorial/src/main/resources/mcmod.info: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "modid": "armortutorial", 4 | "name": "Armor Tutorial", 5 | "description": "Learn how to add armors in an OOP-friendly manner, as well as how to add an armor with a new model", 6 | "version": "1.0", 7 | "mcversion": "1.6.4 and 1.7.2", 8 | "url": "", 9 | "updateUrl": "", 10 | "authors": ["coolAlias"], 11 | "credits": "", 12 | "logoFile": "", 13 | "screenshots": [], 14 | "dependencies": [] 15 | } 16 | ] 17 | -------------------------------------------------------------------------------- /CraftingWithPotionsTutorial.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Using Potions in Crafting Recipes 3 | */ 4 | /* 5 | For this tutorial, I will explain how to use a potion in a crafting recipe, either 6 | as an ingredient, or as a result. 7 | 8 | Just in case you don't already know, we will first cover how to set up a basic 9 | recipe, then move on to potions and then on to something more advanced. 10 | */ 11 | 12 | /** 13 | * PART 1: Basic Crafting and Crafting with Potions 14 | */ 15 | // Recipes are usually declared from within the load method in your main mod class: 16 | @EventHandler 17 | public void load(FMLInitializationEvent event) 18 | { 19 | /* 20 | addShapelessRecipe takes an ItemStack argument; this is the item that will be crafted. 21 | Note that you can also modify the stacksize of the crafting result and set metadata: 22 | new ItemStack(YourModName.yourItemName, stacksize, metadata) 23 | You can just use the first argument and stacksize will default to 1, metadata to 0 24 | 25 | The following arguments are all of the blocks / items needed to craft it, in any order. 26 | In the example, a block of dirt and a stick will craft yourItemName. 27 | */ 28 | GameRegistry.addShapelessRecipe(new ItemStack(YourModName.yourItemName), Block.dirt, Item.stick); 29 | 30 | /* 31 | addRecipe takes the same first argument, followed by 3 strings. 32 | Each string represents a horizontal line in the crafting grid, using different characters to represent the different items needed. Here we've used "x", "#" and "z", but you can use whatever you want. Use as many different characters as you need. 33 | 34 | Following this representation of the crafting grid, we define our variables like so: 'x', Object1, '#', Object2, etc., 35 | where each Object is a Block, Item or ItemStack. You can use your custom items in the recipe simply by referring to the instance you created in your class, i.e. using its name. 36 | */ 37 | GameRegistry.addRecipe(new ItemStack(YourModName.yourItemName), "xxx", "###", "zzz", 'x', Block.dirt, '#', Item.stick, 'z', yourItemName); 38 | } 39 | // Here's a concrete example of a very basic item, a ThrowingRock 40 | // This declares the item; it goes in the main mod 41 | public static final Item throwingRock = new ItemThrowingRock(modItemIndex++).setUnlocalizedName("throwingRock"); 42 | 43 | // This recipe is shapeless and turns one cobblestone block into 9 throwingRocks. 44 | // Note that we're using the second argument of ItemStack to return a stack of 9 throwingRocks 45 | // but we can still leave out the metadata value 46 | GameRegistry.addShapelessRecipe(new ItemStack(MyMod.throwingRock, 9), Block.cobblestone); 47 | 48 | // This recipe has a shape (all 9 squares of the crafting grid) and turns 9 throwing rocks 49 | // back into a cobblestone block. 50 | GameRegistry.addRecipe(new ItemStack(Block.cobblestone), "xxx", "xxx", "xxx", 'x', throwingRock); 51 | 52 | /* 53 | Remember to add recipes in the load method. 54 | 55 | Ok, now we're ready to tackle using potions in our recipes. It's actually quite simple. 56 | We need to use the 3rd argument in ItemStack - itemDamage. While most items use this to 57 | keep track of wear and tear, potions use it as a secondary ID. 58 | 59 | For example, we can craft a damaged wooden sword from a stick like so: 60 | */ 61 | GameRegistry.addShapelessRecipe(new ItemStack(Item.swordWood,1,25), Item.stick); 62 | /* 63 | The sword will already have 25 damage to its durability when crafted 'brand new' 64 | 65 | We need to make a stack of Item.potion with an itemDamage value that matches the'id' of 66 | the potion we want. But what are the potion ID values? The easiest course of action is 67 | to check the minecraft wiki here: http://www.minecraftwiki.net/wiki/Potions 68 | 69 | Halfway down the page is a Data Value Table that shows the itemDamage values for every 70 | potion currently in game, as well as those in the code. Let's look at an example. 71 | 72 | Let's say I want to use a Potion of Poison in my recipe. Looking at the wiki table above, 73 | I see a Regular Potion of Poison has an iD of 8196. So my recipe looks like this: 74 | */ 75 | // This one will craft my mod item from a Potion of Poison: 76 | GameRegistry.addShapelessRecipe(new ItemStack(YourMod.yourModItem), new ItemStack(Item.potion,1,8196)); 77 | // This one will craft a Potion of Poison from my mod item: 78 | GameRegistry.addShapelessRecipe(new ItemStack(Item.potion,1,8196), new ItemStack(YourMod.yourModItem)); 79 | /* 80 | Obviously, you can make your recipes as simple or complex as you like. 81 | 82 | Well, that's pretty easy to do, but not very intuitive. It's not obvious just from 83 | looking at the code what (Item.potion,1,8196) is, which I personally don't like. 84 | 85 | If you're up for something a little more advanced that will allow you to replace 8196 86 | with a readable name, read on. Otherwise, you should be good to go - just check the wiki 87 | for data values of potions anytime you need them! 88 | */ 89 | /** 90 | * PART 2: 91 | */ 92 | /* 93 | For this, we're going to take advantage of the type 'enum.' This allows us to define 94 | constant values in the format of CONSTANT_NAME(value1, value2,...). So we'll set up a 95 | very basic enum class called EnumPotionID like so: 96 | */ 97 | public enum EnumPotionID 98 | { 99 | 100 | POISON(8196), 101 | 102 | private final int potionID; 103 | 104 | private EnumPotionID(int par1) { 105 | this.potionID = par1; 106 | } 107 | 108 | public int id() 109 | { 110 | return this.potionID; 111 | } 112 | } 113 | /* 114 | For more information about the enum type, see: http://docs.oracle.com/javase/tutorial/java/javaOO/enum.html 115 | 116 | This allows us to call EnumPotionID.POISON.id(), which will return the 117 | value 8196, much like calling Item.swordWood returns the correct Item, which we can 118 | then call functions for with the same syntax. 119 | 120 | So now our recipe will look like this: 121 | */ 122 | GameRegistry.addShapelessRecipe(new ItemStack(Item.potion,1,EnumPotionID.POISON.id()), new ItemStack(YourMod.yourModItem)); 123 | /* 124 | Much more readable, isn't it? 125 | 126 | There is also the advantage that if the developers ever change the potion iDs, you will 127 | only need to update your EnumPotionID class values and everywhere else in your code will 128 | automatically be updated. 129 | 130 | Well that's all fine and dandy, but what if you want to make an item craftable from ANY 131 | version of a potion? Say, for example, you want a Potato + any version of a Poison 132 | potion to make a Poisoned Potato. You could code all the recipes by hand. But that 133 | wouldn't be very interesting. 134 | 135 | First, you will need to implement the EnumPotionID class as above, or simply input all 136 | the values by hand. Here's my full version of EnumPotionID: 137 | */ 138 | public enum EnumPotionID 139 | { 140 | /* POTION DATA VALUES from http://www.minecraftwiki.net/wiki/Potions 141 | * EXT means "Extended" version of potion 142 | * REV means "Reverted" version of potion 143 | */ 144 | POTION_AWKWARD(16), 145 | POTION_THICK(32), 146 | POTION_MUNDANE(128), 147 | POTION_MUNDANE_EXT(64), 148 | 149 | /* 150 | * HELPFUL POTIONS 151 | */ 152 | POTION_REGEN(8193), 153 | POTION_REGEN_II(8225), 154 | POTION_REGEN_EXT(8257), 155 | POTION_REGEN_II_EXT(8289), 156 | POTION_REGEN_SPLASH(16385), 157 | POTION_REGEN_SPLASH_II(16417), 158 | POTION_REGEN_SPLASH_EXT(16449), 159 | 160 | POTION_SWIFTNESS(8194), 161 | POTION_SWIFTNESS_II(8226), 162 | POTION_SWIFTNESS_EXT(8258), 163 | POTION_SWIFTNESS_II_EXT(8290), 164 | POTION_SWIFTNESS_SPLASH(16386), 165 | POTION_SWIFTNESS_SPLASH_II(16418), 166 | POTION_SWIFTNESS_SPLASH_EXT(16450), 167 | 168 | POTION_FIRERESIST(8195), 169 | POTION_FIRERESIST_REV(8227), 170 | POTION_FIRERESIST_EXT(8259), 171 | POTION_FIRERESIST_SPLASH(16387), 172 | POTION_FIRERESIST_SPLASH_REV(16419), 173 | POTION_FIRERESIST_SPLASH_EXT(16451), 174 | 175 | POTION_HEALING(8197), 176 | POTION_HEALING_II(8229), 177 | POTION_HEALING_REV(8261), 178 | POTION_HEALING_SPLASH(16389), 179 | POTION_HEALING_SPLASH_II(16421), 180 | POTION_HEALING_SPLASH_REV(16453), 181 | 182 | POTION_NIGHTVISION(8198), 183 | POTION_NIGHTVISION_REV(8230), 184 | POTION_NIGHTVISION_EXT(8262), 185 | POTION_NIGHTVISION_SPLASH(16390), 186 | POTION_NIGHTVISION_SPLASH_REV(16422), 187 | POTION_NIGHTVISION_SPLASH_EXT(16454), 188 | 189 | POTION_STRENGTH(8201), 190 | POTION_STRENGTH_II(8233), 191 | POTION_STRENGTH_EXT(8265), 192 | POTION_STRENGTH_II_EXT(8292), 193 | POTION_STRENGTH_SPLASH(16393), 194 | POTION_STRENGTH_SPLASH_II(16425), 195 | POTION_STRENGTH_SPLASH_EXT(16457), 196 | 197 | POTION_INVISIBILITY(8206), 198 | POTION_INVISIBILITY_REV(8238), 199 | POTION_INVISIBILITY_EXT(8270), 200 | POTION_INVISIBILITY_SPLASH(16398), 201 | POTION_INVISIBILITY_SPLASH_REV(16430), 202 | POTION_INVISIBILITY_SPLASH_EXT(16462), 203 | 204 | /* 205 | * HARMFUL POTIONS 206 | */ 207 | POTION_POISON(8196), 208 | POTION_POISON_II(8228), 209 | POTION_POISON_EXT(8260), 210 | POTION_POISON_SPLASH(16388), 211 | POTION_POISON_SPLASH_II(16420), 212 | POTION_POISON_SPLASH_EXT(16452), 213 | 214 | POTION_WEAKNESS(8200), 215 | POTION_WEAKNESS_REV(8232), 216 | POTION_WEAKNESS_EXT(8264), 217 | POTION_WEAKNESS_SPLASH(16392), 218 | POTION_WEAKNESS_SPLASH_REV(16424), 219 | POTION_WEAKNESS_SPLASH_EXT(16456), 220 | 221 | POTION_SLOWNESS(8202), 222 | POTION_SLOWNESS_REV(8234), 223 | POTION_SLOWNESS_EXT(8266), 224 | POTION_SLOWNESS_SPLASH(16394), 225 | POTION_SLOWNESS_SPLASH_REV(16426), 226 | POTION_SLOWNESS_SPLASH_EXT(16458), 227 | 228 | POTION_HARM(8204), 229 | POTION_HARM_II(8236), 230 | POTION_HARM_REV(8268), 231 | POTION_HARM_SPLASH(16396), 232 | POTION_HARM_SPLASH_II(16428), 233 | POTION_HARM_SPLASH_REV(16460); 234 | 235 | private final int potionID; 236 | 237 | private EnumPotionID(int par1) { 238 | this.potionID = par1; 239 | } 240 | 241 | public int id() 242 | { 243 | return this.potionID; 244 | } 245 | } 246 | 247 | // Next, we're going to make an array "poisonPotions" like so: 248 | public static final Object[] poisonPotions = { 249 | new ItemStack(Item.potion,1,EnumPotionID.POISON.id()), 250 | new ItemStack(Item.potion,1,EnumPotionID.POISON_II.id()), 251 | new ItemStack(Item.potion,1,EnumPotionID.POISON_EXT.id()), 252 | new ItemStack(Item.potion,1,EnumPotionID.POISON_SPLASH.id()), 253 | new ItemStack(Item.potion,1,EnumPotionID.POISON_SPLASH_II.id()), 254 | new ItemStack(Item.potion,1,EnumPotionID.POISON_SPLASH_EXT.id()) 255 | }; 256 | 257 | // Then you would add recipes like this: 258 | for (int i = 0; i < EnumPotionID.poisonPotions.length; ++i) 259 | { 260 | GameRegistry.addShapelessRecipe(new ItemStack(Item.poisonousPotato), Item.potato, EnumPotionID.poisonPotions[i]); 261 | } 262 | /* 263 | If you don't know about arrays, here's a brief explanation: 264 | 265 | An array is basically an ordered list of objects of any type. The array is accessed 266 | using brackets and the place of the object you want in the array, such that arrayName[0] 267 | returns the first object in the array, and arrayName[arrayName.length-1] returns the 268 | last object in the array. 269 | 270 | Arrays are very powerful tools, but if you're new to them, you're likely crash to your 271 | game by throwing null pointer exceptions and not know why, as they won't show up as 272 | errors in your code. Array errors can be difficult to pinpoint, and a solid understanding 273 | of their functionality will go a long way in helping you. 274 | 275 | Learn more here: http://docs.oracle.com/javase/tutorial/java/nutsandbolts/arrays.html 276 | 277 | Ok, so we've used an array to auto-generate our crafting recipes for us. Great. But say for some reason you want every potion type craftable into your new item? For example, you want to make a new FoodItems that confers potion effects based on the potion it was crafted with. Assuming you have your newFoodItem class set up and only need to generate recipes, here's what you could do: 278 | */ 279 | /* 280 | * Object[][] potionMatrix is set up so that the first 281 | * bracket identifies the potion type (e.g. POISON) and 282 | * the second bracket the specific instance of that potion 283 | * (e.g. POISON_EXT) 284 | */ 285 | Object[][] potionMatrix = 286 | {{ new ItemStack(Item.potion,1,EnumPotionID.POISON.potionID()), 287 | new ItemStack(Item.potion,1,EnumPotionID.POISON_II.potionID()), 288 | new ItemStack(Item.potion,1,EnumPotionID.POISON_EXT.potionID()), 289 | new ItemStack(Item.potion,1,EnumPotionID.POISON_SPLASH.potionID()), 290 | new ItemStack(Item.potion,1,EnumPotionID.POISON_SPLASH_II.potionID()), 291 | new ItemStack(Item.potion,1,EnumPotionID.POISON_SPLASH_EXT.potionID()) 292 | }, 293 | { new ItemStack(Item.potion,1,EnumPotionID.WEAKNESS.potionID()), 294 | new ItemStack(Item.potion,1,EnumPotionID.WEAKNESS_REV.potionID()), 295 | new ItemStack(Item.potion,1,EnumPotionID.WEAKNESS_EXT.potionID()), 296 | new ItemStack(Item.potion,1,EnumPotionID.WEAKNESS_SPLASH.potionID()), 297 | new ItemStack(Item.potion,1,EnumPotionID.WEAKNESS_SPLASH_REV.potionID()), 298 | new ItemStack(Item.potion,1,EnumPotionID.WEAKNESS_SPLASH_EXT.potionID()) 299 | }, 300 | {etc.}}; 301 | 302 | /* 303 | * Object[] yourModItems contains the corresponding new food items for 304 | * each potion effect. There MUST be the same number of new items as 305 | * there are Potion types in potionMatrix[][] or you WILL throw null 306 | * pointer exceptions and crash your game. 307 | */ 308 | Object[] yourModItems = {YourMod.breadPoison, YourMod.breadWeakness, etc.}; 309 | 310 | // Iterates through the base potion types 311 | for (int j = 0; j < potionMatrix.length; ++j) 312 | { 313 | // Iterates through the specific potions of a single type 314 | // Be sure to check the length of this array as well, because it may not be the 315 | // same for every index [j] 316 | for (int k = 0; k < potionMatrix[j].length; ++k) 317 | { 318 | Object potioninrecipe = potionMatrix[j][k]; 319 | GameRegistry.addShapelessRecipe(new ItemStack(yourModItems[j]), new Object[] {Item.bread, potioninrecipe}); 320 | } 321 | } 322 | /* 323 | This would generate all the recipes you need to make your Weak Bread, Poisoned Bread, 324 | etc., as you define in your own class. If you're like me and don't like to have lots of 325 | code mucking up your main mod class, just create a new class RegisterCraftingRecipes 326 | with an addRecipe() function and call it like so: 327 | */ 328 | (new RegisterCraftingRecipes()).addRecipes(); 329 | /* 330 | Then you can put all that stuff there and your main mod will look nice and tidy! 331 | 332 | Good luck with your Potion recipes :) 333 | */ -------------------------------------------------------------------------------- /ErrorPrevention-UsingModInfo.java: -------------------------------------------------------------------------------- 1 | /** Error Prevention: Creating a ModInfo file */ 2 | 3 | /* 4 | This has been covered in other tutorials, but I feel it's important enough to mention again. 5 | 6 | I always make a ModInfo class that defines my mod variables such as my mod id then I reference that instead of 7 | hard-coding the ID everwhere. 8 | 9 | Not only does this prevent me from making typos, but it also allows me to change my mod id, mod name or other 10 | variable in one single location and all of my code will still be correct. 11 | 12 | Here's how you can make one for yourself: 13 | */ 14 | @Mod(modid = ModInfo.ID, name = ModInfo.NAME, version = ModInfo.VERSION) 15 | @NetworkMod(clientSideRequired=true, serverSideRequired=false, 16 | channels = {ModInfo.CHANNEL}, packetHanlder = ALPacketHandler.class) 17 | 18 | public final class ArcaneLegacy 19 | { 20 | @Instance(ModInfo.ID) 21 | public static ArcaneLegacy instance; 22 | 23 | // rest of main class here 24 | 25 | } 26 | 27 | // And ModInfo would look like this (in a separate class, not in the main mod): 28 | 29 | public class ModInfo 30 | { 31 | public static final String ID = "coolaliasarcanelegacy"; 32 | public static final String NAME = "Arcane Legacy"; 33 | public static final String VERSION = "0.1.0"; 34 | public static final String CLIENT_PROXY = "coolalias.arcanelegacy.client.ClientProxy"; 35 | public static final String COMMON_PROXY = "coolalias.arcanelegacy.common.CommonProxy"; 36 | public static final String CHANNEL = "ChannelCAAL"; 37 | } 38 | 39 | /* 40 | You can of course add any other information related to your mod here as well, such as CHANNELS for your packet handler. 41 | Whenever you would put "modid" in your code, change it to ModInfo.ID, such as in the Item method registerIcons: 42 | */ 43 | 44 | @Override 45 | @SideOnly(Side.CLIENT) 46 | public void registerIcons(IconRegister iconRegister) 47 | { 48 | this.itemIcon = iconRegister.registerIcon(ModInfo.ID + ":" + this.getUnlocalizedName().substring(5)); 49 | } 50 | 51 | /* 52 | If you ever change your mod id, now you only need to change it in one place, ModInfo, and ALL of your code will still 53 | be 100% correct. Also you won't ever have to worry about typos 54 | 55 | */ 56 | -------------------------------------------------------------------------------- /EventHandlerTutorial.java: -------------------------------------------------------------------------------- 1 | /** Implementing and Using Forge's EventHandler */ 2 | 3 | /* 4 | If you want to change any type of default Minecraft behavior, chances are there is a Forge Event that handles it. 5 | 6 | There are Player Events, Living Events, Item Events, World Events, TerrainGenEvents, Minecart Events... there's just 7 | so much you can do with these it's incredible. 8 | 9 | I personally prefer using EventHandler over TickHandler for this very reason - most things you could ever want to do 10 | already have a framework built to handle it, whereas Tick Handler you have to build it all yourself. 11 | 12 | This tutorial will cover: 13 | 1. Building and using an Event Handler 14 | 2. Advanced information for handling events 15 | 3. A sampling of Event types and their possible uses 16 | */ 17 | 18 | /** 19 | * Step 1: Create TutEventHandler class 20 | */ 21 | /* 22 | IMPORTANT!!! Do NOT name your EventHandler 'EventHandler' - that's already the name of a Forge class. 23 | Also, do NOT edit any of the Forge classes. You need to create a new class to handle events. 24 | */ 25 | 26 | public class TutEventHandler 27 | { 28 | } 29 | 30 | /** 31 | * Step 2: Registering your EventHandler 32 | */ 33 | /* 34 | Resgister your event handler class to EVENT_BUS either in 'load' or 'postInit' methods in your main mod. Note that this step is the same in both 1.6.4 and 1.7.2, but see below for more information on the different event buses. 35 | */ 36 | 37 | @EventHandler 38 | public void load(FMLInitializationEvent event) 39 | { 40 | // IMPORTANT: Be sure to register your handler on the correct bus!!! (see below) 41 | 42 | // the majority of events use the MinecraftForge event bus: 43 | MinecraftForge.EVENT_BUS.register(new TutEventHandler()); 44 | 45 | // but some are on the FML bus: 46 | FMLCommonHandler.instance().bus().register(new YourFMLEventHandler()); 47 | } 48 | 49 | /* 50 | NOTE: Registering to the correct BUS 51 | 52 | You have followed all the steps and your event handling methods just do not seem to be working, what could possibly be going on? Well, each event is posted to a different event bus, and if your event handler is registered to the incorrect bus, then your method will never get called. The vast majority of events are posted to the MinecraftForge.EVENT_BUS, but there are several other event buses: 53 | 54 | 1. MinecraftForge.EVENT_BUS: Most events get posted to this bus. 55 | 56 | 2. MinecraftForge.TERRAIN_GEN_BUS: Most world generation events happen here, such as Populate, Decorate, etc., with the strange exception that Pre and Post events are on the regular EVENT_BUS 57 | 58 | 3. MinecraftForge.ORE_GEN_BUS: Ore generation, obviously 59 | 60 | 4. FML Events: these become very important in 1.7.2, as this is where TickEvents and KeyInputEvents are posted, with TickHandler and KeyHandler no longer existing. 61 | 62 | It is very important to register your event handler to the correct event bus, and only put those events that get posted to a certain event bus in a handler registered to that bus, or your event handling will fail. 63 | 64 | You're finished! That was easy But it doesn't do anything right now, so on to step 3. 65 | */ 66 | /** 67 | * Step 3: Add events to your event handler (an example) 68 | */ 69 | /* 70 | Look through MinecraftForge event types for ones you want to use and add them to your EventHandler by creating a new method with the appropriate Event as a parameter. 71 | 72 | 1.6.4: It must be prefaced by "@ForgeSubscribe" so that it gets called automatically at the right times. 73 | 1.7.2: It must be prefaced by "@SubscribeEvent" so that it gets called automatically at the right times. 74 | 75 | Do not, I repeat do NOT edit the event classes directly. Also, you do NOT need to make a class extending the Event class. 76 | */ 77 | 78 | // In your TutEventHandler class - the name of the method doesn't matter 79 | // Only the Event type parameter is what's important (see below for explanations of some types) 80 | @ForgeSubscribe 81 | public void onLivingUpdateEvent(LivingUpdateEvent event) 82 | { 83 | // This event has an Entity variable, access it like this: 84 | event.entity; 85 | 86 | // do something to player every update tick: 87 | if (event.entity instanceof EntityPlayer) 88 | { 89 | EntityPlayer player = (EntityPlayer) event.entity; 90 | ItemStack heldItem = player.getHeldItem(); 91 | if (heldItem != null && heldItem.itemID == Item.arrow.itemID) { 92 | player.capabilities.allowFlying = true; 93 | } 94 | else { 95 | player.capabilities.allowFlying = player.capabilities.isCreativeMode ? true : false; 96 | } 97 | } 98 | } 99 | 100 | // If you're ever curious what variables the Event stores, type 'event.' 101 | // in Eclipse and it will bring up a menu of all the methods and variables. 102 | // Or go to the implementation by ctrl-clicking on the class name. 103 | 104 | /** 105 | * Step 4: Using Events in your custom classes 106 | */ 107 | /* 108 | Forge Events are all hooked into automatically from vanilla code, but say 109 | you made a custom Bow and want it to use ArrowNock and ArrowLoose events? 110 | You need to post them to the event bus in your item code. 111 | */ 112 | 113 | /** ArrowNockEvent should be placed in 'onItemRightClick' */ 114 | @Override 115 | public ItemStack onItemRightClick(ItemStack itemstack, World world, EntityPlayer player) 116 | { 117 | // Create the event and post it 118 | ArrowNockEvent event = new ArrowNockEvent(player, itemstack); 119 | MinecraftForge.EVENT_BUS.post(event); 120 | 121 | if (event.isCanceled()) 122 | { 123 | // you could do other stuff here as well 124 | return event.result; 125 | } 126 | 127 | player.setItemInUse(itemstack, this.getMaxItemUseDuration(itemstack)); 128 | 129 | return itemstack; 130 | } 131 | 132 | /** ArrowLooseEvent should be placed in 'onPlayerStoppedUsing' */ 133 | @Override 134 | public void onPlayerStoppedUsing(ItemStack itemstack, World world, EntityPlayer player, int par4) 135 | { 136 | // Ticks in use is max duration minus par4, which is equal to max duration - 1 for every tick in use 137 | int ticksInUse = this.getMaxItemUseDuration(itemstack) - par4; 138 | 139 | ArrowLooseEvent event = new ArrowLooseEvent(player, itemstack, ticksInUse); 140 | MinecraftForge.EVENT_BUS.post(event); 141 | 142 | if (event.isCanceled()) { return; } 143 | 144 | // ticksInUse might be modified by the Event in your EventHandler, so reassign it here: 145 | ticksInUse = event.charge; 146 | 147 | // Do whatever else you want with the itemstack like fire an arrow or cast a spell 148 | } 149 | 150 | /** 151 | * Step 5: Adding events to your EventHandler 152 | */ 153 | /* 154 | Look through MinecraftForge event types for ones you want to use and add them to your EventHandler by creating a new method with the appropriate Event as a parameter. 155 | 156 | 1.6.4: It must be prefaced by "@ForgeSubscribe" so that it gets called automatically at the right times. 157 | 1.7.2: It must be prefaced by "@SubscribeEvent" so that it gets called automatically at the right times. 158 | 159 | Do not, I repeat do NOT edit the event classes directly. Also, you do NOT need to make a class extending the Event class. 160 | 161 | The following is a template for whatever event method you want to make. Name it whatever you want, but use the correct Event Type from above. 162 | 163 | Event variables are accessed by using 'event.variableName' I give the variable names for many Events below. 164 | */ 165 | 166 | @ForgeSubscribe 167 | public void methodName(EventType event) 168 | { 169 | // do whatever you want here 170 | } 171 | 172 | /* 173 | * Advanced Information: Setting Priority 174 | */ 175 | /* 176 | Note that event priority works exactly the same in 1.7.2, other than the primary annotation changing to @SubscribeEvent. 177 | 178 | Priority is the order in which all listeners listening to a posted event are called. A listener is a method with the 179 | @ForgeSubscribe annotation, and is said to be actively listening if the class containing the method was registered to 180 | the MinecraftForge EVENT_BUS. Whenever an event is posted matching the parameters of the listener, the listener's 181 | method is called. 182 | 183 | When there are multiple listeners listening to a single event, the order in which they are called is important if one 184 | listener's functionality relies on having first or last access to the event in process, or if it relies on information 185 | set by a prior listener. This can be especially useful with Cancelable events. 186 | 187 | A single Event can be handled multiple times by different handlers or even within the same handler provided the methods 188 | have different names: 189 | */ 190 | @ForgeSubscribe 191 | public void onLivingHurt(LivingHurtEvent event) {} 192 | 193 | @ForgeSubscribe 194 | public void onPlayerHurt(LivingHurtEvent event) {} 195 | /* 196 | Both methods will be called each time a LivingHurtEvent is posted to the EVENT_BUS in the order they are added to 197 | the event listener (see IEventListener), which in the case above is simply their order in the code. The order can 198 | be controlled by appending (priority=VALUE) to the @ForgeSubscribe annotation, where VALUE is defined in the 199 | EventPriority enum class. HIGHEST priority is always called first, while LOWEST priority is called last. 200 | */ 201 | // this method will now be called after 'onPlayerHurt()' 202 | @ForgeSubscribe(priority=LOWEST) 203 | public void onLivingHurt(LivingHurtEvent event) {} 204 | 205 | @ForgeSubscribe(priority=HIGHEST) 206 | public void onPlayerHurt(LivingHurtEvent event) {} 207 | /* 208 | If two listeners have the same priority level, then the order is again controlled by the order in which they are added. 209 | In order to control the flow for such a case within a mod, the methods can be placed in separate 'event handler' 210 | classes to be registered in the order desired: 211 | */ 212 | // In the case of identical priority levels, PlayerHurtHandler will process first 213 | MinecraftForge.EVENT_BUS.register(new PlayerHurtHandler()); 214 | MinecraftForge.EVENT_BUS.register(new LivingHurtHandler()); 215 | // For multiple Mods that affect the same events, the order of mod registration would have the same effect. 216 | /* 217 | * Advanced Information: Cancelable Events 218 | */ 219 | /* 220 | Events with the @Cancelable annotation have the special quality of being cancelable. Once an event is canceled, 221 | subsequent listeners will not process the event unless provided with special annotation: 222 | */ 223 | @ForgeSubscribe // default priority, so it will be called first 224 | public void onLivingHurt(LivingHurtEvent event) { 225 | event.setCanceled(true); 226 | } 227 | 228 | @ForgeSubscribe(priority=LOWEST, receiveCanceled=true) 229 | public void onPlayerHurt(LivingHurtEvent event) { 230 | // un-cancel the event 231 | event.setCanceled(false); 232 | } 233 | /* 234 | By controlling the order in which each listener method is called, it is usually possible to avoid un-canceling a 235 | previously canceled event, although exceptional circumstances do arise; in those cases, extra care must be taken 236 | to avoid making logical errors. 237 | 238 | More will be added as I learn. Thanks to GotoLink for his excellent explanations regarding priority. 239 | */ 240 | 241 | /* 242 | Now for some examples of Event types, their variables, when they are called and what you might do with them, but 243 | first a word of warning: many of these events ONLY get called on one side or the other, so if something is not 244 | working as expected, check which side(s) the event is being called on and it may surprise you. 245 | 246 | An easy way to check is to put a line of debugging code at the beginning of each event method, so long as that event 247 | has access to some kind of entity: 248 | */ 249 | @ForgeSubscribe 250 | public void someEventMethod(SomeEvent event) { 251 | System.out.println("Some event called; is this the client side? " + event.entity.worldObj.isRemote); 252 | } 253 | /* 254 | Now on to the events! But first, a word of warning: many of these events ONLY get called on one side or the other, so if something is not working as expected, check which side(s) the event is being called on and it may surprise you. 255 | 256 | An easy way to check is to put a line of debugging code at the beginning of each event method, so long as that event has access to some kind of entity: 257 | */ 258 | @ForgeSubscribe 259 | public void someEventMethod(SomeEvent event) { 260 | System.out.println("Some event called; is this the client side? " + event.entity.worldObj.isRemote); 261 | } 262 | /* 263 | 264 | IMPORTANT: The following events are from 1.6.4; while many have not changed, some most certainly have. 265 | Always check the net.minecraftforge.event package for the available events, no matter what version 266 | of Minecraft you are modding for. 267 | 268 | 1. ArrowNockEvent 269 | Variables: EntityPlayer player, ItemStack result 270 | Usually called from 'onItemRightClick'. 271 | Uses: It is cancelable, so if some conditions are not met (e.g. no arrows in inventory) you could cancel it and stop 272 | the player from setting the item in use. One thing I use it for is to set a boolean 'isAiming', which I can then 273 | interrupt if the player takes damage or some such (using another boolean 'wasInterrupted') 274 | 275 | 2. ArrowLooseEvent 276 | Variables: EntityPlayer player, ItemStack bow, int charge 277 | Usually called from 'onPlayerStoppedUsing'. It is also cancelable. 278 | Uses: I use it in tandem with the above to check if the player was interrupted, and if so, cancel the event. 279 | 280 | 3. EntityConstructing 281 | Variables: Entity entity 282 | Called for every Entity when its constructor is called. 283 | Uses: Useful if you need to add ExtendedEntityProperties. 284 | 285 | 4. EntityJoinWorldEvent 286 | Variables: Entity entity, World world 287 | Called when an entity joins the world for the first time. 288 | Uses: Useful for synchronizing ExtendedEntityProperties, giving your player an item when spawned or any other number 289 | of things. 290 | 291 | 5. LivingUpdateEvent 292 | Variables: EntityLivingBase entity 293 | Called every tick at the beginning of the entity's onUpdate method. 294 | Uses: This is probably the most useful Event. You can allow player's to fly if holding an item or wearing your armor 295 | set, you can modify a player's fall speed here, add potion effects or anything else you can imagine. It's really 296 | really handy. 297 | 298 | 6. LivingDropsEvent 299 | Variables: EntityLivingBase entity, DamageSource source, ArrayList drops, int lootingLevel, boolean 300 | recentlyHit, int specialDropValue 301 | Called when an entity is killed and drops items. 302 | Uses: Handy if you want to modify a vanilla mobs drops or only drop your custom item if it was killed from your custom 303 | DamageSource. You can also remove items from drops, adjust it based on the looting enchantment level of the item used 304 | to kill it, etc. Pretty useful. 305 | 306 | 7. LivingFallEvent 307 | Variables: EntityLivingBase entity, float distance 308 | Called when the entity hits the ground after a fall, but before damage is calculated. 309 | SPECIAL NOTE: This event is NOT called while in Creative Mode; PlayerFlyableFallEvent is called instead 310 | Uses: It is cancelable, so 'event.setCanceled(true)' will preclude further processing of the fall. 311 | You can also modify the distance fallen here, but keep in mind this is ONLY on impact. If you want 312 | to modify fall distance only while certain conditions are met, better to do it in LivingUpdateEvent. 313 | Also, be sure you are modifying 'event.distance' and NOT 'entity.fallDistance' or you won't change the outcome of the 314 | fall. 315 | 316 | 8. LivingJumpEvent 317 | Variables: EntityLivingBase entity 318 | Called whenever entity jumps. 319 | Uses: Useful for entity.motionY += 10.0D. Just give it a try 320 | 321 | 9. LivingAttackEvent 322 | Variables: EntityLivingBase entity, DamageSource source, float ammount 323 | Called when an entity is attacked, but before any damage is applied 324 | Uses: Cancelable. Here you can do pre-processing of an attack before LivingHurtEvent is called. The source entity of 325 | the attack is stored in DamageSource, and you can adjust the damage to be dealt however you see fit. Basically the 326 | same uses as LivingHurtEvent, but done sooner. 327 | 328 | 10. LivingHurtEvent 329 | Variables: EntityLivingBase entity, DamageSource source, float ammount 330 | Called when an entity is damaged, but before any damage is applied 331 | Uses: Another super useful one if you have custom armor that reduces fire damage, increases damage taken from magic, 332 | do something if ammount is greater than current health or whatever. 333 | 334 | 11. LivingDeathEvent 335 | Variables: EntityLivingBase entity, DamageSource source 336 | Called when an entity dies; cancelable! 337 | Uses: Recall that DamageSource has lots of variables, too, such as getEntity() that returns the entity that caused the 338 | damage and thus that killed the current entity. All sorts of things you could do with that. This is also the place to 339 | cancel death and resurrect yourself, or set a timer for resurrection. If you have data you want to persist through 340 | player death, such as IExtendedEntityProperties, you can save that here as well. 341 | 342 | 12. EntityInteractEvent 343 | Variables: EntityPlayer player, Entity target 344 | Called when the player right-clicks on an entity, such as a cow 345 | Uses: Gee, you could do anything with this. One use could be getting milk into your custom bucket... 346 | 347 | 13. EntityItemPickupEvent 348 | Variables: EntityPlayer player, EntityItem item 349 | Called when the player picks up an item 350 | Uses: This one is useful for special items that need handling on pickup; an example would be if you made something 351 | similar to experience orbs, mana orbs for instance, that replenish mana rather than adding an item to inventory 352 | 353 | 14. HarvestCheck 354 | Variables: EntityPlayer player, Block block, boolean success 355 | Called when the player breaks a block before the block releases its drops 356 | Uses: Coupled with the BreakSpeed event, this is perhaps the best way to change the behavior of mining. 357 | */ 358 | /** 359 | * 1.7.2 TickEvents and Creating a TickHandler 360 | */ 361 | /* 362 | STOP!!! Chances are, you do NOT need to create a tick handler for whatever it is you are doing. There are many methods built-in to Minecraft that already act as tick handlers, and it is ALWAYS better to use them when you can. Why? Because they tick only when the object in question actually exists, whereas a generic tick handler processes every tick no matter what. Here are some of the pre-made tickers at your disposal: 363 | 364 | Entity#onUpdate: called every tick for each Entity; to manipulate vanilla entities, use LivingUpdateEvent 365 | TileEntity#onUpdate: called for tile entities every tick unless you tell it not to tick 366 | Item#onUpdate: called every tick while the specific item is in a player's inventory 367 | Item#onArmorTick: called only for armor each tick that it is equipped 368 | Block#updateTick: may be called randomly based on the block's tick rate, or it may be scheduled 369 | 370 | As you can see, nearly everything you could ever want to tick already has that capability. If what you want to do simply cannot be handled using one of the built-in tick methods, then and ONLY THEN should you consider creating a tick handler. Here's how to do so. 371 | 372 | In 1.7.2, as I'm sure many of you have noticed, the TickHandler class is gone, replaced by what appears to be a single TickEvent that we must now subscribe to in order to achieve the same functionality. Below, I will break it down and explain what exactly is going on and how to use it effectively. 373 | */ 374 | /** 375 | * Step 1: Determine Which TickEvent to Use 376 | */ 377 | /* 378 | There are actually five different subclasses of TickEvent, each called on a specific in-game tick and on a specific side or sides. It is very important to understand the difference between these events and to use the appropriate one: 379 | 380 | ServerTickEvent: called on the server side only 381 | ClientTickEvent: called on the client side only 382 | WorldTickEvent: both sides 383 | PlayerTickEvent: both sides 384 | RenderTickEvent: client side only and called each render tick 385 | 386 | When creating a TickHandler, be sure NOT to subscribe to the generic "TickEvent", as that will listen to every single tick type on every single tick, which is just wasting processing time. 387 | 388 | Once you decide which tick you need, then it is time to create a new TickHandler class. 389 | */ 390 | /** 391 | * Step 2: Create a TickHandler 392 | */ 393 | /* 394 | For now, we are just going to use the old name TickHandler, though we are really creating an event handler to listen to a specific tick. As an example, I will create a RenderTickEvent handler, since that is what I used in Zelda Sword Skills to make the spin attack motion smooth. 395 | */ 396 | // RenderTick is client side only, so we can place the SideOnly annotation here if we want: 397 | @SideOnly(Side.CLIENT) 398 | public class RenderTickHandler { 399 | // we only need one method here, and you can name it whatever you want, but 400 | // I like to name it according to the tick to which I am listening: 401 | @SubscribeEvent 402 | public void onRenderTick(RenderTickEvent event) { 403 | // now you can do whatever you want during each render tick, such as rotate the player's view 404 | } 405 | } 406 | /* 407 | If you need to do different things at the beginning of the tick than at the end of the tick, you can check the tick event's phase in your onWhateverTick method; this is applicable for all TickEvents. 408 | */ 409 | if (event.phase == Phase.START) { 410 | // this is the equivalent of tickStart 411 | } 412 | if (event.phase == Phase.END) { 413 | // and this is the equivalent of tickEnd 414 | } 415 | 416 | // if you have a lot of stuff going on in each of those, you could separate them into separate methods: 417 | @SubscribeEvent 418 | public void onRenderTick(RenderTickEvent event) { 419 | if (event.phase == Phase.START) { 420 | onTickStart(); 421 | } else { 422 | onTickEnd(); 423 | } 424 | } 425 | /* 426 | One other thing to mention here is that since we are on the client side, we might be using Minecraft.getMinecraft() quite a lot to access things like the world, player, etc. Since it would be very wasteful to have to do this each and every tick, a better way is to store the instance of Minecraft when you construct your tick handler: 427 | */ 428 | @SideOnly(Side.CLIENT) 429 | public class RenderTickHandler { 430 | /** Stores an instance of Minecraft for easy access */ 431 | private Minecraft mc; 432 | 433 | // create a constructor that takes a Minecraft argument; now we have it whenever we need it 434 | public RenderTickHandler(Minecraft mc) { 435 | this.mc = mc; 436 | } 437 | 438 | @SubscribeEvent 439 | public void onRenderTick(RenderTickEvent event) { 440 | if (event.phase == Phase.START) { 441 | // we will make the player spin constantly in circles: 442 | mc.thePlayer.rotationYaw += 10.0F; 443 | } 444 | } 445 | } 446 | 447 | /** 448 | * Step 3: Registering Your TickHandler 449 | */ 450 | /* 451 | All TickEvents need to be registered to the FMLCommonHandler event bus, NOT the MinecraftForge EVENT_BUS. For our client-sided render tick handler, we will do this in the ClientProxy, because we will crash the game if we try to register it in a method that is run on the server. 452 | */ 453 | public class CommonProxy { 454 | /** 455 | * We will call this method from our main mod class' FMLPreInitializationEvent method 456 | */ 457 | public void initialize() { 458 | // since we are not registering a tick handler that ticks on the server, we will not put anything here for now 459 | // but if you had a WorldTickEvent or PlayerTickEvent, for example, this is where you should register it 460 | // if you try to register the RenderTickHandler here, your game WILL crash 461 | } 462 | } 463 | 464 | public class ClientProxy extends CommonProxy { 465 | // Our ClientProxy method only gets run on the client side, so it is safe to register our RenderTickHandler here 466 | @Override 467 | public void initialize() { 468 | // calling super will register any 2-sided tick handlers you have that are registered in the CommonProxy 469 | // this is important since the CommonProxy will only register it on the server side, and you will need it 470 | // registered on the client as well; however, we do not have any at this point 471 | super.initialize(); 472 | 473 | // here we register our RenderTickHandler - be sure to pass in the instance of Minecraft! 474 | FMLCommonHandler.instance().bus().register(new RenderTickHandler(Minecraft.getMinecraft())); 475 | 476 | // this is also an ideal place to register things like KeyBindings 477 | } 478 | } 479 | /* 480 | That should be all you need to use TickEvents successfully and efficiently. Remember, NEVER subscribe to TickEvent, ONLY subscribe to the specific type of tick event that you really need, just as you would never subscribe to Event... 481 | 482 | Good luck with 1.7.2! 483 | */ -------------------------------------------------------------------------------- /InventoryItemTutorial.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Creating an Item that stores an Inventory (such as a Backpack) 3 | */ 4 | /* 5 | I'm back with a new tutorial on how to create an Item that can store an Inventory, 6 | such as a backpack. This time I've commented pretty thoroughly within the code itself, 7 | so I'll let it do most of the talking. 8 | 9 | While the exact code used will change from version to version (of both Minecraft and 10 | Forge), the concepts described remain the same. 11 | 12 | Note that this is not a tutorial on how to set up your first mod or create your first Item. If 13 | you have never done either of those before, you should start with a more basic tutorial first. 14 | 15 | If, on the other hand, you already have experience working with GUIs and Containers, you 16 | probably won't have any trouble following along. 17 | 18 | I do highly suggest reading the tutorial on Item NBT, however, as it will greatly aid 19 | in understanding what's to come. Find it here: http://www.minecraftforge.net/wiki/Item_nbt 20 | 21 | NOTE: If you want to open your inventory with the keyboard, check out this tutorial on key binding: 22 | http://www.minecraftforum.net/topic/1798625-162sobiohazardouss-forge-keybinding-tutorial/ 23 | 24 | Now on to the tutorial! 25 | */ 26 | 27 | /** 28 | * Step 1: Set up your main mod space, common proxy and client proxy 29 | */ 30 | /* 31 | * MAIN MOD DECLARATION 32 | */ 33 | package coolalias.inventoryitem; 34 | 35 | @Mod(modid = "inventoryitemmod", name = "Inventory Item Tutorial", version = "1.0.0") 36 | // 1.6.4. only: @NetworkMod(clientSideRequired=true, serverSideRequired=false) 37 | public final class InventoryItemMain 38 | { 39 | @Instance("inventoryitemmod") 40 | public static InventoryItemMain instance; 41 | 42 | @SidedProxy(clientSide = "coolalias.inventoryitem.ClientProxy", serverSide = "coolalias.inventoryitem.CommonProxy") 43 | public static CommonProxy proxy; 44 | 45 | /** This is used to keep track of GUIs that we make*/ 46 | private static int modGuiIndex = 0; 47 | 48 | /** Set our custom inventory Gui index to the next available Gui index */ 49 | public static final int GUI_ITEM_INV = modGuiIndex++; 50 | 51 | // ITEMS ETC. 52 | public static Item itemstore; 53 | 54 | @EventHandler 55 | public void preInit(FMLPreInitializationEvent event) 56 | { 57 | itemstore = new ItemStore().setUnlocalizedName("item_store").setCreativeTab(CreativeTabs.tabMisc); 58 | GameRegistry.registerItem(itemstore, "item_store"); 59 | } 60 | 61 | @EventHandler 62 | public void load(FMLInitializationEvent event) 63 | { 64 | // no renderers or entities to register, but whatever 65 | proxy.registerRenderers(); 66 | // register CommonProxy as our GuiHandler 67 | NetworkRegistry.instance().registerGuiHandler(this, new CommonProxy()); 68 | } 69 | 70 | @EventHandler 71 | public void postInit(FMLPostInitializationEvent event) 72 | { 73 | } 74 | } 75 | /* 76 | * COMMON PROXY CLASS 77 | */ 78 | public class CommonProxy implements IGuiHandler 79 | { 80 | public void registerRenderers() {} 81 | 82 | @Override 83 | public Object getServerGuiElement(int guiId, EntityPlayer player, World world, int x, int y, int z) 84 | { 85 | // Hooray, no 'magic' numbers - we know exactly which Gui this refers to 86 | if (guiId == InventoryItemMain.GUI_ITEM_INV) 87 | { 88 | // Use the player's held item to create the inventory 89 | return new ContainerItem(player, player.inventory, new InventoryItem(player.getHeldItem())); 90 | } 91 | return null; 92 | } 93 | 94 | @Override 95 | public Object getClientGuiElement(int guiId, EntityPlayer player, World world, int x, int y, int z) 96 | { 97 | if (guiId == InventoryItemMain.GUI_ITEM_INV) 98 | { 99 | // We have to cast the new container as our custom class 100 | // and pass in currently held item for the inventory 101 | return new GuiItemInventory((ContainerItem) new ContainerItem(player, player.inventory, new InventoryItem(player.getHeldItem()))); 102 | } 103 | return null; 104 | } 105 | } 106 | 107 | /* 108 | * CLIENT PROXY CLASS 109 | */ 110 | public class ClientProxy extends CommonProxy 111 | { 112 | @Override 113 | public void registerRenderers() {} 114 | } 115 | 116 | /** 117 | * Step 2: Create a custom Inventory class 118 | */ 119 | /* 120 | Create your new class and implement IInventory - it will automatically create all 121 | the methods you need, but you need to fill them in with code. We make our constructor 122 | take an ItemStack argument so we can get and / or set the NBT Tag Compound. This is 123 | how we can save our inventory within an "Item" - by using its enclosing ItemStack. 124 | */ 125 | public class InventoryItem implements IInventory 126 | { 127 | private String name = "Inventory Item"; 128 | 129 | /** Provides NBT Tag Compound to reference */ 130 | private final ItemStack invItem; 131 | 132 | /** Defining your inventory size this way is handy */ 133 | public static final int INV_SIZE = 8; 134 | 135 | /** Inventory's size must be same as number of slots you add to the Container class */ 136 | private ItemStack[] inventory = new ItemStack[INV_SIZE]; 137 | 138 | /** 139 | * @param itemstack - the ItemStack to which this inventory belongs 140 | */ 141 | public InventoryItem(ItemStack stack) 142 | { 143 | invItem = stack; 144 | 145 | // Create a new NBT Tag Compound if one doesn't already exist, or you will crash 146 | if (!stack.hasTagCompound()) { 147 | stack.setTagCompound(new NBTTagCompound()); 148 | } 149 | // note that it's okay to use stack instead of invItem right there 150 | // both reference the same memory location, so whatever you change using 151 | // either reference will change in the other 152 | 153 | // Read the inventory contents from NBT 154 | readFromNBT(stack.getTagCompound()); 155 | } 156 | 157 | @Override 158 | public int getSizeInventory() 159 | { 160 | return inventory.length; 161 | } 162 | 163 | @Override 164 | public ItemStack getStackInSlot(int slot) 165 | { 166 | return inventory[slot]; 167 | } 168 | 169 | @Override 170 | public ItemStack decrStackSize(int slot, int amount) 171 | { 172 | ItemStack stack = getStackInSlot(slot); 173 | if(stack != null) 174 | { 175 | if(stack.stackSize > amount) 176 | { 177 | stack = stack.splitStack(amount); 178 | // Don't forget this line or your inventory will not be saved! 179 | onInventoryChanged(); 180 | } 181 | else 182 | { 183 | // this method also calls onInventoryChanged, so we don't need to call it again 184 | setInventorySlotContents(slot, null); 185 | } 186 | } 187 | return stack; 188 | } 189 | 190 | @Override 191 | public ItemStack getStackInSlotOnClosing(int slot) 192 | { 193 | ItemStack stack = getStackInSlot(slot); 194 | setInventorySlotContents(slot, null); 195 | return stack; 196 | } 197 | 198 | @Override 199 | public void setInventorySlotContents(int slot, ItemStack stack) 200 | { 201 | inventory[slot] = itemstack; 202 | 203 | if (stack != null && stack.stackSize > getInventoryStackLimit()) 204 | { 205 | stack.stackSize = getInventoryStackLimit(); 206 | } 207 | 208 | // Don't forget this line or your inventory will not be saved! 209 | onInventoryChanged(); 210 | } 211 | 212 | // 1.7.2+ renamed to getInventoryName 213 | @Override 214 | public String getInvName() 215 | { 216 | return name; 217 | } 218 | 219 | // 1.7.2+ renamed to hasCustomInventoryName 220 | @Override 221 | public boolean isInvNameLocalized() 222 | { 223 | return name.length() > 0; 224 | } 225 | 226 | @Override 227 | public int getInventoryStackLimit() 228 | { 229 | return 64; 230 | } 231 | 232 | /** 233 | * This is the method that will handle saving the inventory contents, as it is called (or should be called!) 234 | * anytime the inventory changes. Perfect. Much better than using onUpdate in an Item, as this will also 235 | * let you change things in your inventory without ever opening a Gui, if you want. 236 | */ 237 | // 1.7.2+ renamed to markDirty 238 | @Override 239 | public void onInventoryChanged() 240 | { 241 | for (int i = 0; i < getSizeInventory(); ++i) 242 | { 243 | if (getStackInSlot(i) != null && getStackInSlot(i).stackSize == 0) { 244 | inventory[i] = null; 245 | } 246 | } 247 | 248 | // This line here does the work: 249 | writeToNBT(invItem.getTagCompound()); 250 | } 251 | 252 | @Override 253 | public boolean isUseableByPlayer(EntityPlayer entityplayer) 254 | { 255 | return true; 256 | } 257 | 258 | // 1.7.2+ renamed to openInventory(EntityPlayer player) 259 | @Override 260 | public void openChest() {} 261 | 262 | // 1.7.2+ renamed to closeInventory(EntityPlayer player) 263 | @Override 264 | public void closeChest() {} 265 | 266 | /** 267 | * This method doesn't seem to do what it claims to do, as 268 | * items can still be left-clicked and placed in the inventory 269 | * even when this returns false 270 | */ 271 | @Override 272 | public boolean isItemValidForSlot(int slot, ItemStack itemstack) 273 | { 274 | // Don't want to be able to store the inventory item within itself 275 | // Bad things will happen, like losing your inventory 276 | // Actually, this needs a custom Slot to work 277 | return !(itemstack.getItem() instanceof ItemStore); 278 | } 279 | 280 | /** 281 | * A custom method to read our inventory from an ItemStack's NBT compound 282 | */ 283 | public void readFromNBT(NBTTagCompound compound) 284 | { 285 | // Gets the custom taglist we wrote to this compound, if any 286 | // 1.7.2+ change to compound.getTagList("ItemInventory", Constants.NBT.TAG_COMPOUND); 287 | NBTTagList items = compound.getTagList("ItemInventory"); 288 | 289 | for (int i = 0; i < items.tagCount(); ++i) 290 | { 291 | // 1.7.2+ change to items.getCompoundTagAt(i) 292 | NBTTagCompound item = (NBTTagCompound) items.tagAt(i); 293 | int slot = item.getInteger("Slot"); 294 | 295 | // Just double-checking that the saved slot index is within our inventory array bounds 296 | if (slot >= 0 && slot < getSizeInventory()) { 297 | inventory[slot] = ItemStack.loadItemStackFromNBT(item); 298 | } 299 | } 300 | } 301 | 302 | /** 303 | * A custom method to write our inventory to an ItemStack's NBT compound 304 | */ 305 | public void writeToNBT(NBTTagCompound tagcompound) 306 | { 307 | // Create a new NBT Tag List to store itemstacks as NBT Tags 308 | NBTTagList items = new NBTTagList(); 309 | 310 | for (int i = 0; i < getSizeInventory(); ++i) 311 | { 312 | // Only write stacks that contain items 313 | if (getStackInSlot(i) != null) 314 | { 315 | // Make a new NBT Tag Compound to write the itemstack and slot index to 316 | NBTTagCompound item = new NBTTagCompound(); 317 | item.setInteger("Slot", i); 318 | // Writes the itemstack in slot(i) to the Tag Compound we just made 319 | getStackInSlot(i).writeToNBT(item); 320 | 321 | // add the tag compound to our tag list 322 | items.appendTag(nbttagcompound1); 323 | } 324 | } 325 | // Add the TagList to the ItemStack's Tag Compound with the name "ItemInventory" 326 | tagcompound.setTag("ItemInventory", items); 327 | } 328 | } 329 | /* 330 | If you want to be able to place your inventory-holding Item within another 331 | instance of itself, you'll need to have a way to distinguish between each 332 | instance of the item so you can check to make sure you're not placing the item 333 | within itself. What you'll need to do is assign a UUID to a String variable 334 | within your Inventory class for every ItemStack that holds an instance of your 335 | Item, like so: 336 | */ 337 | // declaration of variable: 338 | protected String uniqueID; 339 | 340 | /** initialize variable within the constructor: */ 341 | uniqueID = ""; 342 | 343 | if (!itemstack.hasTagCompound()) 344 | { 345 | itemstack.setTagCompound(new NBTTagCompound()); 346 | // no tag compound means the itemstack does not yet have a UUID, so assign one: 347 | uniqueID = UUID.randomUUID().toString(); 348 | } 349 | 350 | /** When reading from NBT: */ 351 | if ("".equals(uniqueID)) 352 | { 353 | // try to read unique ID from NBT 354 | uniqueID = tagcompound.getString("uniqueID"); 355 | // if it's still "", assign a new one: 356 | if ("".equals(uniqueID)) 357 | { 358 | uniqueID = UUID.randomUUID().toString(); 359 | } 360 | } 361 | 362 | /** Writing to NBT: */ 363 | // just add this line: 364 | tagcompound.setString("uniqueID", this.uniqueID); 365 | /* 366 | Finally, in your Container class, you will need to check if the currently opened 367 | inventory's uniqueID is equal to the itemstack's uniqueID in the method 368 | 'transferStackInSlot' as well as check if the itemstack is the currently equipped 369 | item in the method 'slotClick'. In both cases, you'll need to prevent the itemstack 370 | from being moved or it will cause bad things to happen. 371 | */ 372 | 373 | /** 374 | * Step 3: Create a custom Container for your Inventory 375 | */ 376 | /* 377 | There's a LOT of code in this one, but read through all of the comments carefully 378 | and it should become clear what everything does. 379 | 380 | As a bonus, one of my previous tutorials is included within! 381 | "How to Properly Override Shift-Clicking" is here and better than ever! 382 | At least in my opinion. 383 | 384 | If you're like me, and you find no end of frustration trying to figure out which 385 | f-ing index you should use for which slots in your container when overriding 386 | transferStackInSlot, or if your following the original tutorial, then read on. 387 | */ 388 | public class ContainerItem extends Container 389 | { 390 | /** The Item Inventory for this Container, only needed if you want to reference isUseableByPlayer */ 391 | private final InventoryItem inventory; 392 | 393 | /** Using these will make transferStackInSlot easier to understand and implement 394 | * INV_START is the index of the first slot in the Player's Inventory, so our 395 | * InventoryItem's number of slots (e.g. 5 slots is array indices 0-4, so start at 5) 396 | * Notice how we don't have to remember how many slots we made? We can just use 397 | * InventoryItem.INV_SIZE and if we ever change it, the Container updates automatically. */ 398 | private static final int INV_START = InventoryItem.INV_SIZE, INV_END = INV_START+26, 399 | HOTBAR_START = INV_END+1, HOTBAR_END = HOTBAR_START+8; 400 | 401 | // If you're planning to add armor slots, put those first like this: 402 | // ARMOR_START = InventoryItem.INV_SIZE, ARMOR_END = ARMOR_START+3, 403 | // INV_START = ARMOR_END+1, and then carry on like above. 404 | 405 | public ContainerItem(EntityPlayer par1Player, InventoryPlayer inventoryPlayer, InventoryItem inventoryItem) 406 | { 407 | this.inventory = inventoryItem; 408 | 409 | int i; 410 | 411 | // ITEM INVENTORY - you'll need to adjust the slot locations to match your texture file 412 | // I have them set vertically in columns of 4 to the right of the player model 413 | for (i = 0; i < InventoryItem.INV_SIZE; ++i) 414 | { 415 | // You can make a custom Slot if you need different behavior, 416 | // such as only certain item types can be put into this slot 417 | // We made a custom slot to prevent our inventory-storing item 418 | // from being stored within itself, but if you want to allow that and 419 | // you followed my advice at the end of the above step, then you 420 | // could get away with using the vanilla Slot class 421 | this.addSlotToContainer(new SlotItemInv(this.inventory, i, 80 + (18 * (int)(i/4)), 8 + (18*(i%4)))); 422 | } 423 | 424 | // If you want, you can add ARMOR SLOTS here as well, but you need to 425 | // make a public version of SlotArmor. I won't be doing that in this tutorial. 426 | /* 427 | for (i = 0; i < 4; ++i) 428 | { 429 | // These are the standard positions for survival inventory layout 430 | this.addSlotToContainer(new SlotArmor(this.player, inventoryPlayer, inventoryPlayer.getSizeInventory() - 1 - i, 8, 8 + i * 18, i)); 431 | } 432 | */ 433 | 434 | // PLAYER INVENTORY - uses default locations for standard inventory texture file 435 | for (i = 0; i < 3; ++i) 436 | { 437 | for (int j = 0; j < 9; ++j) 438 | { 439 | this.addSlotToContainer(new Slot(inventoryPlayer, j + i * 9 + 9, 8 + j * 18, 84 + i * 18)); 440 | } 441 | } 442 | 443 | // PLAYER ACTION BAR - uses default locations for standard action bar texture file 444 | for (i = 0; i < 9; ++i) 445 | { 446 | this.addSlotToContainer(new Slot(inventoryPlayer, i, 8 + i * 18, 142)); 447 | } 448 | } 449 | 450 | @Override 451 | public boolean canInteractWith(EntityPlayer entityplayer) 452 | { 453 | // be sure to return the inventory's isUseableByPlayer method 454 | // if you defined special behavior there: 455 | return inventory.isUseableByPlayer(player); 456 | } 457 | 458 | /** 459 | * Called when a player shift-clicks on a slot. You must override this or you will crash when someone does that. 460 | */ 461 | public ItemStack transferStackInSlot(EntityPlayer par1EntityPlayer, int index) 462 | { 463 | ItemStack itemstack = null; 464 | Slot slot = (Slot) this.inventorySlots.get(index); 465 | 466 | if (slot != null && slot.getHasStack()) 467 | { 468 | ItemStack itemstack1 = slot.getStack(); 469 | itemstack = itemstack1.copy(); 470 | 471 | // If item is in our custom Inventory or armor slot 472 | if (index < INV_START) 473 | { 474 | // try to place in player inventory / action bar 475 | if (!this.mergeItemStack(itemstack1, INV_START, HOTBAR_END+1, true)) 476 | { 477 | return null; 478 | } 479 | 480 | slot.onSlotChange(itemstack1, itemstack); 481 | } 482 | // Item is in inventory / hotbar, try to place in custom inventory or armor slots 483 | else 484 | { 485 | /* 486 | If your inventory only stores certain instances of Items, 487 | you can implement shift-clicking to your inventory like this: 488 | 489 | // Check that the item is the right type 490 | if (itemstack1.getItem() instanceof ItemCustom) 491 | { 492 | // Try to merge into your custom inventory slots 493 | // We use 'InventoryItem.INV_SIZE' instead of INV_START just in case 494 | // you also add armor or other custom slots 495 | if (!this.mergeItemStack(itemstack1, 0, InventoryItem.INV_SIZE, false)) 496 | { 497 | return null; 498 | } 499 | } 500 | // If you added armor slots, check them here as well: 501 | // Item being shift-clicked is armor - try to put in armor slot 502 | if (itemstack1.getItem() instanceof ItemArmor) 503 | { 504 | int type = ((ItemArmor) itemstack1.getItem()).armorType; 505 | if (!this.mergeItemStack(itemstack1, ARMOR_START + type, ARMOR_START + type + 1, false)) 506 | { 507 | return null; 508 | } 509 | } 510 | Otherwise, you have basically 2 choices: 511 | 1. shift-clicking between player inventory and custom inventory 512 | 2. shift-clicking between action bar and inventory 513 | 514 | Be sure to choose only ONE of the following implementations!!! 515 | */ 516 | /** 517 | * Implementation number 1: Shift-click into your custom inventory 518 | */ 519 | if (index >= INV_START) 520 | { 521 | // place in custom inventory 522 | if (!this.mergeItemStack(itemstack1, 0, INV_START, false)) 523 | { 524 | return null; 525 | } 526 | } 527 | 528 | /** 529 | * Implementation number 2: Shift-click items between action bar and inventory 530 | */ 531 | // item is in player's inventory, but not in action bar 532 | if (index >= INV_START && index < HOTBAR_START) 533 | { 534 | // place in action bar 535 | if (!this.mergeItemStack(itemstack1, HOTBAR_START, HOTBAR_END+1, false)) 536 | { 537 | return null; 538 | } 539 | } 540 | // item in action bar - place in player inventory 541 | else if (index >= HOTBAR_START && index < HOTBAR_END+1) 542 | { 543 | if (!this.mergeItemStack(itemstack1, INV_START, INV_END+1, false)) 544 | { 545 | return null; 546 | } 547 | } 548 | } 549 | 550 | if (itemstack1.stackSize == 0) 551 | { 552 | slot.putStack((ItemStack) null); 553 | } 554 | else 555 | { 556 | slot.onSlotChanged(); 557 | } 558 | 559 | if (itemstack1.stackSize == itemstack.stackSize) 560 | { 561 | return null; 562 | } 563 | 564 | slot.onPickupFromSlot(par1EntityPlayer, itemstack1); 565 | } 566 | 567 | return itemstack; 568 | } 569 | 570 | /** 571 | * You should override this method to prevent the player from moving the stack that 572 | * opened the inventory, otherwise if the player moves it, the inventory will not 573 | * be able to save properly 574 | */ 575 | @Override 576 | public ItemStack slotClick(int slot, int button, int flag, EntityPlayer player) { 577 | // this will prevent the player from interacting with the item that opened the inventory: 578 | if (slot >= 0 && getSlot(slot) != null && getSlot(slot).getStack() == player.getHeldItem()) { 579 | return null; 580 | } 581 | return super.slotClick(slot, button, flag, player); 582 | } 583 | } 584 | 585 | /* 586 | Special note: If your custom inventory's stack limit is 1 and you allow shift-clicking itemstacks into it, 587 | you will need to override mergeStackInSlot to avoid losing all the items but one in a stack when you shift-click. 588 | */ 589 | /** 590 | * Vanilla mergeItemStack method doesn't correctly handle inventories whose 591 | * max stack size is 1 when you shift-click into the inventory. 592 | * This is a modified method I wrote to handle such cases. 593 | * Note you only need it if your slot / inventory's max stack size is 1 594 | */ 595 | @Override 596 | protected boolean mergeItemStack(ItemStack stack, int start, int end, boolean backwards) 597 | { 598 | boolean flag1 = false; 599 | int k = (backwards ? end - 1 : start); 600 | Slot slot; 601 | ItemStack itemstack1; 602 | 603 | if (stack.isStackable()) 604 | { 605 | while (stack.stackSize > 0 && (!backwards && k < end || backwards && k >= start)) 606 | { 607 | slot = (Slot) inventorySlots.get(k); 608 | itemstack1 = slot.getStack(); 609 | 610 | if (!slot.isItemValid(stack)) { 611 | k += (backwards ? -1 : 1); 612 | continue; 613 | } 614 | 615 | if (itemstack1 != null && itemstack1.getItem() == stack.getItem() && 616 | (!stack.getHasSubtypes() || stack.getItemDamage() == itemstack1.getItemDamage()) && ItemStack.areItemStackTagsEqual(stack, itemstack1)) 617 | { 618 | int l = itemstack1.stackSize + stack.stackSize; 619 | 620 | if (l <= stack.getMaxStackSize() && l <= slot.getSlotStackLimit()) { 621 | stack.stackSize = 0; 622 | itemstack1.stackSize = l; 623 | inventory.onInventoryChanged(); 624 | flag1 = true; 625 | } else if (itemstack1.stackSize < stack.getMaxStackSize() && l < slot.getSlotStackLimit()) { 626 | stack.stackSize -= stack.getMaxStackSize() - itemstack1.stackSize; 627 | itemstack1.stackSize = stack.getMaxStackSize(); 628 | inventory.onInventoryChanged(); 629 | flag1 = true; 630 | } 631 | } 632 | 633 | k += (backwards ? -1 : 1); 634 | } 635 | } 636 | if (stack.stackSize > 0) 637 | { 638 | k = (backwards ? end - 1 : start); 639 | while (!backwards && k < end || backwards && k >= start) { 640 | slot = (Slot) inventorySlots.get(k); 641 | itemstack1 = slot.getStack(); 642 | 643 | if (!slot.isItemValid(stack)) { 644 | k += (backwards ? -1 : 1); 645 | continue; 646 | } 647 | 648 | if (itemstack1 == null) { 649 | int l = stack.stackSize; 650 | if (l <= slot.getSlotStackLimit()) { 651 | slot.putStack(stack.copy()); 652 | stack.stackSize = 0; 653 | inventory.onInventoryChanged(); 654 | flag1 = true; 655 | break; 656 | } else { 657 | putStackInSlot(k, new ItemStack(stack.getItem(), slot.getSlotStackLimit(), stack.getItemDamage())); 658 | stack.stackSize -= slot.getSlotStackLimit(); 659 | inventory.onInventoryChanged(); 660 | flag1 = true; 661 | } 662 | } 663 | 664 | k += (backwards ? -1 : 1); 665 | } 666 | } 667 | 668 | return flag1; 669 | } 670 | /* 671 | Making the custom slot is very simple, if you're going that route: 672 | */ 673 | public class SlotItemInv extends Slot 674 | { 675 | public SlotItemInv(IInventory inv, int index, int xPos, int yPos) 676 | { 677 | super(inv, index, xPos, yPos); 678 | } 679 | 680 | // This is the only method we need to override so that 681 | // we can't place our inventory-storing Item within 682 | // its own inventory (thus making it permanently inaccessible) 683 | // as well as preventing abuse of storing backpacks within backpacks 684 | /** 685 | * Check if the stack is a valid item for this slot. 686 | */ 687 | @Override 688 | public boolean isItemValid(ItemStack itemstack) 689 | { 690 | // Everything returns true except an instance of our Item 691 | return !(itemstack.getItem() instanceof ItemStore); 692 | } 693 | } 694 | 695 | /** 696 | * Step 4: Create the GUI for our custom Inventory 697 | */ 698 | 699 | /* 700 | There's not much to this, mostly just copy and paste from vanilla classes. 701 | */ 702 | public class GuiItemInventory extends GuiContainer 703 | { 704 | /** x and y size of the inventory window in pixels. Defined as float, passed as int 705 | * These are used for drawing the player model. */ 706 | private float xSize_lo; 707 | private float ySize_lo; 708 | 709 | /** ResourceLocation takes 2 parameters: ModId, path to texture at the location: 710 | * "src/minecraft/assets/modid/" 711 | * 712 | * I have provided a sample texture file that works with this tutorial. Download it 713 | * from Forge_Tutorials/textures/gui/ 714 | */ 715 | private static final ResourceLocation iconLocation = new ResourceLocation("inventoryitemmod", "textures/gui/inventoryitem.png"); 716 | 717 | /** The inventory to render on screen */ 718 | private final InventoryItem inventory; 719 | 720 | public GuiItemInventory(ContainerItem containerItem) 721 | { 722 | super(containerItem); 723 | this.inventory = containerItem.inventory; 724 | } 725 | 726 | /** 727 | * Draws the screen and all the components in it. 728 | */ 729 | public void drawScreen(int par1, int par2, float par3) 730 | { 731 | super.drawScreen(par1, par2, par3); 732 | this.xSize_lo = (float)par1; 733 | this.ySize_lo = (float)par2; 734 | } 735 | 736 | /** 737 | * Draw the foreground layer for the GuiContainer (everything in front of the items) 738 | */ 739 | protected void drawGuiContainerForegroundLayer(int par1, int par2) 740 | { 741 | String s = this.inventory.isInvNameLocalized() ? this.inventory.getInvName() : I18n.getString(this.inventory.getInvName()); 742 | this.fontRenderer.drawString(s, this.xSize / 2 - this.fontRenderer.getStringWidth(s) / 2, 0, 4210752); 743 | this.fontRenderer.drawString(I18n.getString("container.inventory"), 26, this.ySize - 96 + 4, 4210752); 744 | } 745 | 746 | /** 747 | * Draw the background layer for the GuiContainer (everything behind the items) 748 | */ 749 | protected void drawGuiContainerBackgroundLayer(float par1, int par2, int par3) 750 | { 751 | GL11.glColor4f(1.0F, 1.0F, 1.0F, 1.0F); 752 | this.mc.getTextureManager().bindTexture(iconLocation); 753 | int k = (this.width - this.xSize) / 2; 754 | int l = (this.height - this.ySize) / 2; 755 | this.drawTexturedModalRect(k, l, 0, 0, this.xSize, this.ySize); 756 | int i1; 757 | drawPlayerModel(k + 51, l + 75, 30, (float)(k + 51) - this.xSize_lo, (float)(l + 75 - 50) - this.ySize_lo, this.mc.thePlayer); 758 | } 759 | 760 | /** 761 | * This renders the player model in standard inventory position (in later versions of Minecraft / Forge, you can 762 | * simply call GuiInventory.drawEntityOnScreen directly instead of copying this code) 763 | */ 764 | public static void drawPlayerModel(int x, int y, int scale, float yaw, float pitch, EntityLivingBase entity) { 765 | GL11.glEnable(GL11.GL_COLOR_MATERIAL); 766 | GL11.glPushMatrix(); 767 | GL11.glTranslatef(x, y, 50.0F); 768 | GL11.glScalef(-scale, scale, scale); 769 | GL11.glRotatef(180.0F, 0.0F, 0.0F, 1.0F); 770 | float f2 = entity.renderYawOffset; 771 | float f3 = entity.rotationYaw; 772 | float f4 = entity.rotationPitch; 773 | float f5 = entity.prevRotationYawHead; 774 | float f6 = entity.rotationYawHead; 775 | GL11.glRotatef(135.0F, 0.0F, 1.0F, 0.0F); 776 | RenderHelper.enableStandardItemLighting(); 777 | GL11.glRotatef(-135.0F, 0.0F, 1.0F, 0.0F); 778 | GL11.glRotatef(-((float) Math.atan(pitch / 40.0F)) * 20.0F, 1.0F, 0.0F, 0.0F); 779 | entity.renderYawOffset = (float) Math.atan(yaw / 40.0F) * 20.0F; 780 | entity.rotationYaw = (float) Math.atan(yaw / 40.0F) * 40.0F; 781 | entity.rotationPitch = -((float) Math.atan(pitch / 40.0F)) * 20.0F; 782 | entity.rotationYawHead = entity.rotationYaw; 783 | entity.prevRotationYawHead = entity.rotationYaw; 784 | GL11.glTranslatef(0.0F, entity.yOffset, 0.0F); 785 | RenderManager.instance.playerViewY = 180.0F; 786 | RenderManager.instance.renderEntityWithPosYaw(entity, 0.0D, 0.0D, 0.0D, 0.0F, 1.0F); 787 | entity.renderYawOffset = f2; 788 | entity.rotationYaw = f3; 789 | entity.rotationPitch = f4; 790 | entity.prevRotationYawHead = f5; 791 | entity.rotationYawHead = f6; 792 | GL11.glPopMatrix(); 793 | RenderHelper.disableStandardItemLighting(); 794 | GL11.glDisable(GL12.GL_RESCALE_NORMAL); 795 | OpenGlHelper.setActiveTexture(OpenGlHelper.lightmapTexUnit); 796 | GL11.glDisable(GL11.GL_TEXTURE_2D); 797 | OpenGlHelper.setActiveTexture(OpenGlHelper.defaultTexUnit); 798 | } 799 | } 800 | 801 | /** 802 | * Step 5: Finally, create your custom Item class that will open the Inventory 803 | */ 804 | /* 805 | Nice to end on an easy one. The item only needs to open the inventory gui. 806 | method. I named it ItemStore to more clearly distinguish it from the rest of my 807 | classes. Not very creative, I know, but sufficient for a tutorial. 808 | */ 809 | public class ItemStore extends Item 810 | { 811 | public ItemStore(int par1) 812 | { 813 | super(par1); 814 | // ItemStacks that store an NBT Tag Compound are limited to stack size of 1 815 | setMaxStackSize(1); 816 | // you'll want to set a creative tab as well, so you can get your item 817 | setCreativeTab(CreativeTabs.tabMisc); 818 | } 819 | 820 | // Without this method, your inventory will NOT work!!! 821 | @Override 822 | public int getMaxItemUseDuration(ItemStack stack) { 823 | return 1; // return any value greater than zero 824 | } 825 | 826 | @Override 827 | public ItemStack onItemRightClick(ItemStack itemstack, World world, EntityPlayer player) 828 | { 829 | if (!world.isRemote) 830 | { 831 | // If player not sneaking, open the inventory gui 832 | if (!player.isSneaking()) { 833 | player.openGui(InventoryItemMain.instance, InventoryItemMain.GUI_ITEM_INV, world, 0, 0, 0); 834 | } 835 | 836 | // Otherwise, stealthily place some diamonds in there for a nice surprise next time you open it up :) 837 | else { 838 | new InventoryItem(player.getHeldItem()).setInventorySlotContents(0, new ItemStack(Item.diamond,4)); 839 | } 840 | } 841 | 842 | return itemstack; 843 | } 844 | 845 | @Override 846 | @SideOnly(Side.CLIENT) 847 | public void registerIcons(IconRegister iconRegister) 848 | { 849 | this.itemIcon = iconRegister.registerIcon("inventoryitemmod:" + this.getUnlocalizedName().substring(5)); 850 | } 851 | } 852 | /* 853 | And that's it! 854 | */ 855 | -------------------------------------------------------------------------------- /ModdingWithAPIs.java: -------------------------------------------------------------------------------- 1 | /* 2 | MODDING WITH APIs 3 | 4 | In this tutorial, I will cover how to install an API in a manner that does not require you to place the entire API 5 | directly in your project directory, how to build and package your mod independently from the API so that you are not 6 | distributing other people's mods with your own, and how to utilize an API such that your mod will still function 7 | without it, but will still be able to take advantage of the extra features when it is present. 8 | 9 | I struggled with this myself while working on my own mod, but thanks to GotoLink's guidance and extreme patience I 10 | finally got everything working, and I figure the things I learned will be greatly beneficial to anyone else looking 11 | to hook into another mod's API. 12 | 13 | Before we start, do note that this tutorial assumes that you are already capable of writing your own mod; I will not 14 | be providing any support here for the basics of setting up a mod. Also, this tutorial assumes that you are using the 15 | latest versions of Forge, meaning that you are also using Gradle, as the vast majority of mods with APIs all use Forge. 16 | 17 | If you would like to follow along with the tutorial step-by-step, you can download the API that I will be using here: 18 | https://github.com/coolAlias/ZeldaSwordSkills-1.6.4/releases 19 | 20 | Step 1: Installing the API 21 | 22 | 1. Assuming you already have your workspace set up, create a new folder named "/libs" in your project directory. 23 | That's the directory that contains your /bin, /build, /gradle, and /src folders, and now also has a /libs folder. 24 | 25 | 2. Ask the mod author(s) for a binary distributable / source file of their mod and place it in the /libs folder you 26 | just created. 27 | 28 | 3. In Eclipse, right-click on your project, select "Build Path" and then "Configure Build Path". Click the 29 | "Libraries" tab, then "Add External JARs" and find the binary file that you just placed in the /libs folder. 30 | 31 | 4. If the API author provided a source file, open the "Referenced Libraries" tree in your package explorer, find the 32 | API binary that you just referenced and right-click on it; go to "Properties" -> "External location" -> "External 33 | file" and navigate to wherever you stored the source file, preferably right next to the binary in the /libs folder. 34 | 35 | 5. Run your debug configuration and see if everything is still working; if so, great! If not, you may need to run 36 | setupDev and setupDecomp workspace one more time: 37 | 38 | a. gradlew setupDevWorkspace 39 | b. gradlew setupDecompWorkspace 40 | c. gradlew eclipse // will vary depending on your IDE 41 | 42 | Once you have the debug client working, you should see that both your mod and the API are loaded; if you start a new 43 | game, anything added by the API mod will be in that game as well and should be fully functional. This is extremely 44 | handy while developing an addon or simply testing compatibility between mods. 45 | 46 | You are now ready to start using the API in your mod! 47 | 48 | NOTE: Not all APIs will be able to use this setup; core mods, for example, can be tricky to work with due to their 49 | access transformers needing to modify the vanilla jar before being usable. 50 | 51 | Let's take Battlegear2's API as an example. 52 | 53 | 1. For BG2, the binary distributable should be placed not in the project directory, but wherever you have designated as 54 | the working directory in a "/mods" folder. For most people using Eclipse, this will be your "projectDirectory/eclipse/mods", 55 | or if you followed Lex's multi-project workspace video, in "workspaceLocation/run/mods", though I recommend pointing 56 | your API-dependent mod's workspace at its own local eclipse directory instead of the /run directory so you don't have 57 | to perform all of the following steps for every single mod in your workspace. 58 | 59 | 2. Then, follow steps 3, 4, and 5 above, run the debug client, start a game and try to open the inventory. The game 60 | should crash at this point with an Illegal Access Error - that's the access transformer failing. 61 | 62 | 3. Find the "battlegear_at.cfg" file, place that in your resources directory and re-run all of the commands from step 63 | 5, then try again. 64 | 65 | 4. If it crashes again, check in your /build directory for a "deobfuscated-bin.jar" file - this is the modified 66 | Minecraft jar - and change your library reference from "forgeSrc..." to that file instead. If you don't see that 67 | file, you may need to close Eclipse and run the commands again with "--refresh-dependencies". Note that in 1.7.2, 68 | I have never found this file and it still seems to work, but in 1.6.4 it seems to be required. Your mileage may vary. 69 | 70 | 5. At this point it should work, but if it doesn't, make sure that you are using EXACTLY the same version of Forge 71 | for which the core mod was written, because if any of the fields it tries to modify have different names, the process 72 | will fail and you will most likely get an exception when attempting to access that field. 73 | 74 | It can be very frustrating and time-consuming working with core mod APIs, sometimes requiring those same steps to be 75 | repeated over and over again even after you have successfully set it up once when, for example, you clean your project 76 | or otherwise re-reference the Forge Minecraft jar instead of the one modified by the access transformers. 77 | 78 | Step 2: Implementing an API Interface 79 | 80 | Our first order of business will be creating a new Item that can pick up blocks using Zelda Sword Skills' ILiftBlock 81 | interface. Simply create a new Item class and implement ILiftBlock. You should be able to import it right away, but 82 | you may need to explicitly tell Eclipse where to look, or fix your project setup if you forgot to add the API as a 83 | referenced library. Be sure to link the API source if you have it so you get readable names as method parameters, 84 | rather than arg0, d1, etc., and then let Eclipse add the unimplemented methods for you. 85 | 86 | */ 87 | public class ItemLifter extends Item implements ILiftBlock { 88 | 89 | public ItemLifter(int id) { 90 | super(id); 91 | } 92 | 93 | @Override 94 | public BlockWeight getLiftStrength(EntityPlayer player, ItemStack stack, Block block, int meta) { 95 | // TODO Auto-generated method stub 96 | return null; 97 | } 98 | 99 | @Override 100 | public ItemStack onLiftBlock(EntityPlayer player, ItemStack stack, Block block, int meta) { 101 | // TODO Auto-generated method stub 102 | return null; 103 | } 104 | } 105 | /* 106 | I'll let you handle the basics of the Item and focus on the API methods. There are just two methods to implement for 107 | this interface, and the first one requires another API class, BlockWeight, which should already have been imported. 108 | Just type in BlockWeight followed by a period to see a list of options; we'll choose "EXTREME_II" just for fun, even 109 | though there aren't any blocks in that category (yet). 110 | 111 | The second method asks us to return an ItemStack, and the java-docs explain that this is the stack that will be returned 112 | to the player when the block is placed. We want to get our item back, so we will return "stack" instead of "null", but 113 | first we will damage the stack by 1 so that it will eventually break. Now our methods look like this: 114 | */ 115 | 116 | @Override 117 | public BlockWeight getLiftStrength(EntityPlayer player, ItemStack stack, Block block, int meta) { 118 | return BlockWeight.EXTREME_II; 119 | } 120 | 121 | @Override 122 | public ItemStack onLiftBlock(EntityPlayer player, ItemStack stack, Block block, int meta) { 123 | stack.damageItem(1, player); 124 | return stack; 125 | } 126 | 127 | /* 128 | Go ahead and start up the client in Eclipse; you should have an item that picks up any solid block that you right-click 129 | on, and returns the same item but slightly damaged when you place the block down. 130 | 131 | Now, if you were to build the mod and attempt to play it in Minecraft without Zelda Sword Skills installed, your game 132 | will crash with a No Class Definition Found error, since we are trying to access several API classes that are not 133 | present. So, before we build the mod, we will first add some code that will strip the API interface and methods if 134 | the required mod classes are not present. 135 | 136 | Step 3: Stripping Interfaces 137 | 138 | Luckily for us, FML is capable of stripping interfaces by using cpw's awesome @Optional.Interface annotation. 139 | 140 | There are 3 fields we need to fill out: iface, modid, and striprefs: 141 | 142 | 1. "iface" is the complete package path of the interface we wish to strip, so it is best to simply copy the import 143 | path and paste that in to avoid any typos. If you get the path incorrect, it will not work. 144 | 145 | 2. "modid" is obviously the modid of the mod that owns the interface to strip. Again, be sure to spell it correctly. 146 | 147 | 3. "striprefs" set this to true to strip interface and method references that are not found. I don't know why anyone 148 | would ever set this to false, but I'm sure there are uses for that as well. 149 | 150 | Add the annotation above the class declaration: 151 | */ 152 | 153 | @Optional.Interface(iface="zeldaswordskills.api.item.ILiftBlock", modid="zeldaswordskills", striprefs=true) 154 | public class ItemLifter extends Item implements ILiftBlock { 155 | // your class goes here 156 | } 157 | 158 | /* 159 | Make sure you import "cpw.mods.fml.common.Optional" and not the google Optional class. 160 | 161 | One last thing we should do is strip the API methods from the class, though the mod will probably still work just 162 | fine even if you do not. It's just one simple line: @Method(modid="apiModId"), and you absolutely should use it if 163 | the API method in question is not part of an interface that you are stripping. 164 | */ 165 | 166 | // put this same line above all API methods: 167 | @Method(modid="zeldaswordskills") 168 | @Override 169 | public BlockWeight getLiftStrength(EntityPlayer player, ItemStack stack, Block block, int meta) { 170 | return BlockWeight.EXTREME_II; 171 | } 172 | 173 | /* 174 | Step 4: Load Order 175 | 176 | Once you have your API-utilizing mod all working in the debug environment, there are a few things that should be done 177 | before building and packaging the final product. The first is to make sure your mod will load after the mod whose API 178 | you are using; this is done in the mcmod.info file. Let's take a look at some of the available fields: 179 | 180 | 1. "requiredMods": [ "Forge", "someOtherMod" ], 181 | Any mods you list here will be required for your mod to load; your mod cannot load without them. Since we want our 182 | mod to be functional even if the other mod is not present, we will not be using this field here, but it is very 183 | useful if your mod requires some other mod's functionality in order to be usable. 184 | 185 | 2. "dependencies": [ "zeldaswordskills" ], 186 | Any mods that your mod is dependent upon will be loaded before yours; this is very important if you need to know 187 | that a mod is present or not during the pre-initialization stages, and is generally a good idea to put any mod that 188 | your mod might rely upon as a dependency, even if you do not need to do anything with it in the early stages of mod 189 | loading. 190 | 191 | 3. "dependants": [ "ModSubmodule" ] 192 | Any mods listed here are mods that depend upon and will be loaded after your mod; useful if you make a submodule of 193 | your own mod. Note that the submodule should list the parent mod as a dependency and possibly required mod. 194 | 195 | 4. "useDependencyInformation": "true" 196 | You MUST set this to "true" or the above three fields will be meaningless as far as mod order is concerned. If you 197 | omit this field, chances are someone will crash when your mod is installed alongside the dependency, even if you 198 | don't. This is because if our mod loads before the api, when we try to access the api methods, they will not yet 199 | exist. 200 | 201 | You can learn all about how FML uses the mcmod.info file and other things that you can do with it on the wiki: 202 | https://github.com/MinecraftForge/FML/wiki/FML-mod-information-file 203 | 204 | For this tutorial, we will only add the "dependencies" and "useDependencyInformation" lines, ensuring that the API 205 | we want to use is loaded first if present. 206 | */ 207 | 208 | "dependencies": ["zeldaswordskills"], 209 | "useDependencyInformation": "true" 210 | 211 | /* 212 | For many APIs, assigning dependencies in mcmod.info should be enough to guarantee load order. This is the case for 213 | any API that does not require access during the pre-initialization stages of a mod, for example if you were using a 214 | structure generation or mob animation API. Before using any methods or classes from such APIs, it would be sufficient 215 | simply to check if the mod is loaded: 216 | */ 217 | 218 | if (Loader.isModLoaded("api_modid")) { 219 | // do something with the API 220 | } 221 | 222 | // If I need to access this frequently, I typically store the mod loaded state during pre-initialization: 223 | public static boolean isWhateverApiLoaded; 224 | 225 | @EventHandler 226 | public void preInit(FMLPreInitializationEvent event) { 227 | isWhateverApiLoaded = Loader.isModLoaded("whateverAPI"); 228 | // note that this only means that the mod has been recognized, not necessarily that it is fully 229 | // initialized and ready to go yet; you should NOT try to use anything to do with the API 230 | // until the FMLInitializationEvent at the earliest, even with dependency information sorting 231 | // the load order, just to be safe 232 | } 233 | 234 | /* 235 | However, since we require the API to be loaded in time for our new Item, we need access to a fully-functional API 236 | during our mod's pre-initialization event. The only way to load an API prior to pre-init is if the author included 237 | API markers for each of the API packages: 238 | */ 239 | 240 | // filename is "package-info.java" for every one of these files 241 | @API(owner = "zeldaswordskills", provides = "ZeldaAPI", apiVersion = "0.1") 242 | package zeldaswordskills.api; 243 | 244 | import cpw.mods.fml.common.API; 245 | 246 | /* 247 | There should be one of these files in every package that provides API features; if not, you will need to contact the 248 | API author and ask them to provide these files, or your mod is quite likely to crash if not for you, then for the 249 | majority of people using your mod. Remember, these markers are ONLY needed if you require the API during pre-initialization, 250 | such as for implementing specific interfaces in your Items or Blocks, so don't trouble the author if the API does not 251 | truly require them. 252 | 253 | Step 5: Building the Mod 254 | 255 | The final step is of course building the mod. Since our mod is dependent upon an API, we need to let the compiler 256 | know where to find that code during the build process or we will get lots of Class Not Found and similar errors. 257 | 258 | To do so, simply add any dependencies to a "compile files()" method in the build.gradle file, using the full path 259 | relative to your project directory and separating each file path with a comma. Note that you can use "../" to move 260 | up a folder if, for example, you have a dependency in the working directory instead of the project directory. 261 | 262 | "libs/zeldaswordskills-1.6.4-0.6.3.jar" -> located in /workingDirectory/projectDirectory/libs/ 263 | 264 | "../run/mods/zeldaswordskills-1.6.4-0.6.3.jar" -> located in /workingDirectory/run/mods/ 265 | */ 266 | 267 | version = "1.0" 268 | group= "com.google.coolalias008.modwithapi" 269 | archivesBaseName = "modwithapi" 270 | 271 | dependencies { 272 | compile files ( 273 | "libs/zeldaswordskills-1.6.4-0.6.3.jar" 274 | ) 275 | } 276 | 277 | /* 278 | Once the build file is saved, open up a command console and run "gradlew build"; it should compile with no errors, 279 | but if it doesn't, double-check the file paths and make sure nothing is misspelled. 280 | 281 | Time to load it up in Minecraft and give it a go! Try first with just your mod alone and make sure that is working, 282 | then exit the Minecraft launcher completely (just to be safe), add the API-providing mod to your /mods folder, and 283 | launch once more. If you followed the tutorial and are using my ZeldaAPI, you should now have an item that can pick 284 | up any solid block, just by implementing a single interface! Pretty awesome. 285 | */ 286 | /* 287 | Step 6: Multiple Interfaces 288 | 289 | Alright, you can do a single interface, but what about when you want to implement several API interfaces in a single 290 | item, block, or other class? You cannot simply stack @Optionals on top of each other, but you can use an InterfaceList: 291 | */ 292 | 293 | @Optional.InterfaceList(value={ 294 | @Optional.Interface(iface="zeldaswordskills.api.block.ILiftable", modid="zeldaswordskills", striprefs=true), 295 | @Optional.Interface(iface="zeldaswordskills.api.block.ISmashable", modid="zeldaswordskills", striprefs=true) 296 | }) 297 | 298 | /* 299 | The individual @Optional.Interfaces are exactly the same as before, but separtated by commas and nested inside of an 300 | array, all enclosed by the @Optional.InterfaceList annotation. 301 | 302 | Alright, so let's make a block that is both liftable AND smashable by implementing ILiftable and ISmashable. Import 303 | those two classes and let Eclipse add the unimplemented methods for you, and be sure to set up the rest of the Block 304 | class (texture, creative tab, etc.). I will only cover the API methods here. 305 | */ 306 | 307 | @Method(modid="zeldaswordskills") 308 | @Override 309 | public BlockWeight getSmashWeight(EntityPlayer player, ItemStack stack, int meta) { 310 | // let's make our block very easy to smash, since we do not have any smashing items yet, the only 311 | // way to smash our block would be using one of the Zelda Hammers 312 | return BlockWeight.VERY_LIGHT; 313 | } 314 | 315 | @Method(modid="zeldaswordskills") 316 | @Override 317 | public Result onSmashed(World world, EntityPlayer player, ItemStack stack, int x, int y, int z, int side) { 318 | // for the sake of simplicity, we will just use the default smashing mechanics 319 | return Result.DEFAULT; 320 | } 321 | 322 | @Method(modid="zeldaswordskills") 323 | @Override 324 | public BlockWeight getLiftWeight(EntityPlayer player, ItemStack stack, int meta) { 325 | // we want our block to be extremely difficult to lift, giving our custom itemLifter a purpose; 326 | // not even any of the items in Zelda can lift our block! mwa ha ha! 327 | return BlockWeight.EXTREME_II; 328 | } 329 | 330 | @Method(modid="zeldaswordskills") 331 | @Override 332 | public void onLifted(World world, EntityPlayer player, ItemStack stack, int x, int y, int z, int meta) { 333 | // if you need to handle a tile entity or do anything else before the block is lifted, 334 | // you can do so here; however, we do not need to for our simple block 335 | } 336 | 337 | @Method(modid="zeldaswordskills") 338 | @Override 339 | public void onHeldBlockPlaced(World world, ItemStack stack, int x, int y, int z, int meta) { 340 | // if you want to do something special when the block is placed, you can do so here, 341 | // but we will leave it empty for this tutorial 342 | } 343 | 344 | /* 345 | That's it for the API methods. Once the rest of the block is set up and registered, you can go ahead and give it a 346 | try! You should have a block that can only be lifted with the special itemLifter that we made earlier, but can be 347 | smashed even with just the wooden hammer from Zelda. If you run your mod by itself, you will still have the item and 348 | block in the game, but without the lifting and smashing mechanics provided by the API. This is great if your items 349 | and blocks have other functions that still make them useful independently. 350 | 351 | You should now be able to work with pretty much any mod API in a manner that will still allow your mod to function 352 | independently should the API-providing mod not be present, as well as avoid including the API code in your mod 353 | directly. Good luck! Don't forget to give GotoLink kudos if you see him around - he likes to hang out over on 354 | MinecraftForge forums, so go bump up his karma from time to time! 355 | */ 356 | -------------------------------------------------------------------------------- /MultiInputFurnaceTutorial.java: -------------------------------------------------------------------------------- 1 | import java.util.Arrays; 2 | 3 | /** 4 | * TUTORIAL: Creating a Multi-Input Furnace with Variable-Input Recipes 5 | */ 6 | /* 7 | Do you need a furnace that can handle 3 inputs? How about 5? 10? Do some of your 8 | furnace recipes require 2 input itemstacks to smelt, while other recipes call for 9 | more or only 1? Look no further! Below you will learn how to do just that! 10 | 11 | But first, if you've never made even a single-input furnace or other TileEntity-based Container/Gui, 12 | please do that before trying to follow this tutorial. I'm not going to cover all aspects of the Gui, 13 | Block and other such things here as there are other tutorials on that. 14 | 15 | If you need help setting up the basics, check out MrrGingerNinja's tutorial on Tile-Entity based Inventory: 16 | http://www.minecraftforum.net/topic/1959857-162-advanced-minecraft-forge-modding-tutorial-2-inventories/ 17 | 18 | I also recommend Microjunk's furnace tutorials: 19 | http://www.minecraftforum.net/topic/1924178-forge-162-micros-furnace-tutorials/ 20 | 21 | On to the tutorial. 22 | */ 23 | /** 24 | * Step 1: Make a Container Class 25 | */ 26 | public class ContainerArcaneInscriber extends Container 27 | { 28 | private TileEntityArcaneInscriber inscriber; 29 | private int lastProgressTime; 30 | private int lastBurnTime; 31 | private int lastItemBurnTime; 32 | 33 | // NOTE that here you could add as many slots as you want. 34 | 35 | // I have a complementary discharge slot array because the items I use as fuel 36 | // are rechargeable and I want to get them back 37 | 38 | // The way I use INPUT slots is both for fuel AND to determine the output result 39 | 40 | // You'll probably just want to use it for the latter, in which case you'll need to add a FUEL slot 41 | public static final int INPUT[] = {0,1,2,3,4,5,6}; 42 | 43 | // delete the DISCHARGE slots if you don't need to keep your used up fuel 44 | public static final int DISCHARGE[] = {7,8,9,10,11,12,13}; 45 | 46 | // BLANK_SCROLL is NOT fuel, but a final requirement to actually inscribe a scroll. This allows me 47 | // to set up a recipe in the rune slots and see what it is first before actually consuming the runes. You 48 | // probably would change this slot to store FUEL instead, like most furnaces do. 49 | 50 | // RECIPE just stores the current recipe so I can keep inscribing scrolls even after the runes are used 51 | // up and to display the recipe on screen. You probably don't need this slot. 52 | 53 | public static final int RUNE_SLOTS = INPUT.length, BLANK_SCROLL = RUNE_SLOTS*2, RECIPE = BLANK_SCROLL+1, 54 | OUTPUT = RECIPE+1, INV_START = OUTPUT+1, INV_END = INV_START+26, HOTBAR_START = INV_END+1, 55 | HOTBAR_END= HOTBAR_START+8; 56 | 57 | public ContainerArcaneInscriber(InventoryPlayer inventoryPlayer, TileEntityArcaneInscriber par2TileEntityArcaneInscriber) 58 | { 59 | int i; 60 | 61 | this.inscriber = par2TileEntityArcaneInscriber; 62 | 63 | // ADD CUSTOM SLOTS 64 | for (i = 0; i < RUNE_SLOTS; ++i) { 65 | this.addSlotToContainer(new Slot(par2TileEntityArcaneInscriber, INPUT[i], 43 + (18*i), 15)); 66 | } 67 | for (i = 0; i < RUNE_SLOTS; ++i) { 68 | this.addSlotToContainer(new SlotArcaneInscriberDischarge(par2TileEntityArcaneInscriber, DISCHARGE[i], 43 + (18*i), 64)); 69 | } 70 | 71 | this.addSlotToContainer(new Slot(par2TileEntityArcaneInscriber, BLANK_SCROLL, 63, 39)); 72 | this.addSlotToContainer(new SlotArcaneInscriberRecipe(par2TileEntityArcaneInscriber, RECIPE, 17, 35)); 73 | this.addSlotToContainer(new SlotArcaneInscriber(inventoryPlayer.player, par2TileEntityArcaneInscriber, OUTPUT, 119, 39)); 74 | 75 | // ADD PLAYER INVENTORY 76 | for (i = 0; i < 3; ++i) 77 | { 78 | for (int j = 0; j < 9; ++j) 79 | { 80 | this.addSlotToContainer(new Slot(inventoryPlayer, j + i * 9 + 9, 8 + j * 18, 84 + i * 18)); 81 | } 82 | } 83 | 84 | // ADD PLAYER ACTION BAR 85 | for (i = 0; i < 9; ++i) 86 | { 87 | this.addSlotToContainer(new Slot(inventoryPlayer, i, 8 + i * 18, 142)); 88 | } 89 | } 90 | 91 | public void addCraftingToCrafters(ICrafting iCrafting) 92 | { 93 | super.addCraftingToCrafters(iCrafting); 94 | iCrafting.sendProgressBarUpdate(this, 0, this.inscriber.inscribeProgressTime); 95 | } 96 | 97 | /** 98 | * Looks for changes made in the container, sends them to every listener. 99 | */ 100 | public void detectAndSendChanges() 101 | { 102 | super.detectAndSendChanges(); 103 | 104 | for (int i = 0; i < this.crafters.size(); ++i) 105 | { 106 | ICrafting icrafting = (ICrafting)this.crafters.get(i); 107 | 108 | if (this.lastProgressTime != this.inscriber.inscribeProgressTime) 109 | { 110 | icrafting.sendProgressBarUpdate(this, 0, this.inscriber.inscribeProgressTime); 111 | } 112 | } 113 | 114 | this.lastProgressTime = this.inscriber.inscribeProgressTime; 115 | } 116 | 117 | @SideOnly(Side.CLIENT) 118 | public void updateProgressBar(int par1, int par2) 119 | { 120 | if (par1 == 0) 121 | { 122 | this.inscriber.inscribeProgressTime = par2; 123 | } 124 | } 125 | 126 | @Override 127 | public boolean canInteractWith(EntityPlayer entityplayer) 128 | { 129 | return this.inscriber.isUseableByPlayer(entityplayer); 130 | } 131 | 132 | /** 133 | * Called when a player shift-clicks on a slot. You must override this or you will crash when someone does that. 134 | */ 135 | public ItemStack transferStackInSlot(EntityPlayer par1EntityPlayer, int par2) 136 | { 137 | ItemStack itemstack = null; 138 | Slot slot = (Slot)this.inventorySlots.get(par2); 139 | 140 | if (slot != null && slot.getHasStack()) 141 | { 142 | ItemStack itemstack1 = slot.getStack(); 143 | itemstack = itemstack1.copy(); 144 | 145 | // If item is in TileEntity inventory 146 | if (par2 < INV_START) 147 | { 148 | // try to place in player inventory / action bar 149 | if (!this.mergeItemStack(itemstack1, INV_START, HOTBAR_END+1, true)) 150 | { 151 | return null; 152 | } 153 | 154 | slot.onSlotChange(itemstack1, itemstack); 155 | } 156 | // Item is in player inventory, try to place in inscriber 157 | else if (par2 > OUTPUT) 158 | { 159 | // if it is a charged rune, place in the first open input slot 160 | if (TileEntityArcaneInscriber.isSource(itemstack1)) 161 | { 162 | if (!this.mergeItemStack(itemstack1, INPUT[0], INPUT[RUNE_SLOTS-1]+1, false)) 163 | { 164 | return null; 165 | } 166 | } 167 | // if it's a blank scroll, place in the scroll slot 168 | else if (itemstack1.itemID == ArcaneLegacy.scrollBlank.itemID) 169 | { 170 | if (!this.mergeItemStack(itemstack1, BLANK_SCROLL, BLANK_SCROLL+1, false)) 171 | { 172 | return null; 173 | } 174 | } 175 | // item in player's inventory, but not in action bar 176 | else if (par2 >= INV_START && par2 < HOTBAR_START) 177 | { 178 | // place in action bar 179 | if (!this.mergeItemStack(itemstack1, HOTBAR_START, HOTBAR_END+1, false)) 180 | { 181 | return null; 182 | } 183 | } 184 | // item in action bar - place in player inventory 185 | else if (par2 >= HOTBAR_START && par2 < HOTBAR_END+1 && !this.mergeItemStack(itemstack1, INV_START, HOTBAR_START, false)) 186 | { 187 | return null; 188 | } 189 | } 190 | // In one of the inscriber slots; try to place in player inventory / action bar 191 | else if (!this.mergeItemStack(itemstack1, INV_START, HOTBAR_END+1, false)) 192 | { 193 | return null; 194 | } 195 | 196 | if (itemstack1.stackSize == 0) 197 | { 198 | slot.putStack((ItemStack)null); 199 | } 200 | else 201 | { 202 | slot.onSlotChanged(); 203 | } 204 | 205 | if (itemstack1.stackSize == itemstack.stackSize) 206 | { 207 | return null; 208 | } 209 | 210 | slot.onPickupFromSlot(par1EntityPlayer, itemstack1); 211 | } 212 | return itemstack; 213 | } 214 | } 215 | 216 | /** 217 | * Step 2: Make your Custom Slots, if needed 218 | */ 219 | // Most furnace-like containers will only ever need the first slot I make here; 220 | // the other two are particular to my special case. 221 | public class SlotArcaneInscriber extends Slot 222 | { 223 | /** The player that is using the GUI where this slot resides. */ 224 | private EntityPlayer thePlayer; 225 | private int field_75228_b; 226 | 227 | public SlotArcaneInscriber(EntityPlayer par1EntityPlayer, IInventory par2IInventory, int par3, int par4, int par5) 228 | { 229 | super(par2IInventory, par3, par4, par5); 230 | this.thePlayer = par1EntityPlayer; 231 | } 232 | 233 | /** 234 | * Check if the stack is a valid item for this slot. Always true beside for the armor slots. 235 | */ 236 | public boolean isItemValid(ItemStack par1ItemStack) 237 | { 238 | return false; 239 | } 240 | 241 | /** 242 | * Decrease the size of the stack in slot (first int arg) by the amount of the second int arg. Returns the new 243 | * stack. 244 | */ 245 | public ItemStack decrStackSize(int par1) 246 | { 247 | if (this.getHasStack()) 248 | { 249 | this.field_75228_b += Math.min(par1, this.getStack().stackSize); 250 | } 251 | 252 | return super.decrStackSize(par1); 253 | } 254 | 255 | public void onPickupFromSlot(EntityPlayer par1EntityPlayer, ItemStack par2ItemStack) 256 | { 257 | this.onCrafting(par2ItemStack); 258 | super.onPickupFromSlot(par1EntityPlayer, par2ItemStack); 259 | } 260 | 261 | /** 262 | * the itemStack passed in is the output - ie, iron ingots, and pickaxes, not ore and wood. Typically increases an 263 | * internal count then calls onCrafting(item). 264 | */ 265 | protected void onCrafting(ItemStack par1ItemStack, int par2) 266 | { 267 | this.field_75228_b += par2; 268 | this.onCrafting(par1ItemStack); 269 | } 270 | 271 | /** The itemStack passed in is the output - ie, iron ingots, and pickaxes, not ore and wood. */ 272 | protected void onCrafting(ItemStack par1ItemStack) 273 | { 274 | par1ItemStack.onCrafting(this.thePlayer.worldObj, this.thePlayer, this.field_75228_B); 275 | 276 | if (!this.thePlayer.worldObj.isRemote) 277 | { 278 | int i = this.field_75228_b; 279 | float f = SpellRecipes.spells().getExperience(par1ItemStack); 280 | int j; 281 | 282 | if (f == 0.0F) 283 | { 284 | i = 0; 285 | } 286 | else if (f < 1.0F) 287 | { 288 | j = MathHelper.floor_float((float)i * f); 289 | 290 | if (j < MathHelper.ceiling_float_int((float)i * f) && (float)Math.random() < (float)i * f - (float)j) 291 | { 292 | ++j; 293 | } 294 | 295 | i = j; 296 | } 297 | 298 | while (i > 0) 299 | { 300 | j = EntityXPOrb.getXPSplit(i); 301 | i -= j; 302 | this.thePlayer.worldObj.spawnEntityInWorld(new EntityXPOrb(this.thePlayer.worldObj, this.thePlayer.posX, this.thePlayer.posY + 0.5D, this.thePlayer.posZ + 0.5D, j)); 303 | } 304 | } 305 | 306 | this.field_75228_b = 0; 307 | } 308 | } 309 | 310 | /** 311 | * I have this slot so I can get the discharged runes back for re-use rather than consuming them in the process. 312 | * You probably won't need this kind of slot for a furnace. 313 | */ 314 | class SlotArcaneInscriberDischarge extends Slot 315 | { 316 | private int field_75228_b; 317 | 318 | public SlotArcaneInscriberDischarge(IInventory par2IInventory, int par3, int par4, int par5) 319 | { 320 | super(par2IInventory, par3, par4, par5); 321 | } 322 | 323 | /** 324 | * Check if the stack is a valid item for this slot. Always true beside for the armor slots. 325 | */ 326 | public boolean isItemValid(ItemStack par1ItemStack) 327 | { 328 | return false; 329 | } 330 | 331 | /** 332 | * Decrease the size of the stack in slot (first int arg) by the amount of the second int arg. Returns the 333 | * new stack. 334 | */ 335 | public ItemStack decrStackSize(int par1) 336 | { 337 | if (this.getHasStack()) 338 | { 339 | this.field_75228_b += Math.min(par1, this.getStack().stackSize); 340 | } 341 | 342 | return super.decrStackSize(par1); 343 | } 344 | 345 | public void onPickupFromSlot(EntityPlayer par1EntityPlayer, ItemStack par2ItemStack) 346 | { 347 | super.onPickupFromSlot(par1EntityPlayer, par2ItemStack); 348 | } 349 | } 350 | /** 351 | * I made this slot to show the output of the current runes on screen, so player's know what they are about to make 352 | */ 353 | class SlotArcaneInscriberRecipe extends Slot 354 | { 355 | private int field_75228_b; 356 | 357 | public SlotArcaneInscriberRecipe(IInventory par2IInventory, int par3, int par4, int par5) 358 | { 359 | super(par2IInventory, par3, par4, par5); 360 | } 361 | 362 | /** 363 | * Check if the stack is a valid item for this slot. Always true beside for the armor slots. 364 | */ 365 | public boolean isItemValid(ItemStack par1ItemStack) 366 | { 367 | return false; 368 | } 369 | 370 | /** 371 | * Return whether this slot's stack can be taken from this slot. 372 | */ 373 | public boolean canTakeStack(EntityPlayer par1EntityPlayer) 374 | { 375 | return false; 376 | } 377 | 378 | /** 379 | * Decrease the size of the stack in slot (first int arg) by the amount of the second int arg. Returns the new 380 | * stack. 381 | */ 382 | public ItemStack decrStackSize(int par1) 383 | { 384 | if (this.getHasStack()) 385 | { 386 | this.field_75228_b += Math.min(par1, this.getStack().stackSize); 387 | } 388 | 389 | return super.decrStackSize(par1); 390 | } 391 | 392 | public void onPickupFromSlot(EntityPlayer par1EntityPlayer, ItemStack par2ItemStack) 393 | { 394 | // can't be picked up so nothing here 395 | } 396 | } 397 | 398 | /** 399 | * Step 3: Your Tile Entity Class 400 | */ 401 | public class TileEntityArcaneInscriber extends TileEntity implements ISidedInventory 402 | { 403 | private static final int[] slots_top = new int[] {0}; 404 | private static final int[] slots_bottom = new int[] {2, 1}; 405 | private static final int[] slots_sides = new int[] {1}; 406 | 407 | /** Array bounds = number of slots in ContainerArcaneInscriber */ 408 | private ItemStack[] inscriberInventory = new ItemStack[ContainerArcaneInscriber.INV_START]; 409 | 410 | /** Time required to scribe a single scroll */ 411 | private static final int INSCRIBE_TIME = 100, RUNE_CHARGE_TIME = 400; 412 | 413 | /** The number of ticks that the inscriber will keep inscribing */ 414 | public int currentInscribeTime; 415 | 416 | /** The number of ticks that a charged rune will provide */ 417 | public int inscribeTime = 400; 418 | 419 | /** The number of ticks that the current scroll has been inscribing for */ 420 | public int inscribeProgressTime; 421 | 422 | private String displayName = "Arcane Inscriber"; 423 | 424 | public TileEntityArcaneInscriber() { 425 | } 426 | 427 | @Override 428 | public int getSizeInventory() { 429 | return inscriberInventory.length; 430 | } 431 | 432 | @Override 433 | public ItemStack getStackInSlot(int slot) { 434 | return this.inscriberInventory[slot]; 435 | } 436 | 437 | @Override 438 | public ItemStack decrStackSize(int slot, int amt) 439 | { 440 | ItemStack stack = getStackInSlot(slot); 441 | if (stack != null) { 442 | if (stack.stackSize <= amt) { 443 | setInventorySlotContents(slot, null); 444 | } else { 445 | stack = stack.splitStack(amt); 446 | if (stack.stackSize == 0) { 447 | setInventorySlotContents(slot, null); 448 | } 449 | } 450 | } 451 | return stack; 452 | } 453 | 454 | @Override 455 | public ItemStack getStackInSlotOnClosing(int slot) 456 | { 457 | ItemStack stack = getStackInSlot(slot); 458 | if (stack != null) { setInventorySlotContents(slot, null); } 459 | return stack; 460 | } 461 | 462 | @Override 463 | public void setInventorySlotContents(int slot, ItemStack stack) 464 | { 465 | inscriberInventory[slot] = stack; 466 | if (stack != null && stack.stackSize > getInventoryStackLimit()) { 467 | stack.stackSize = getInventoryStackLimit(); 468 | } 469 | } 470 | 471 | @Override 472 | public String getInvName() { 473 | return this.isInvNameLocalized() ? this.displayName : "container.arcaneinscriber"; 474 | } 475 | 476 | /** 477 | * If this returns false, the inventory name will be used as an unlocalized name, and translated into the player's 478 | * language. Otherwise it will be used directly. 479 | */ 480 | public boolean isInvNameLocalized() 481 | { 482 | return this.displayName != null && this.displayName.length() > 0; 483 | } 484 | 485 | /** 486 | * Sets the custom display name to use when opening a GUI linked to this tile entity. 487 | */ 488 | public void setGuiDisplayName(String par1Str) 489 | { 490 | this.displayName = par1Str; 491 | } 492 | 493 | @Override 494 | public int getInventoryStackLimit() { 495 | return 64; 496 | } 497 | 498 | /** 499 | * Returns an integer between 0 and the passed value representing how 500 | * close the current item is to being completely cooked 501 | */ 502 | @SideOnly(Side.CLIENT) 503 | public int getInscribeProgressScaled(int par1) 504 | { 505 | return this.inscribeProgressTime * par1 / INSCRIBE_TIME; 506 | } 507 | 508 | /** 509 | * Returns an integer between 0 and the passed value representing how much burn time is left on the current fuel 510 | * item, where 0 means that the item is exhausted and the passed value means that the item is fresh 511 | */ 512 | @SideOnly(Side.CLIENT) 513 | public int getInscribeTimeRemainingScaled(int par1) 514 | { 515 | return this.currentInscribeTime * par1 / this.INSCRIBE_TIME; 516 | } 517 | 518 | /** 519 | * Returns true if the furnace is currently burning 520 | */ 521 | public boolean isInscribing() 522 | { 523 | return this.currentInscribeTime > 0; 524 | } 525 | 526 | /** 527 | * Allows the entity to update its state. Overridden in most subclasses, e.g. the mob spawner uses this to count 528 | * ticks and creates a new spawn inside its implementation. 529 | */ 530 | public void updateEntity() 531 | { 532 | boolean flag = this.currentInscribeTime > 0; 533 | boolean flag1 = false; 534 | 535 | if (this.currentInscribeTime > 0) 536 | { 537 | --this.currentInscribeTime; 538 | 539 | // Container recipe doesn't match current non-null InscribingResult 540 | flag1 = (this.inscriberInventory[ContainerArcaneInscriber.RECIPE] != this.getCurrentRecipe() && this.getCurrentRecipe() != null); 541 | // Recipe changed - reset timer and current recipe slot 542 | if (flag1) 543 | { 544 | this.inscriberInventory[ContainerArcaneInscriber.RECIPE] = this.getCurrentRecipe(); 545 | this.onInventoryChanged(); 546 | this.currentInscribeTime = 0; 547 | } 548 | } 549 | 550 | if (!this.worldObj.isRemote) 551 | { 552 | if (this.currentInscribeTime == 0) 553 | { 554 | flag1 = (this.inscriberInventory[ContainerArcaneInscriber.RECIPE] != this.getCurrentRecipe()); 555 | this.inscriberInventory[ContainerArcaneInscriber.RECIPE] = this.getCurrentRecipe(); 556 | // Recipe changed - update inventory 557 | // (only because I'm showing the output for the current recipe on screen) 558 | if (flag1) { this.onInventoryChanged(); } 559 | 560 | if (this.canInscribe()) { 561 | // This is the equivalent of getItemBurnTime from furnace. Note again that I am setting 562 | // my burn time based on an INPUT slot, even though this is generally done with FUEL 563 | this.currentInscribeTime = this.getInscriberChargeTime(this.inscriberInventory[0]); 564 | } 565 | 566 | if (this.currentInscribeTime > 0) 567 | { 568 | flag1 = true; 569 | 570 | // This is where you decrement your FUEL slot's inventory. 571 | // However, since I use INPUT as FUEL and need to save the used up FUEL in DISCHARGE, 572 | // I will use a for loop to decrement all of the inputs and increment all of the discharge slots 573 | // Yours will probably look much simpler - look at the vanilla Furnace code to see an example 574 | for (int i = 0; i < ContainerArcaneInscriber.RUNE_SLOTS; ++i) 575 | { 576 | if (this.inscriberInventory[ContainerArcaneInscriber.INPUT[i]] != null) 577 | { 578 | --this.inscriberInventory[ContainerArcaneInscriber.INPUT[i]].stackSize; 579 | if (this.inscriberInventory[ContainerArcaneInscriber.DISCHARGE[i]] != null) { 580 | ++this.inscriberInventory[ContainerArcaneInscriber.DISCHARGE[i]].stackSize; 581 | } 582 | else { 583 | ItemStack discharge = new ItemStack(ArcaneLegacy.runeBasic,1,this.inscriberInventory[ContainerArcaneInscriber.INPUT[i]].getItemDamage()); 584 | this.inscriberInventory[ContainerArcaneInscriber.DISCHARGE[i]] = discharge.copy(); 585 | } 586 | 587 | if (this.inscriberInventory[ContainerArcaneInscriber.INPUT[i]].stackSize == 0) 588 | { 589 | this.inscriberInventory[ContainerArcaneInscriber.INPUT[i]] = this.inscriberInventory[ContainerArcaneInscriber.INPUT[i]].getItem().getContainerItemStack(inscriberInventory[ContainerArcaneInscriber.INPUT[i]]); 590 | } 591 | } 592 | } 593 | } 594 | } 595 | 596 | // Everything's good to go, so increment furnace progress until it reaches the required time to smelt your item(s) 597 | if (this.isInscribing() && this.canInscribe()) 598 | { 599 | ++this.inscribeProgressTime; 600 | if (this.inscribeProgressTime == INSCRIBE_TIME) 601 | { 602 | this.inscribeProgressTime = 0; 603 | this.inscribeScroll(); 604 | flag1 = true; 605 | } 606 | } 607 | else 608 | { 609 | this.inscribeProgressTime = 0; 610 | } 611 | 612 | if (flag != this.currentInscribeTime > 0) 613 | { 614 | flag1 = true; 615 | BlockArcaneInscriber.updateInscriberBlockState(this.currentInscribeTime > 0, this.worldObj, this.xCoord, this.yCoord, this.zCoord); 616 | } 617 | } 618 | 619 | if (flag1) 620 | { 621 | this.onInventoryChanged(); 622 | } 623 | } 624 | 625 | /** 626 | * Returns true if the inscriber can inscribe a scroll; 627 | * i.e. has a blank scroll, has a charged rune, destination stack isn't full, etc. 628 | * This method will look very different for every furnace depending on whatever requirements 629 | * you decide are necessary for your furnace to smelt an item or items 630 | */ 631 | private boolean canInscribe() 632 | { 633 | boolean canInscribe = true; 634 | // Still time remaining to inscribe current recipe 635 | if (this.isInscribing() && this.inscriberInventory[ContainerArcaneInscriber.RECIPE] != null) 636 | { 637 | canInscribe = (this.inscriberInventory[ContainerArcaneInscriber.BLANK_SCROLL] == null ? false : true); 638 | } 639 | // No charged rune in first input slot 640 | else if (this.inscriberInventory[ContainerArcaneInscriber.INPUT[0]] == null) 641 | { 642 | canInscribe = false; 643 | } 644 | // No blank scrolls to inscribe 645 | else if (this.inscriberInventory[ContainerArcaneInscriber.BLANK_SCROLL] == null) 646 | { 647 | canInscribe = false; 648 | } 649 | // Check if any of the discharge slots are full 650 | else 651 | { 652 | for (int i = 0; i < ContainerArcaneInscriber.RUNE_SLOTS && canInscribe; ++i) 653 | { 654 | if (this.inscriberInventory[ContainerArcaneInscriber.DISCHARGE[i]] != null) 655 | { 656 | // Check if input[i] and discharge[i] are mismatched 657 | if (this.inscriberInventory[ContainerArcaneInscriber.INPUT[i]] != null) 658 | { 659 | canInscribe = ((this.inscriberInventory[ContainerArcaneInscriber.INPUT[i]].getItemDamage() == this.inscriberInventory[ContainerArcaneInscriber.DISCHARGE[i]].getItemDamage()) 660 | && this.inscriberInventory[ContainerArcaneInscriber.DISCHARGE[i]].stackSize < this.inscriberInventory[ContainerArcaneInscriber.DISCHARGE[i]].getMaxStackSize()); 661 | } 662 | else 663 | { 664 | canInscribe = this.inscriberInventory[ContainerArcaneInscriber.DISCHARGE[i]].stackSize < this.inscriberInventory[ContainerArcaneInscriber.DISCHARGE[i]].getMaxStackSize(); 665 | } 666 | } 667 | } 668 | } 669 | 670 | // If all of your above requirements are met, then on to the final check. Most of these should 671 | // be the same as I have here regardless of furnace type, but notice I have an extra check for 672 | // a null itemstack because the smelting result might be stored in my recipe slot even if all 673 | // other slots in the furnace are empty. 674 | if (canInscribe) { 675 | ItemStack itemstack = getCurrentRecipe(); 676 | if (itemstack == null) { itemstack = this.inscriberInventory[ContainerArcaneInscriber.RECIPE]; } 677 | // Invalid recipe 678 | if (itemstack == null) return false; 679 | // Recipe is different from the current recipe 680 | if (this.inscriberInventory[ContainerArcaneInscriber.RECIPE] != null && !this.inscriberInventory[ContainerArcaneInscriber.RECIPE].isItemEqual(itemstack)) return false; 681 | // Output slot is empty, inscribe away! 682 | if (this.inscriberInventory[ContainerArcaneInscriber.OUTPUT] == null) return true; 683 | // Current scroll in output slot is different than recipe output 684 | if (!this.inscriberInventory[ContainerArcaneInscriber.OUTPUT].isItemEqual(itemstack)) return false; 685 | // Inscribing may surpass stack size limit 686 | int result = inscriberInventory[ContainerArcaneInscriber.OUTPUT].stackSize + itemstack.stackSize; 687 | return (result <= getInventoryStackLimit() && result <= itemstack.getMaxStackSize()); 688 | } 689 | else 690 | { 691 | return canInscribe; 692 | } 693 | } 694 | 695 | // If you have more than one output per recipe, this will return an ItemStack[] array instead 696 | public ItemStack getCurrentRecipe() { 697 | eturn SpellRecipes.spells().getInscribingResult(this.inscriberInventory); 698 | } 699 | 700 | /** 701 | * Inscribe a blank scroll with the last current recipe 702 | */ 703 | public void inscribeScroll() 704 | { 705 | if (this.canInscribe()) 706 | { 707 | // If you had multiple outputs, this would be an ItemStack[] array that you would 708 | // then need to iterate through, checking if each index was null and if not, 709 | // finding the correct output slot to try and add to 710 | ItemStack inscribeResult = this.inscriberInventory[ContainerArcaneInscriber.RECIPE]; 711 | 712 | if (inscribeResult != null) 713 | { 714 | if (this.inscriberInventory[ContainerArcaneInscriber.OUTPUT] == null) 715 | { 716 | this.inscriberInventory[ContainerArcaneInscriber.OUTPUT] = inscribeResult.copy(); 717 | } 718 | else if (this.inscriberInventory[ContainerArcaneInscriber.OUTPUT].isItemEqual(inscribeResult)) 719 | { 720 | inscriberInventory[ContainerArcaneInscriber.OUTPUT].stackSize += inscribeResult.stackSize; 721 | } 722 | 723 | // This is where you'd decrement all your INPUT slots, but for me, I only need to do that for 724 | // BLANK_SCROLL since I used my INPUT as fuel earlier 725 | --this.inscriberInventory[ContainerArcaneInscriber.BLANK_SCROLL].stackSize; 726 | 727 | if (this.inscriberInventory[ContainerArcaneInscriber.BLANK_SCROLL].stackSize <= 0) 728 | { 729 | this.inscriberInventory[ContainerArcaneInscriber.BLANK_SCROLL] = null; 730 | } 731 | } 732 | } 733 | } 734 | 735 | /** 736 | * Returns the number of ticks that the supplied rune will keep 737 | * the inscriber running, or 0 if the rune isn't charged 738 | */ 739 | public static int getInscriberChargeTime(ItemStack rune) 740 | { 741 | if (rune != null && rune.itemID == ArcaneLegacy.runeCharged.itemID) { 742 | return RUNE_CHARGE_TIME; 743 | } else { return 0; } 744 | } 745 | 746 | /** 747 | * Return true if item is an energy source (i.e. a charged rune) 748 | */ 749 | public static boolean isSource(ItemStack itemstack) 750 | { 751 | return getInscriberChargeTime(itemstack) > 0; 752 | } 753 | 754 | @Override 755 | public boolean isUseableByPlayer(EntityPlayer player) 756 | { 757 | return worldObj.getBlockTileEntity(xCoord, yCoord, zCoord) == this && 758 | player.getDistanceSq(xCoord + 0.5, yCoord + 0.5, zCoord + 0.5) < 64; 759 | } 760 | 761 | @Override 762 | public void openChest() {} 763 | 764 | @Override 765 | public void closeChest() {} 766 | 767 | /** 768 | * Returns true if automation is allowed to insert the given stack (ignoring stack size) into the given slot. 769 | */ 770 | public boolean isItemValidForSlot(int slot, ItemStack itemstack) 771 | { 772 | boolean isValid = false; 773 | 774 | if (slot >= ContainerArcaneInscriber.INPUT[0] && slot <= ContainerArcaneInscriber.INPUT[ContainerArcaneInscriber.RUNE_SLOTS-1]) 775 | { 776 | isValid = itemstack.getItem().itemID == ArcaneLegacy.runeCharged.itemID; 777 | } 778 | else if (slot == ContainerArcaneInscriber.BLANK_SCROLL) 779 | { 780 | isValid = itemstack.getItem().itemID == ArcaneLegacy.scrollBlank.itemID; 781 | } 782 | return isValid; 783 | } 784 | 785 | /** 786 | * Returns an array containing the indices of the slots that can be accessed by automation on the given side of this 787 | * block. 788 | */ 789 | public int[] getAccessibleSlotsFromSide(int par1) 790 | { 791 | return par1 == 0 ? slots_bottom : (par1 == 1 ? slots_top : slots_sides); 792 | } 793 | 794 | /** 795 | * Returns true if automation can insert the given item in the given slot from the given side. 796 | * Args: Slot, item, side 797 | */ 798 | public boolean canInsertItem(int par1, ItemStack par2ItemStack, int par3) 799 | { 800 | return this.isItemValidForSlot(par1, par2ItemStack); 801 | } 802 | 803 | /** 804 | * Returns true if automation can extract the given item in the given slot from the given side. 805 | * Args: Slot, item, side 806 | */ 807 | public boolean canExtractItem(int slot, ItemStack itemstack, int side) 808 | { 809 | return (slot == ContainerArcaneInscriber.OUTPUT || (slot >= ContainerArcaneInscriber.DISCHARGE[0] && slot <= ContainerArcaneInscriber.DISCHARGE[ContainerArcaneInscriber.RUNE_SLOTS-1])); 810 | } 811 | 812 | @Override 813 | public void readFromNBT(NBTTagCompound tagCompound) 814 | { 815 | super.readFromNBT(tagCompound); 816 | NBTTagList nbttaglist = tagCompound.getTagList("Items"); 817 | this.inscriberInventory = new ItemStack[this.getSizeInventory()]; 818 | 819 | for (int i = 0; i < nbttaglist.tagCount(); ++i) 820 | { 821 | NBTTagCompound nbttagcompound1 = (NBTTagCompound)nbttaglist.tagAt(i); 822 | int b0 = nbttagcompound1.getInteger("Slot"); 823 | 824 | f (b0 >= 0 && b0 < this.inscriberInventory.length) 825 | { 826 | this.inscriberInventory[b0] = ItemStack.loadItemStackFromNBT(nbttagcompound1); 827 | } 828 | } 829 | 830 | this.currentInscribeTime = tagCompound.getShort("IncribeTime"); 831 | this.inscribeProgressTime = tagCompound.getShort("InscribeProgress"); 832 | // this.inscribeTime = INSCRIBE_TIME; 833 | 834 | if (tagCompound.hasKey("CustomName")) 835 | { 836 | this.displayName = tagCompound.getString("CustomName"); 837 | } 838 | } 839 | 840 | @Override 841 | public void writeToNBT(NBTTagCompound tagCompound) 842 | { 843 | super.writeToNBT(tagCompound); 844 | tagCompound.setShort("InscribeTime", (short)this.currentInscribeTime); 845 | tagCompound.setShort("InscribeProgress", (short)this.inscribeProgressTime); 846 | NBTTagList nbttaglist = new NBTTagList(); 847 | 848 | for (int i = 0; i < this.inscriberInventory.length; ++i) 849 | { 850 | if (this.inscriberInventory[i] != null) 851 | { 852 | NBTTagCompound nbttagcompound1 = new NBTTagCompound(); 853 | nbttagcompound1.setInteger("Slot", i); 854 | this.inscriberInventory[i].writeToNBT(nbttagcompound1); 855 | nbttaglist.appendTag(nbttagcompound1); 856 | } 857 | } 858 | 859 | tagCompound.setTag("Items", nbttaglist); 860 | 861 | if (this.isInvNameLocalized()) 862 | { 863 | tagCompound.setString("CustomName", this.displayName); 864 | } 865 | } 866 | } 867 | 868 | /** 869 | * Step 4: Your Recipe class 870 | */ 871 | /* 872 | This is where much of the magic happens 873 | We'll use HashMaps to store an array of item metadata values to return an ItemStack 874 | result. If the items in your recipes will have different Item IDs, then add those 875 | to the hashmap as well. I only needed metadata values because all my recipes only 876 | involve a single rune item with many subtypes. 877 | */ 878 | public class SpellRecipes 879 | { 880 | private static final SpellRecipes spells = new SpellRecipes(); 881 | // This creates a HashMap whose Key is a specific, ordered List of Integers 882 | // If you want multiple outputs from one recipe, change the ItemStack to an ItemStack[] 883 | // (and of course adjust your TileEntity and Container code) 884 | private HashMap, ItemStack> metaInscribingList = new HashMap, ItemStack>(); 885 | // Same as above except it gives us the experience for each crafting result 886 | private HashMap, Float> metaExperience = new HashMap, Float>(); 887 | 888 | /** 889 | * Used to call methods addInscribing and getInscribingResult. 890 | */ 891 | public static final SpellRecipes spells() { 892 | return spells; 893 | } 894 | 895 | /** 896 | * Adds all recipes to the HashMap 897 | */ 898 | private SpellRecipes() 899 | { 900 | // Note that I have defined constant integer values in my ItemRune class 901 | // such that ItemRune.RUNE_NAME returns the appropriate int value for the 902 | // corresponding metadata 903 | 904 | // This one only takes 2 Items to craft: 905 | this.addInscribing(Arrays.asList(ItemRune.RUNE_CREATE,ItemRune.RUNE_FIRE),new ItemStack(ArcaneLegacy.scrollCombust), 0.3F); 906 | 907 | // This one takes 7 Items to craft (the max number of slots currently in my Arcane Inscriber, but I could easily add more): 908 | this.addInscribing(Arrays.asList(ItemRune.RUNE_AUGMENT,ItemRune.RUNE_AUGMENT,ItemRune.RUNE_CREATE,ItemRune.RUNE_AUGMENT,ItemRune.RUNE_LIFE,ItemRune.RUNE_SPACE,ItemRune.RUNE_TIME),new ItemStack(ArcaneLegacy.scrollHealAuraI), 1.0F); 909 | 910 | // Here's a generic format for adding both item ID and metadata: 911 | this.addInscribing(Arrays.asList(Item1.itemID, metadata1, Item2.itemID, metadata2, ... etc.), new ItemStack(craftResult.itemID, stacksize, metadata), XP); 912 | 913 | // You could also skip the addInscribing method and add directly to the HashMap: 914 | metaInscribingList.put(Arrays.asList(Item1.itemID, meta1, Item2.itemID, meta2,... ItemN.itemID, metaN), ItemStack(craftResult.itemID,stacksize,metadata)); 915 | // Note that XP must be added each time as well, effectively doubling the lines of code in this method 916 | metaExperience.put(Arrays.asList(ItemResult.itemID, ItemResult.getItemDamage()), experience); 917 | } 918 | 919 | /** 920 | * Adds an array of runes, the resulting scroll, and experience given 921 | */ 922 | public void addInscribing(List runes, ItemStack scroll, float experience) 923 | { 924 | // Check if recipe already exists and print conflict information: 925 | if (metaInscribingList.containsKey(runes)) 926 | { 927 | System.out.println("[WARNING] Conflicting recipe: " + runes.toString() + " for " + metaInscribingList.get(runes).toString()); 928 | } 929 | else 930 | { 931 | // Add new recipe to the HashMap... wow, it looks so simple like this :) 932 | metaInscribingList.put(runes, scroll); 933 | metaExperience.put(Arrays.asList(scroll.itemID, scroll.getItemDamage()), experience); 934 | } 935 | } 936 | 937 | /** 938 | * Used to get the resulting ItemStack form a source inventory (fed to it by the contents of the slots in your container) 939 | * @param item The Source inventory from your custom furnace input slots 940 | * @return The result ItemStack (NOTE: if you want multiple outputs, change to ItemStack[] and adjust accordingly) 941 | */ 942 | public ItemStack getInscribingResult(ItemStack[] runes) 943 | { 944 | // count the recipe length so we can make the appropriate sized array 945 | int recipeLength = 0; 946 | for (int i = 0; i < runes.length && runes[i] != null && i < ContainerArcaneInscriber.RUNE_SLOTS; ++i) 947 | { 948 | // +1 for metadata value of itemstack, add another +1 if you also need the itemID 949 | ++recipeLength; 950 | } 951 | // make the array and fill it with the integer values from the passed in ItemStacks 952 | // Note that I'm only using the metadata value as all my runes have the same itemID 953 | Integer[] idIndex = new Integer[recipeLength]; 954 | for (int i = 0; i < recipeLength; ++i) { 955 | // if you need itemID as well put this: 956 | // idIndex[i] = (Integer.valueOf(runes[i].itemID)); 957 | // be sure to increment i before you do the metadata if you added an itemID 958 | idIndex[i] = (Integer.valueOf(runes[i].getItemDamage())); 959 | } 960 | // And use it as the key to get the correct result from the HashMap: 961 | return (ItemStack) metaInscribingList.get(Arrays.asList(idIndex)); 962 | } 963 | 964 | /** 965 | * Grabs the amount of base experience for this item to give when pulled from the furnace slot. 966 | */ 967 | public float getExperience(ItemStack item) 968 | { 969 | if (item == null || item.getItem() == null) 970 | { 971 | return 0; 972 | } 973 | float ret = -1; // value returned by "item.getItem().getSmeltingExperience(item);" when item doesn't specify experience to give 974 | if (ret < 0 && metaExperience.containsKey(Arrays.asList(item.itemID, item.getItemDamage()))) 975 | { 976 | ret = metaExperience.get(Arrays.asList(item.itemID, item.getItemDamage())); 977 | } 978 | 979 | return (ret < 0 ? 0 : ret); 980 | } 981 | 982 | public Map, ItemStack> getMetaInscribingList() 983 | { 984 | return metaInscribingList; 985 | } 986 | } 987 | /* 988 | And that's it! Congratulations, you can now make a ridiculously flexible furnace. 989 | 990 | HashMaps are our friend :D 991 | */ 992 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Forge_Tutorials 2 | =============== 3 | 4 | Welcome to coolAlias' Minecraft Forge Modding Tutorials! 5 | 6 | I've tried to add tutorials for subjects that I either haven't seen elsewhere, needed updating or just needed 7 | further explanation. 8 | 9 | Most of my tutorials are NOT set up like full proper code and WILL give you lots of errors if you just 10 | copy and paste it into Eclipse. 11 | 12 | That being said, all of the code I provide I have personally used and tested, so if you read all the comments, 13 | follow the directions and do your best to understand what the code is doing, I'm confident you, too, will be 14 | able to get it working. 15 | 16 | I'm by no means, however, an expert in Java, so if you are and spot errors in my code or just have a better way 17 | of doing it, then please let me know! 18 | 19 | If you have any suggestions on how to make the tutorials more clear, if you have a question or if you just want to 20 | say hi, leave a comment or p.m. me on minecraftforum.net and I'll see what I can do :) 21 | 22 | If you'd prefer to read the tutorials on the forums, here are the links: 23 | 24 | - EventHandler and IExtendedEntityProperties: 25 | http://www.minecraftforum.net/topic/1952901-making-an-eventhandler-and-event-type-explanations/ 26 | 27 | - Creating an Item that stores an Inventory: 28 | http://www.minecraftforum.net/topic/1949352-creating-an-item-that-stores-an-inventory/ 29 | 30 | - Rendering Your Custom Item Texture: 31 | http://www.minecraftforum.net/topic/1892995-rendering-your-custom-item-texture/ 32 | 33 | - Multi-Input/Output Furnace with Variable-Input Recipes: 34 | http://www.minecraftforum.net/topic/1961477-multi-inputoutput-furnace-with-variable-input-recipes/ 35 | 36 | - Overriding shift-click in custom Containers: 37 | http://www.minecraftforum.net/topic/1919622-custom-container-how-to-properly-override-shift-clicking/ 38 | 39 | - Using Potions in Crafting Recipes: 40 | http://www.minecraftforum.net/topic/1891579-using-potions-in-crafting-recipes/ 41 | 42 | - Enchanted Books and Crafting Recipes: 43 | http://www.minecraftforum.net/topic/1893462-enchanted-book-crafting-recipes-an-intro-to-nbt-tag-compounds/ 44 | -------------------------------------------------------------------------------- /RenderingCustomItemEntity.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Rendering a Custom Item Texture 3 | */ 4 | /* 5 | In this tutorial, we will make a custom, Item-based projectile, a throwing rock. It covers all the steps 6 | necessary to add any projectile entity into the game, including how to get it to render on the screen properly. 7 | 8 | For this tutorial, we're going to make a new item ThrowingRock and get it to render 9 | a custom texture when thrown. We'll assume you've got your main mod space set up. 10 | If not, I HIGHLY suggest you go over to TechGuy543's tutorial at 11 | http://www.minecraftforum.net/topic/960286-techguys-modding-tutorials/ 12 | and make sure you set up your main mod correctly or you will NOT be able to get 13 | your custom item rendering. 14 | */ 15 | 16 | /** 17 | * Step 1: Creating a custom Item, e.g. ItemThrowingRock 18 | */ 19 | /* 20 | Like I said, we're going to make a ThrowingRock. To do this, we just make a new 21 | class called ThrowingRock in one of our packages. This class needs to exend Item, 22 | but we're going to make it extend another class, BaseModItem, which extends Item. 23 | The magic of inheritance will allow our ThrowingRock to extend Item through 24 | BaseModItem. Here is BaseModItem's code. 25 | */ 26 | public class BaseModItem extends Item 27 | { 28 | // no more IDs 29 | public BaseModItem() { 30 | super(); 31 | } 32 | 33 | // IconRegister renamed to IIconRegister 34 | @Override 35 | @SideOnly(Side.CLIENT) 36 | public void registerIcons(IIconRegister iconRegister) { 37 | itemIcon = iconRegister.registerIcon("tutorial:" + getUnlocalizedName().substring(5).toLowerCase()); 38 | } 39 | } 40 | /* 41 | This allows us to avoid re-writing the registerIcon function in every single new 42 | item we make. Nifty. Now here's what ItemThrowingRock will look like. 43 | */ 44 | public class ItemThrowingRock extends BaseModItem 45 | { 46 | // no more ID parameter 47 | public ItemThrowingRock() 48 | { 49 | super(); 50 | // we'll set the max stack size and creative tab from here: 51 | setMaxStackSize(18); 52 | setCreativeTab(CreativeTabs.tabCombat); 53 | } 54 | } 55 | /* 56 | We set the maxStackSize and CreativeTab in the constructor above, but you could also 57 | do it when you declare the item in your main mod instance: 58 | */ 59 | 60 | public static Item throwingRock; 61 | 62 | // ALL Blocks, Items, etc. must be initialized during FML Pre-Initialization Event 63 | @EventHandler 64 | public void preInit(FMLPreInitializationEvent event) { 65 | throwingRock = new ItemThrowingRock().setUnlocalizedName("throwingRock"). 66 | setMaxStackSize(18).setCreativeTab(CreativeTabs.tabCombat); 67 | } 68 | 69 | /* 70 | So far our ThrowingRock doesn't do anything. We want this class to throw rocks, much 71 | like snowballs, so we'll steal ItemSnowball's onImpact function and paste it into our 72 | ItemThrowingRock class: 73 | */ 74 | /** 75 | * Called whenever this item is equipped and the right mouse button is pressed. Args: itemStack, world, entityPlayer 76 | */ 77 | public ItemStack onItemRightClick(ItemStack itemstack, World world, EntityPlayer player) 78 | { 79 | if (!player.capabilities.isCreativeMode) 80 | { 81 | --itemstack.stackSize; 82 | } 83 | 84 | world.playSoundAtEntity(player, "random.bow", 0.5F, 0.4F / (itemRand.nextFloat() * 0.4F + 0.8F)); 85 | 86 | // IMPORTANT! Only spawn new entities on the server. If the world is not remote, 87 | // that means you are on the server: 88 | if (!world.isRemote) 89 | { 90 | world.spawnEntityInWorld(new EntitySnowball(world, player)); 91 | } 92 | 93 | return itemstack; 94 | } 95 | /* 96 | Ok, run the code and you'll see your ThrowingRocks spawn snowballs. That's not what 97 | we want. Change "EntitySnowball" to "EntityThrowingRock." You'll get an error, but 98 | we'll fix that in step 2. 99 | */ 100 | 101 | /** 102 | * Step 2: Creating a custom Entity: EntityThrowingRock 103 | */ 104 | // Ok, we want our ThrowingRock to behave similarly to snowballs, so let's look at that class. 105 | public class EntitySnowball extends EntityThrowable 106 | { 107 | public EntitySnowball(World par1World) 108 | { 109 | super(par1World); 110 | } 111 | public EntitySnowball(World par1World, EntityLivingBase par2EntityLivingBase) 112 | { 113 | super(par1World, par2EntityLivingBase); 114 | } 115 | public EntitySnowball(World par1World, double par2, double par4, double par6) 116 | { 117 | super(par1World, par2, par4, par6); 118 | } 119 | /** 120 | * Called when this EntityThrowable hits a block or entity. 121 | */ 122 | protected void onImpact(MovingObjectPosition par1MovingObjectPosition) 123 | { 124 | if (par1MovingObjectPosition.entityHit != null) 125 | { 126 | byte b0 = 0; 127 | if (par1MovingObjectPosition.entityHit instanceof EntityBlaze) 128 | { 129 | b0 = 3; 130 | } 131 | 132 | par1MovingObjectPosition.entityHit.attackEntityFrom(DamageSource.causeThrownDamage(this, this.getThrower()), (float)b0); 133 | } 134 | for (int i = 0; i < 8; ++i) 135 | { 136 | this.worldObj.spawnParticle("snowballpoof", this.posX, this.posY, this.posZ, 0.0D, 0.0D, 0.0D); 137 | } 138 | if (!this.worldObj.isRemote) 139 | { 140 | this.setDead(); 141 | } 142 | } 143 | } 144 | /* 145 | Well, we can just copy and paste this into our mod package and rename it to 146 | EntityThrowingRock. Perfect. Here are some ways you can tweak it to make it more 147 | rock-like: 148 | 149 | Tweak 1: Do you see "byte b0" in the code? That's how much damage our entity will inflict. For snowballs, it is zero unless you hit a blaze. Well, we don't need that, so delete all that stuff about the blaze as well as the related import. Let's rename "byte b0" to "float rockDamage." Now it's obvious what it does. Now you have to change "(float)b0" to "rockDamage." 150 | Tweak 2: Change the damage. Right now, it says "float rockDamage = 0"; let's change it to 2 so as not to make it too powerful, but you can change it to whatever you want. 151 | Tweak 3: Our rock spawns snowballpoofs on impact. Lame. Just change "snowballpoof" to "crit" for now. It won't look awesome, but it's better than snow. 152 | 153 | Here's what our new EntityThrowingRock code looks like: 154 | */ 155 | public class EntityThrowingRock extends EntityThrowable 156 | { 157 | public EntityThrowingRock(World par1World) 158 | { 159 | super(par1World); 160 | } 161 | public EntityThrowingRock(World par1World, EntityLivingBase par2EntityLivingBase) 162 | { 163 | super(par1World, par2EntityLivingBase); 164 | // TODO Auto-generated constructor stub 165 | } 166 | public EntityThrowingRock(World par1World, double par2, double par4, double par6) 167 | { 168 | super(par1World, par2, par4, par6); 169 | // TODO Auto-generated constructor stub 170 | } 171 | /** 172 | * Called when this EntityThrowable hits a block or entity. 173 | */ 174 | @Override 175 | protected void onImpact(MovingObjectPosition par1MovingObjectPosition) 176 | { 177 | if (par1MovingObjectPosition.entityHit != null) 178 | { 179 | float rockDamage = 2; 180 | par1MovingObjectPosition.entityHit.attackEntityFrom(DamageSource.causeThrownDamage(this, this.getThrower()), rockDamage); 181 | } 182 | for (int l = 0; l < 4; ++l) 183 | { 184 | this.worldObj.spawnParticle("crit", this.posX, this.posY, this.posZ, 0.0D, 0.0D, 0.0D); 185 | } 186 | if (!this.worldObj.isRemote) 187 | { 188 | this.setDead(); 189 | } 190 | } 191 | } 192 | /* 193 | Note the "@Override" before our onImpact function - it's not critical here, as EntityThrowable.onImpact() 194 | doesn't have anything in it, but it's good practice to put this here; if the method name or signature ever 195 | changes in a Minecraft update, you will know immediately because your old method will give you an error. 196 | This means you need to find the new method signature and change your method name - simply removing @Override 197 | will get rid of the error, too, but then your method will never be called! 198 | */ 199 | 200 | /** 201 | * Step 3: Registering your custom entity and renderer. 202 | */ 203 | /* 204 | We've set up our custom Entity class, but Minecraft doesn't know about it yet. So we have to let it know in using 205 | EntityRegistry.registerModEntity in our main mod load method and tell it what renderer to use using 206 | RenderingRegistry.registerEntityRenderingHandler in our ClientProxy. Here's how: 207 | */ 208 | @EventHandler 209 | public void preInit(FMLPreInitializationEvent event) 210 | { 211 | // Config, Blocks, Items go here: 212 | throwingRock = new ItemThrowingRock().setUnlocalizedName("throwingRock"); 213 | 214 | 215 | // I like using an incrementable index to set my IDs rather than writing 1, 2, 3, etc., so I never have 216 | // to worry about order or if I missed a number (doesn't really matter though) 217 | int modEntityID = 0; 218 | 219 | // If you have a lot of Entities to register, consider creating a class with a static 'initEntity' method 220 | // so your main class stays tidy and readable 221 | EntityRegistry.registerModEntity(EntityThrowingRock.class, "Throwing Rock", ++modEntityID, this, 64, 10, true); 222 | 223 | // Now that we've registered the entity, tell the proxy to register the renderer 224 | proxy.registerRenderers(); 225 | } 226 | // Now to the ClientProxy: 227 | public class ClientProxy extends CommonProxy 228 | { 229 | @Override 230 | public void registerRenderers() 231 | { 232 | RenderingRegistry.registerEntityRenderingHandler(EntityThrowingRock.class, new RenderSnowball(YourModName.throwingRock)); 233 | } 234 | } 235 | /* 236 | registerModEntity(...) takes the class, a name (just make it up here), a unique ID, 237 | the instance of YourMod, tracking range, frequency of tracking updates, and whether 238 | to send velocity information. 239 | 240 | RenderSnowball(...) requires the item to be rendered as an argument; since we haven't 241 | declared any items locally, we use the declaration from the instance of our mod. 242 | 243 | NOTE: See this thread http://www.minecraftforum.net/topic/1417041-mod-entity-problem-updated-with-forge/page__st__140#entry18822284 244 | for an explanation of Mod Entity vs. Global Entity IDs. 245 | 246 | NOTE: A good reference to decide what tracking update frequency to use: 247 | https://docs.google.com/spreadsheet/pub?key=0Ap8gssssFFPAdFRXREZGSzZRY3k1WE8wcUE4S09xWXc&single=true&gid=0&output=html 248 | (from the post mentioned in the previous note). For projectiles, ranges of 10-20 are the norm. 249 | 250 | NOTE: If you want your custom item to render differently from a vanilla item, you will 251 | need to make a custom render class. See the next step. 252 | 253 | Your custom entity should now render correctly in the world! Congratulations! 254 | */ 255 | 256 | /** 257 | * Step 4: Rendering: The Power of Inheritance 258 | */ 259 | /* 260 | Okay, so you've got everything working correctly, but you want your Item to exhibit 261 | some custom behavior when it renders. We need to create a new Render class. The easiest 262 | way to do this is to copy from an existing Renderer and paste it into your new class. 263 | In our case, ThrowingRock behaves like Snowballs, so we will copy RenderSnowball and 264 | rename it to RenderThrowingRock: 265 | */ 266 | @SideOnly(Side.CLIENT) 267 | public class RenderThrowingRock extends Render 268 | { 269 | private Item field_94151_a; 270 | private int field_94150_f; 271 | 272 | public RenderThrowingRock(Item par1Item, int par2) 273 | { 274 | this.field_94151_a = par1Item; 275 | this.field_94150_f = par2; 276 | } 277 | 278 | public RenderThrowingRock(Item par1Item) 279 | { 280 | this(par1Item, 0); 281 | } 282 | 283 | public void doRender(Entity par1Entity, double par2, double par4, double par6, float par8, float par9) 284 | { 285 | Icon icon = this.field_94151_a.getIconFromDamage(this.field_94150_f); 286 | 287 | if (icon != null) 288 | { 289 | GL11.glPushMatrix(); 290 | GL11.glTranslatef((float)par2, (float)par4, (float)par6); 291 | GL11.glEnable(GL12.GL_RESCALE_NORMAL); 292 | GL11.glScalef(0.5F, 0.5F, 0.5F); 293 | 294 | // this.func_110777_b(par1Entity); // worked in Forge 804, but no longer; use this: 295 | this.bindEntityTexture(par1Entity); 296 | 297 | Tessellator tessellator = Tessellator.instance; 298 | 299 | if (icon == ItemPotion.func_94589_d("bottle_splash")) 300 | { 301 | int i = PotionHelper.func_77915_a(((EntityPotion)par1Entity).getPotionDamage(), false); 302 | float f2 = (float)(i >> 16 & 255) / 255.0F; 303 | float f3 = (float)(i >> 8 & 255) / 255.0F; 304 | float f4 = (float)(i & 255) / 255.0F; 305 | GL11.glColor3f(f2, f3, f4); 306 | GL11.glPushMatrix(); 307 | this.func_77026_a(tessellator, ItemPotion.func_94589_d("overlay")); 308 | GL11.glPopMatrix(); 309 | GL11.glColor3f(1.0F, 1.0F, 1.0F); 310 | } 311 | 312 | this.func_77026_a(tessellator, icon); 313 | GL11.glDisable(GL12.GL_RESCALE_NORMAL); 314 | GL11.glPopMatrix(); 315 | } 316 | } 317 | 318 | @Override 319 | protected ResourceLocation getEntityTexture(Entity entity) { 320 | return TextureMap.locationItemsTexture; 321 | } 322 | 323 | 324 | private void func_77026_a(Tessellator par1Tessellator, Icon par2Icon) 325 | { 326 | float f = par2Icon.getMinU(); 327 | float f1 = par2Icon.getMaxU(); 328 | float f2 = par2Icon.getMinV(); 329 | float f3 = par2Icon.getMaxV(); 330 | float f4 = 1.0F; 331 | float f5 = 0.5F; 332 | float f6 = 0.25F; 333 | GL11.glRotatef(180.0F - this.renderManager.playerViewY, 0.0F, 1.0F, 0.0F); 334 | GL11.glRotatef(-this.renderManager.playerViewX, 1.0F, 0.0F, 0.0F); 335 | par1Tessellator.startDrawingQuads(); 336 | par1Tessellator.setNormal(0.0F, 1.0F, 0.0F); 337 | par1Tessellator.addVertexWithUV((double)(0.0F - f5), (double)(0.0F - f6), 0.0D, (double)f, (double)f3); 338 | par1Tessellator.addVertexWithUV((double)(f4 - f5), (double)(0.0F - f6), 0.0D, (double)f1, (double)f3); 339 | par1Tessellator.addVertexWithUV((double)(f4 - f5), (double)(f4 - f6), 0.0D, (double)f1, (double)f2); 340 | par1Tessellator.addVertexWithUV((double)(0.0F - f5), (double)(f4 - f6), 0.0D, (double)f, (double)f2); 341 | par1Tessellator.draw(); 342 | } 343 | } 344 | 345 | /** 346 | Pure copy-paste. Genius. Be sure to import everything necessary. But now I'll let you in on an even BETTER way, 347 | that you should use whenever you can: make a new class that EXTENDS whatever class you want to emulate, rather 348 | than copying and pasting: 349 | */ 350 | @SideOnly(Side.CLIENT) 351 | public class RenderThrowingRock extends RenderSnowball 352 | { 353 | public RenderThrowingRock(Item item) { 354 | this(item, 0); 355 | } 356 | 357 | public RenderThrowingRock(Item item, int par2) { 358 | super(item, par2); 359 | } 360 | 361 | // now you can override the render methods if you want 362 | // call super to get the original functionality, and/or add some stuff of your own 363 | // I'll leave that up to you to experiment with 364 | } 365 | /** 366 | Wow, that's so much simpler. You barely have to do anything. Remember that for the future. Of course you don't 367 | really have to even create a Render class for the throwing rock, as you can just pass the Item directly to 368 | RenderSnowball when you register, but you can use this technique in many places, overriding methods to add new 369 | functionality while getting the benefits of the old. 370 | */ 371 | 372 | // Now, in our ClientProxy, we need to change this: 373 | RenderingRegistry.registerEntityRenderingHandler(EntityThrowingRock.class, new RenderSnowball(YourModName.throwingRock)); 374 | 375 | // to this: 376 | RenderingRegistry.registerEntityRenderingHandler(EntityThrowingRock.class, new RenderThrowingRock(YourModName.throwingRock)); 377 | 378 | // Or, if not using a custom render, use RenderSnowball with your Item as the parameter: 379 | RenderingRegistry.registerEntityRenderingHandler(EntityThrowingRock.class, new RenderSnowball(YourModName.throwingRock)); 380 | 381 | /** 382 | * Step 5: Rendering a Model 383 | */ 384 | /* 385 | If you want to do some different rendering than that offered by RenderSnowball, for example if you want a model, 386 | you will need to create a new class that extends Render, or one of Render's subclasses. 387 | 388 | You will need a ResourceLocation for your texture; this takes 2 parameters: ModId and the path to your texture at 389 | the location: "src/minecraft/assets/modid/" 390 | 391 | You can also set it with a single parameter, the string of the path to the texture: 392 | "mymodid:textures/entity/mytexture.png" or (mymodid + ":textures/entity/mytexture.png"). 393 | 394 | In any class that extends Render, or TileEntitySpecialRenderer, you can bind the texture simply by using 395 | "this.bindTexture(yourResourceLocation);" 396 | 397 | On to the class itself: 398 | */ 399 | @SideOnly(Side.CLIENT) 400 | public class RenderCustomEntity extends Render 401 | { 402 | // ResourceLocations are typically static and final, but that is not an absolute requirement 403 | private static final ResourceLocation texture = new ResourceLocation("yourmodid", "textures/entity/yourtexture.png"); 404 | 405 | // if you want a model, be sure to add it here: 406 | private ModelBase model; 407 | 408 | public RenderCustomEntity() { 409 | // we could have initialized it above, but here is fine as well: 410 | model = new ModelCustomEntity(); 411 | } 412 | 413 | @Override 414 | protected ResourceLocation getEntityTexture(Entity entity) { 415 | // this method should return your texture, which may be different based 416 | // on certain characteristics of your custom entity; if that is the case, 417 | // you may want to make a second method that takes your class: 418 | return getCustomTexture((CustomEntity) entity); 419 | } 420 | 421 | private ResourceLocation getCustomTexture(CustomEntity entity) { 422 | // now you have access to your custom entity fields and methods, if any, 423 | // and can base the texture to return upon those 424 | return texture; 425 | } 426 | 427 | // in whatever render method you are using; this one is from Render class: 428 | @Override 429 | public void doRender(Entity entity, double x, double y, double z, float yaw, float partialTick) { 430 | // again, if you need some information from your custom entity class, you can cast to your 431 | // custom class, either passing off to another method, or just doing it here 432 | // in this example, it is not necessary 433 | 434 | // if you are going to do any openGL matrix transformations, be sure to always Push and Pop 435 | GL11.glPushMatrix(); 436 | 437 | // bind your texture: 438 | bindTexture(texture); 439 | 440 | // do whatever transformations you need, then render 441 | 442 | // typically you will at least want to translate for x/y/z position: 443 | GL11.glTranslated(x, y, z); 444 | 445 | // if you are using a model, you can do so like this: 446 | model.render(entity, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0625F); 447 | 448 | // note all the values are 0 except the final argument, which is scale 449 | // vanilla Minecraft almost excusively uses 0.0625F, but you can change it to whatever works 450 | 451 | GL11.glPopMatrix(); 452 | } 453 | } 454 | 455 | /** 456 | * Step 6: HELP!!! A.k.a Common Problems and Troubleshooting. 457 | */ 458 | /* 459 | First, please read ALL sections of the above tutorial carefully. Then see if you have done all of the below: 460 | 461 | 1. Read ALL sections of the tutorial carefully. 462 | 2. Make sure you have created your texture using GIMP, Paint, or whatever and saved in .png format. 463 | 3. Make sure your texture is in the correct folder; for forge, it is: 464 | forge/mcp/src/minecraft/assets/yourmodid/textures/items/ 465 | 4. Make sure your folder "yourmodid" is all lower-case. 466 | 5. Re-read the tutorial. Did you follow ALL of the steps EXACTLY? 467 | 6. Did you follow TechGuy's tutorial (linked at the top) for setting up your main mod, 468 | CommonProxy and ClientProxy? 469 | 7. Is your modid the same throughout all of your code? See my tutorial on creating a ModInfo 470 | file to prevent common errors of this sort. 471 | 472 | Other problems will be noted below. I will update this section if anyone has further problems that aren't addressed 473 | elsewhere. 474 | 475 | PROBLEM: Null Pointer Exception at line 42 of your Render class 476 | at Icon icon = this.field_94151_a.getIconFromDamage(this.field_94150_f); 477 | 478 | SOLUTION: Be sure to finish initializing all Items before registering the Render class, otherwise the Item's icon 479 | will be null when the Render is registered, resulting in an NPE. 480 | 481 | */ 482 | -------------------------------------------------------------------------------- /StructureArrayTutorialPart1.java: -------------------------------------------------------------------------------- 1 | /* 2 | STRUCTURE GENERATOR TOOL TUTORIAL PART 1: YOUR FIRST STRUCTURE 3 | 4 | This tutorial is for use with my Structure Generation Tool, NOT regular minecraft structures. 5 | 6 | In this tutorial I will cover how to build your structure using an array from scratch. 7 | Please also refer to the instructions in 'StructureArrays.java' for more information. 8 | */ 9 | 10 | /* 11 | * Step 1: Create a new class and array for your structure 12 | */ 13 | /* 14 | Just create a completely empty class. We'll name it 'StructureArrayTutorial' for now. 15 | */ 16 | public class StructureArrayTutorial 17 | { 18 | 19 | } 20 | /* 21 | Yep, totally empty right now. Next, look in StructureArrays.java, find 'blockArrayTemplate', 22 | and copy and paste that into your class. It should now look like this: 23 | */ 24 | public class StructureArrayTutorial 25 | { 26 | public static final int[][][][] blockArrayTemplate = 27 | { 28 | { // y 29 | { // x 30 | {} // z 31 | } 32 | } 33 | }; 34 | } 35 | 36 | /* 37 | * Step 2: Create a basic structure 38 | */ 39 | /* 40 | In this step, we're going to create a 5x5 platform. Recall that the z axis runs 41 | north to south, and the x axis runs east to west. All we need to do is place a 42 | block at five consecutive points along the z axis, making a north-south column, 43 | and repeat this column five times along the x axis (east-west). 44 | */ 45 | public static final int[][][][] blockArrayTemplate = 46 | { 47 | { // y 48 | { // x 49 | // z: place 5 blocks here that will run north to south 50 | // This first z column is stored at x = 0 in our block array 51 | {Block.wood.blockID}, // here's one, be sure to add a comma ',' 52 | {Block.wood.blockID}, // 2 53 | {Block.wood.blockID}, // 3 54 | {Block.wood.blockID}, // 4 55 | {Block.wood.blockID} // 5 56 | // That finishes one column, but we need four more columns 57 | // The next column will be at x = 1 58 | }, // Again, be sure to add a comma ',' 59 | { // This is the start of x = 1 60 | // The brackets within start off the z array once again 61 | // Just copy and paste from above! 62 | {Block.wood.blockID}, // here's one, be sure to add a comma ',' 63 | {Block.wood.blockID}, // 2 64 | {Block.wood.blockID}, // 3 65 | {Block.wood.blockID}, // 4 66 | {Block.wood.blockID} // 5 67 | // Just like that! Now copy an entire x subsection and paste it 68 | // 3 more times. Don't forget to add that comma! 69 | }, 70 | { // This is the start of x = 1 71 | // The brackets within start off the z array once again 72 | // Just copy and paste from above! 73 | {Block.wood.blockID}, // here's one, be sure to add a comma ',' 74 | {Block.wood.blockID}, // 2 75 | {Block.wood.blockID}, // 3 76 | {Block.wood.blockID}, // 4 77 | {Block.wood.blockID} // 5 78 | // Just like that! Now copy an entire x subsection and paste it 79 | // 3 more times. Don't forget to add that comma! 80 | }, 81 | { // This is the start of x = 1 82 | // The brackets within start off the z array once again 83 | // Just copy and paste from above! 84 | {Block.wood.blockID}, // here's one, be sure to add a comma ',' 85 | {Block.wood.blockID}, // 2 86 | {Block.wood.blockID}, // 3 87 | {Block.wood.blockID}, // 4 88 | {Block.wood.blockID} // 5 89 | // Just like that! Now copy an entire x subsection and paste it 90 | // 3 more times. Don't forget to add that comma! 91 | }, 92 | { // This is the start of x = 1 93 | // The brackets within start off the z array once again 94 | // Just copy and paste from above! 95 | {Block.wood.blockID}, // here's one, be sure to add a comma ',' 96 | {Block.wood.blockID}, // 2 97 | {Block.wood.blockID}, // 3 98 | {Block.wood.blockID}, // 4 99 | {Block.wood.blockID} // 5 <-- Remove the last comma! 100 | // Just like that! Now copy an entire x subsection and paste it 101 | // 3 more times. Don't forget to add that comma! 102 | }, // <-- Remove the last comma, as there won't be another x sub-array here 103 | } 104 | }; // <-- There needs to be a semi-colon ';' at the end of your array 105 | /* 106 | Step 3: Add your structure to the StructureGenerator and test it out 107 | */ 108 | /* 109 | To allow your structure to be easily spawned by the ItemStructureSpawner, we 110 | need to add a new Structure to the list. Open the StructureGenerator class and 111 | go to the very bottom. There you'll find a static {...} initializer that adds 112 | all the current Structures. Add the following code after the last structure: 113 | */ 114 | // In StructureGenerator: 115 | static 116 | { 117 | // All the other structures are here, put your code last: 118 | structure = new Structure("Tutorial"); 119 | structure.addBlockArray(StructureArrayTutorial.blockArrayTutorial); 120 | structures.add(structure); 121 | } 122 | /* 123 | Easy as that! Note that I changed the name from 'blockArrayTemplate' to 124 | 'blockArrayTutorial'. You should do the same. Now launch Minecraft in Creative 125 | mode, grab yourself a hammer (ItemStructureSpawner), press '[' left bracket 126 | and your 'Tutorial' structure should be selected. Right click on a block to 127 | generate it. Well, that's not so great, is it? A 5x5 wooden platform. Yay. 128 | Let's make it better. 129 | */ 130 | /* 131 | * Step 4: Build a House 132 | */ 133 | /* 134 | First, clean up the array by removing all those random comments, but leave the 135 | 'x=0', 'z=0', 'z=1', etc. comments in there, and make sure they are correct. 136 | Trust me, it makes it a LOT easier to keep track of stuff that way. 137 | 138 | Now your array should look like this: 139 | */ 140 | public static final int[][][][] blockArrayTutorial = 141 | { 142 | { // y = 1 143 | { // x = 1 144 | {Block.wood.blockID}, // z = 1 145 | {Block.wood.blockID}, // z = 2 146 | {Block.wood.blockID}, // z = 3 147 | {Block.wood.blockID}, // z = 4 148 | {Block.wood.blockID} // z = 5 149 | }, 150 | { // x = 2 151 | {Block.wood.blockID}, // z = 1 152 | {Block.wood.blockID}, // z = 2 153 | {Block.wood.blockID}, // z = 3 154 | {Block.wood.blockID}, // z = 4 155 | {Block.wood.blockID} // z = 5 156 | }, 157 | { // x = 3 158 | {Block.wood.blockID}, // z = 1 159 | {Block.wood.blockID}, // z = 2 160 | {Block.wood.blockID}, // z = 3 161 | {Block.wood.blockID}, // z = 4 162 | {Block.wood.blockID} // z = 5 163 | }, 164 | { // x = 4 165 | {Block.wood.blockID}, // z = 1 166 | {Block.wood.blockID}, // z = 2 167 | {Block.wood.blockID}, // z = 3 168 | {Block.wood.blockID}, // z = 4 169 | {Block.wood.blockID} // z = 5 170 | }, 171 | { // x = 5 172 | {Block.wood.blockID}, // z = 1 173 | {Block.wood.blockID}, // z = 2 174 | {Block.wood.blockID}, // z = 3 175 | {Block.wood.blockID}, // z = 4 176 | {Block.wood.blockID} // z = 5 177 | } 178 | } 179 | }; 180 | /* 181 | You should notice that I started counting at 1, even though the array index is 182 | really 0. That's because it doesn't matter as far as we're concerned. We just 183 | need to keep track of the relative order, and it's far more natural to count 184 | from 1 rather than 0. If you'd rather start at 0, that's fine too. 185 | 186 | Ok, first copy and paste ALL of the array from x=1 to x=5 into a new y layer, 187 | then do it one more time. You should now have 3 identical 5x5 'layers'. 188 | */ 189 | public static final int[][][][] blockArrayTutorial = 190 | { 191 | { // y = 1 192 | { // x = 1 193 | {Block.wood.blockID}, // z = 1 194 | {Block.wood.blockID}, // z = 2 195 | {Block.wood.blockID}, // z = 3 196 | {Block.wood.blockID}, // z = 4 197 | {Block.wood.blockID} // z = 5 198 | }, 199 | { // x = 2 200 | {Block.wood.blockID}, // z = 1 201 | {Block.wood.blockID}, // z = 2 202 | {Block.wood.blockID}, // z = 3 203 | {Block.wood.blockID}, // z = 4 204 | {Block.wood.blockID} // z = 5 205 | }, 206 | { // x = 3 207 | {Block.wood.blockID}, // z = 1 208 | {Block.wood.blockID}, // z = 2 209 | {Block.wood.blockID}, // z = 3 210 | {Block.wood.blockID}, // z = 4 211 | {Block.wood.blockID} // z = 5 212 | }, 213 | { // x = 4 214 | {Block.wood.blockID}, // z = 1 215 | {Block.wood.blockID}, // z = 2 216 | {Block.wood.blockID}, // z = 3 217 | {Block.wood.blockID}, // z = 4 218 | {Block.wood.blockID} // z = 5 219 | }, 220 | { // x = 5 221 | {Block.wood.blockID}, // z = 1 222 | {Block.wood.blockID}, // z = 2 223 | {Block.wood.blockID}, // z = 3 224 | {Block.wood.blockID}, // z = 4 225 | {Block.wood.blockID} // z = 5 226 | } 227 | }, 228 | { // y = 2 229 | { // x = 1 230 | {Block.wood.blockID}, // z = 1 231 | {Block.wood.blockID}, // z = 2 232 | {Block.wood.blockID}, // z = 3 233 | {Block.wood.blockID}, // z = 4 234 | {Block.wood.blockID} // z = 5 235 | }, 236 | { // x = 2 237 | {Block.wood.blockID}, // z = 1 238 | {Block.wood.blockID}, // z = 2 239 | {Block.wood.blockID}, // z = 3 240 | {Block.wood.blockID}, // z = 4 241 | {Block.wood.blockID} // z = 5 242 | }, 243 | { // x = 3 244 | {Block.wood.blockID}, // z = 1 245 | {Block.wood.blockID}, // z = 2 246 | {Block.wood.blockID}, // z = 3 247 | {Block.wood.blockID}, // z = 4 248 | {Block.wood.blockID} // z = 5 249 | }, 250 | { // x = 4 251 | {Block.wood.blockID}, // z = 1 252 | {Block.wood.blockID}, // z = 2 253 | {Block.wood.blockID}, // z = 3 254 | {Block.wood.blockID}, // z = 4 255 | {Block.wood.blockID} // z = 5 256 | }, 257 | { // x = 5 258 | {Block.wood.blockID}, // z = 1 259 | {Block.wood.blockID}, // z = 2 260 | {Block.wood.blockID}, // z = 3 261 | {Block.wood.blockID}, // z = 4 262 | {Block.wood.blockID} // z = 5 263 | } 264 | }, 265 | { // y = 3 266 | { // x = 1 267 | {Block.wood.blockID}, // z = 1 268 | {Block.wood.blockID}, // z = 2 269 | {Block.wood.blockID}, // z = 3 270 | {Block.wood.blockID}, // z = 4 271 | {Block.wood.blockID} // z = 5 272 | }, 273 | { // x = 2 274 | {Block.wood.blockID}, // z = 1 275 | {Block.wood.blockID}, // z = 2 276 | {Block.wood.blockID}, // z = 3 277 | {Block.wood.blockID}, // z = 4 278 | {Block.wood.blockID} // z = 5 279 | }, 280 | { // x = 3 281 | {Block.wood.blockID}, // z = 1 282 | {Block.wood.blockID}, // z = 2 283 | {Block.wood.blockID}, // z = 3 284 | {Block.wood.blockID}, // z = 4 285 | {Block.wood.blockID} // z = 5 286 | }, 287 | { // x = 4 288 | {Block.wood.blockID}, // z = 1 289 | {Block.wood.blockID}, // z = 2 290 | {Block.wood.blockID}, // z = 3 291 | {Block.wood.blockID}, // z = 4 292 | {Block.wood.blockID} // z = 5 293 | }, 294 | { // x = 5 295 | {Block.wood.blockID}, // z = 1 296 | {Block.wood.blockID}, // z = 2 297 | {Block.wood.blockID}, // z = 3 298 | {Block.wood.blockID}, // z = 4 299 | {Block.wood.blockID} // z = 5 300 | } 301 | } 302 | }; 303 | /* 304 | You can see how the array quickly gets very long, so we're going to scrunch it 305 | together some to save space. Later on, we may have to write it 'longhand' again 306 | in order to more clearly see what's going on. Here's the short version: 307 | */ 308 | public static final int[][][][] blockArrayTutorial = 309 | { 310 | { // y = 1 311 | { // x = 1 312 | {Block.wood.blockID},{Block.wood.blockID},{Block.wood.blockID},{Block.wood.blockID},{Block.wood.blockID} 313 | }, 314 | { // x = 2 315 | {Block.wood.blockID},{Block.wood.blockID},{Block.wood.blockID},{Block.wood.blockID},{Block.wood.blockID} 316 | }, 317 | { // x = 3 318 | {Block.wood.blockID},{Block.wood.blockID},{Block.wood.blockID},{Block.wood.blockID},{Block.wood.blockID} 319 | }, 320 | { // x = 4 321 | {Block.wood.blockID},{Block.wood.blockID},{Block.wood.blockID},{Block.wood.blockID},{Block.wood.blockID} 322 | }, 323 | { // x = 5 324 | {Block.wood.blockID},{Block.wood.blockID},{Block.wood.blockID},{Block.wood.blockID},{Block.wood.blockID} 325 | } 326 | }, 327 | { // y = 2 328 | { // x = 1 329 | {Block.wood.blockID},{Block.wood.blockID},{Block.wood.blockID},{Block.wood.blockID},{Block.wood.blockID} 330 | }, 331 | { // x = 2 332 | {Block.wood.blockID},{Block.wood.blockID},{Block.wood.blockID},{Block.wood.blockID},{Block.wood.blockID} 333 | }, 334 | { // x = 3 335 | {Block.wood.blockID},{Block.wood.blockID},{Block.wood.blockID},{Block.wood.blockID},{Block.wood.blockID} 336 | }, 337 | { // x = 4 338 | {Block.wood.blockID},{Block.wood.blockID},{Block.wood.blockID},{Block.wood.blockID},{Block.wood.blockID} 339 | }, 340 | { // x = 5 341 | {Block.wood.blockID},{Block.wood.blockID},{Block.wood.blockID},{Block.wood.blockID},{Block.wood.blockID} 342 | } 343 | }, 344 | { // y = 3 345 | { // x = 1 346 | {Block.wood.blockID},{Block.wood.blockID},{Block.wood.blockID},{Block.wood.blockID},{Block.wood.blockID} 347 | }, 348 | { // x = 2 349 | {Block.wood.blockID},{Block.wood.blockID},{Block.wood.blockID},{Block.wood.blockID},{Block.wood.blockID} 350 | }, 351 | { // x = 3 352 | {Block.wood.blockID},{Block.wood.blockID},{Block.wood.blockID},{Block.wood.blockID},{Block.wood.blockID} 353 | }, 354 | { // x = 4 355 | {Block.wood.blockID},{Block.wood.blockID},{Block.wood.blockID},{Block.wood.blockID},{Block.wood.blockID} 356 | }, 357 | { // x = 5 358 | {Block.wood.blockID},{Block.wood.blockID},{Block.wood.blockID},{Block.wood.blockID},{Block.wood.blockID} 359 | } 360 | } 361 | }; 362 | /* 363 | Use caution when condensing your arrays in this way, as it's much easier to make errors like this. 364 | I actually recommend you keep your array in the longer format, but for the sake of space in this 365 | tutorial, I will use the condensed version. 366 | 367 | Ok, so we wanted to make a house, right? A house is hollow on the inside, so we just need to set 368 | all the blocks inside to air, or id of 0. But where is inside? Easy. The first and last index of 369 | both x and z represent the outer edges of the array, and thus are the outer edges of our structure. 370 | So the first and last block in each z 'column' need to stay wood, as do all of the blocks at the 371 | first and last x arrays. We need to do this for each y 'layer' as well, but we'll leave y=3 totally 372 | solid as a roof. 373 | */ 374 | public static final int[][][][] blockArrayTutorial = 375 | { 376 | { // y = 1 377 | { // x = 1 378 | // We need an entrance to, so let's make it in the center of this column here, so at z=3 379 | {Block.wood.blockID},{Block.wood.blockID},{0},{Block.wood.blockID},{Block.wood.blockID} 380 | }, 381 | { // x = 2 382 | {Block.wood.blockID},{0},{0},{0},{Block.wood.blockID} 383 | }, 384 | { // x = 3 385 | {Block.wood.blockID},{0},{0},{0},{Block.wood.blockID} 386 | }, 387 | { // x = 4 388 | {Block.wood.blockID},{0},{0},{0},{Block.wood.blockID} 389 | }, 390 | { // x = 5 391 | {Block.wood.blockID},{Block.wood.blockID},{Block.wood.blockID},{Block.wood.blockID},{Block.wood.blockID} 392 | } 393 | }, 394 | { // y = 2 395 | { // x = 1 396 | // Don't forget to make your entrance at the same x/z location in the next layer! 397 | // In y=1, we made a hole at x=1, z=3, so let's do the same here for a 2-block tall entrance 398 | {Block.wood.blockID},{Block.wood.blockID},{0},{Block.wood.blockID},{Block.wood.blockID} 399 | }, 400 | { // x = 2 401 | {Block.wood.blockID},{0},{0},{0},{Block.wood.blockID} 402 | }, 403 | { // x = 3 404 | {Block.wood.blockID},{0},{0},{0},{Block.wood.blockID} 405 | }, 406 | { // x = 4 407 | {Block.wood.blockID},{0},{0},{0},{Block.wood.blockID} 408 | }, 409 | { // x = 5 410 | {Block.wood.blockID},{Block.wood.blockID},{Block.wood.blockID},{Block.wood.blockID},{Block.wood.blockID} 411 | } 412 | }, 413 | { // y = 3 414 | { // x = 1 415 | {Block.wood.blockID},{Block.wood.blockID},{Block.wood.blockID},{Block.wood.blockID},{Block.wood.blockID} 416 | }, 417 | { // x = 2 418 | {Block.wood.blockID},{Block.wood.blockID},{Block.wood.blockID},{Block.wood.blockID},{Block.wood.blockID} 419 | }, 420 | { // x = 3 421 | {Block.wood.blockID},{Block.wood.blockID},{Block.wood.blockID},{Block.wood.blockID},{Block.wood.blockID} 422 | }, 423 | { // x = 4 424 | {Block.wood.blockID},{Block.wood.blockID},{Block.wood.blockID},{Block.wood.blockID},{Block.wood.blockID} 425 | }, 426 | { // x = 5 427 | {Block.wood.blockID},{Block.wood.blockID},{Block.wood.blockID},{Block.wood.blockID},{Block.wood.blockID} 428 | } 429 | } 430 | }; 431 | /* 432 | Alright, give it a try and see. Where is your entrance? Probably not in front of you. What's 433 | going on? Well, we didn't set a default facing for our structure. Since we want x=1 (minimum 434 | x value) to be the front of our structure, taking a glance at the StructureArrays.java 435 | instructions tells us we need to set our default facing to WEST. In StructureGenerator where 436 | we added our new "Tutorial" structure, you need to add one line. 437 | */ 438 | structure = new Structure("Tutorial"); 439 | structure.addBlockArray(StructureArrays.blockArrayTutorial); 440 | // The following line does just what it says. Best to do it before adding the structure to the List. 441 | structure.setFacing(StructureGeneratorBase.WEST); 442 | structures.add(structure); 443 | /* 444 | Try again, your entrance should be directly in front of you when you generate the structure 445 | no matter which way you turn. 446 | 447 | Note that setting a structure facing is only important when you want a specific side to face 448 | the player; if you are generating structures during world generation, it won't matter what 449 | default facing you set, and you can even forego setting a facing altogether. I like to always 450 | have a facing in mind, though, as it makes figuring out the correct metadata values easier. 451 | Once you have the correct value, though, it won't matter which facing you give the structure. 452 | */ 453 | /* 454 | * Step 5: Embellishments and Furnishings 455 | */ 456 | /* 457 | So you've got a hollow wooden box with a hole in it large enough for Steve to walk through. 458 | Not very exciting. In this step we'll add a door, some windows, a bed, a chest, and a torch. 459 | 460 | First, the windows. Obviously they will replace one of the wall blocks, so change some 'wood' 461 | blocks to 'glass'. I'd suggest doing this at your second y layer, perhaps in the center of 462 | each wall. 463 | 464 | Next, we need to add a door. That's a little more complicated, and will involve using metadata. 465 | In our array, each 'z' value is actually an array of its own, though we currently only store a 466 | single block id value in there. To add metadata, simply place a comma ',' after the block id 467 | followed by the value: {Block.block.blockID, MetaDataValue} 468 | 469 | Taking a look at either the Minecraft wiki 'DataValues' page or StructureArrays.java instructions, 470 | we see: "Bottom block should have a value of 0,1,2,3 facing west, north, east, or south". Hm, well, 471 | our door is on the front of our structure, and our structure faces west, so it would make sense for 472 | our door to face west as well. Therefore the bottom block should have a metadata value of '0'. For 473 | the top block, the metadata value is simply that of the bottom block plus '8'. 474 | 475 | For the bed, we'll put it along the back wall, so it's length is along the north-south axis. Let's 476 | put the head towards the wall, rather than in the center of the room. Refer back to the instruction 477 | manual to see how to set metadata for beds: "0,1,2,3 facing south, west, north, east; plus 8 for head". 478 | 479 | Our bed will be facing 'left' when we look at it from the doorway, which for a structure of facing 480 | WEST, 'left' is to the north (and also happens to be 'left' in the array, as in z = 1). So the block 481 | in the corner should have a meta of north+head, or 2+8 = 10. The block to it's right also needs to 482 | face north, so we'll set it's meta to 2. 483 | 484 | Next, we'll place a chest in the opposite corner, just to the right of the door. We want it to be 485 | easily accessible right when you step inside, so it will face toward the entrance, which just so 486 | happens to be the same direction our bed is facing, north. Note that chests don't use the same meta 487 | values as beds, so go find the correct value for a chest facing 'north' and use that. 488 | 489 | Finally, we'll place a torch to light up the inside of our little house. A nice place for this might 490 | be opposite the chest, on the block adjacent to the door and facing inwards. Recall that our door faces 491 | west, so the torch should face east, since it's facing the opposite direction compared to the door. 492 | 493 | Here's a little diagram of the floor layout; your array setup should look very similar! Keep in mind 494 | that the glass windows and torch should be at eye level, not on the ground. 495 | 496 | W W D W W W: Wood =: Glass 497 | W T - C W D: Door -: Air 498 | = - - - = C: Chest 499 | W B B - W B: Bed 500 | W W = W W T: Torch 501 | 502 | Fire it up and see how it looks! If a block is out of place, try placing it elsewhere in the array 503 | until you get it right. If a block is facing the wrong way, check StructureArrays.java once again 504 | and read up on how to set metadata for that block. 505 | 506 | If you got everything just right, play around with it and see if you can rearrange the room. Swap the 507 | bed with the chest, but keep the bed running along the wall. Move the torch to a different wall. Can 508 | you figure out how to get a torch on the outside of the house? 509 | 510 | If you tried and tried and tried but couldn't get the structure correct, I've uploaded the completed 511 | block array to my Github Forge Tutorials page under 'supplementary' materials: 512 | 513 | https://github.com/coolAlias/Forge_Tutorials/tree/master/supplementary 514 | */ 515 | -------------------------------------------------------------------------------- /StructureArrayTutorialPart2.java: -------------------------------------------------------------------------------- 1 | /* 2 | STRUCTURE GENERATOR TOOL TUTORIAL PART 2: USING CUSTOM HOOKS 3 | 4 | In this tutorial we will take a look at some of the additional features available with this 5 | tool using the basic furnished home we created in Part 1 as the foundation. 6 | 7 | First, we'll take a look at how to implement the StructureGenerator class, as that forms the 8 | foundation for using custom hooks. 9 | */ 10 | /** 11 | * Step 1: Create a class extending StructureGeneratorBase 12 | */ 13 | /* 14 | If you downloaded the source code, there is already a class that does this, but for the sake 15 | of demonstration, we'll start from scratch. We'll call the new class 'MyStructureGenerator' 16 | and just let Eclipse automatically add all the methods we need. 17 | 18 | We're also going to need some way to store our available structures, otherwise we have to set 19 | all of the structure variables each time we want to generate a structure. We're going to use 20 | a List of Structure objects with a static initializer. 21 | */ 22 | public class MyStructureGenerator extends StructureGeneratorBase 23 | { 24 | /** List storing all structures currently available */ 25 | public static final List structures = new LinkedList(); 26 | 27 | public MyStructureGenerator() { 28 | // TODO Auto-generated constructor stub 29 | } 30 | 31 | public MyStructureGenerator(Entity entity, int[][][][] blocks) { 32 | super(entity, blocks); 33 | // TODO Auto-generated constructor stub 34 | } 35 | 36 | public MyStructureGenerator(Entity entity, int[][][][] blocks, int structureFacing) { 37 | super(entity, blocks, structureFacing); 38 | // TODO Auto-generated constructor stub 39 | } 40 | 41 | public MyStructureGenerator(Entity entity, int[][][][] blocks, int structureFacing, int offX, int offY, int offZ) { 42 | super(entity, blocks, structureFacing, offX, offY, offZ); 43 | // TODO Auto-generated constructor stub 44 | } 45 | 46 | @Override 47 | public int getRealBlockID(int fakeID, int customData1) { 48 | // TODO Auto-generated method stub 49 | return 0; 50 | } 51 | 52 | @Override 53 | public void onCustomBlockAdded(World world, int x, int y, int z, int fakeID, int customData1, int customData2) { 54 | // TODO Auto-generated method stub 55 | 56 | } 57 | 58 | /** This is where you add your structures to the List we made at the top */ 59 | static { 60 | // A temporary object to store a Structure before adding it to the List 61 | Structure structure; 62 | 63 | // For each structure you need to set 'structure' to a new instance of Structure 64 | structure = new Structure("Tutorial Home"); 65 | 66 | // Add all your structure's block arrays to the structure, starting from the bottom 67 | // and working up; our home, however, only has one array 68 | structure.addBlockArray(StructureArrayTutorial.blockArrayTutorial); 69 | 70 | // Remember to set the structure facing 71 | structure.setFacing(StructureGeneratorBase.WEST); 72 | 73 | // Finally, add the structure to the List 74 | structures.add(structure); 75 | } 76 | } 77 | /* 78 | Now you can create a 'public static final MyStructureGenerator gen = new MyStructureGenerator()' 79 | in your main mod class or anywhere else you like, and you will have access to your structures 80 | from anywhere you need via the List called 'structures'. 81 | 82 | For example, from a block's onBlockActivated method, you could generate your house like so: 83 | */ 84 | @Override 85 | public boolean onBlockActivated(World world, int x, int y, int z, EntityPlayer player, int par6, float par7, float par8, float par9) 86 | { 87 | // You only need to generate on the server 88 | if (!world.isRemote) { 89 | // Store the structure we want to generate in a local variable for ease of reference 90 | // Use care when accessing your List.get(index) method or you will get null pointer exceptions 91 | Structure structure = YourMod.gen.structures.get(1); 92 | 93 | // Add player facing to rotation calculations so your structure faces you when generated 94 | YourMod.gen.setPlayerFacing(player); 95 | 96 | // Sets the structure to the structure at index '1' in the List; i.e. the Tutorial Home 97 | YourMod.gen.setStructure(structure); 98 | 99 | // Optionally, set your structure's offset coordinates 100 | YourMod.gen.setDefaultOffset(structure.getOffsetX(), structure.getOffsetY(), structure.getOffsetZ()); 101 | 102 | // Now generate the structure at the x/y/z provided by the block's method parameters 103 | YourMod.gen.generate(world, world.rand, x, y, z); 104 | } 105 | } 106 | /** 107 | * Step 2: Setting up a Custom Hook 108 | */ 109 | /* 110 | Custom hooks are all about definitions. As such, we're going to create a 'library' or reference 111 | class that does nothing but hold our definitions, much like some do for Item IDs and names. 112 | This way all of our custom hook types are in one place and we can easily check for conflicts. 113 | 114 | Each custom hook id acts as a 'fake' block id and is used in lieu of a real block ID within 115 | the block array. Instead of using 'Block.chest.blockID' we will use our custom id, returning 116 | the real chest blockID from the 'getRealBlockID' method in MyStructureGenerator. 117 | 118 | Since Forge uses block IDs from 0-4095, we will start our custom hook indices from 4096. Being 119 | outside of the normal block range acts as a flag, telling the structure generator that it needs 120 | to handle this 'block' in a special way; that is, by calling the 'onCustomBlockAdded' method. 121 | 122 | Let's set up our CustomHooks reference class first, with a single custom id. 123 | */ 124 | public class CustomHooks { 125 | public static final int CUSTOM_CHEST = 4096; 126 | } 127 | /* 128 | Simple. Now we need to make sure 'getRealBlockID' returns the correct id. 129 | */ 130 | @Override 131 | public int getRealBlockID(int fakeID, int customData1) 132 | { 133 | switch(fakeID) { 134 | case CustomHooks.CUSTOM_CHEST: return Block.chest.blockID; 135 | default: return 0; 136 | } 137 | } 138 | /* 139 | Finally, we need to define what happens when CUSTOM_CHEST id is placed in the world. For this, 140 | we use the 'onCustomBlockAdded' method. Think of it in the same way you do the vanilla methods 141 | 'onBlockAdded', 'onBlockPlacedBy' etc. 142 | */ 143 | @Override 144 | public void onCustomBlockAdded(World world, int x, int y, int z, int fakeID, int customData1, int customData2) 145 | { 146 | // For now we'll ignore all the other parameters and only use the fakeID, i.e. our custom hook id 147 | switch(fakeID) { 148 | case CustomHooks.CUSTOM_CHEST: 149 | // Using the pre-made method addItemToTileInventory adds items to the first slot available 150 | 151 | // Let's load our chest with goodies! We'll take advantage of addItemToTileInventory's 152 | // return value: true if the item was added, false if there was no more room 153 | boolean canAdd; 154 | do { 155 | canAdd = addItemToTileInventory(world, new ItemStack(Item.diamond, 64), x, y, z); 156 | if (canAdd) canAdd = addItemToTileInventory(world, new ItemStack(Item.emerald, 64), x, y, z); 157 | } while (canAdd); 158 | break; 159 | } 160 | } 161 | /* 162 | Fire up Minecraft and generate your structure. You should now see... nothing? Why not? Well, 163 | we didn't add our custom hook into our structure array! Open up StructureArrayTutorial and 164 | replace 'Block.chest.blockID' with 'CustomHooks.CUSTOM_CHEST'. Try generating your structure 165 | again. You should have lots of goodies this time. 166 | 167 | Don't feel limited by my above implementation. You could just as well create your own lists 168 | of WeightedRandomChestContents from which to select items, rather than hard-coding the chest 169 | contents like I did here. 170 | */ 171 | /** 172 | * Step 3: Using CustomData1 and CustomData2 173 | */ 174 | /* 175 | Ok, we've got a chest generating with stuff inside, but what if you want some variety in your 176 | chest contents? Do you need to define a new custom hook id for every chest? No and yes. You 177 | don't need to define a new custom hook id with the accompanying real block id; we can simply 178 | use CUSTOM_CHEST for any custom chest. We do, however, have to define our chest somehow. I 179 | think of it like 'subtypes' for my hook id, and I store it in customData1. 180 | 181 | Recall that the final array in the blockArray holds up to 4 int values: 182 | {blockID, metadata, customData1, customData2} 183 | 184 | So far, we've totally ignored the two custom data elements. To demonstrate quickly how they 185 | work, let's change our CUSTOM_CHEST onCustomBlockAdded code a little. 186 | */ 187 | @Override 188 | public void onCustomBlockAdded(World world, int x, int y, int z, int fakeID, int customData1, int customData2) 189 | { 190 | // For now we'll ignore all the other parameters and only use the fakeID, i.e. our custom hook id 191 | switch(fakeID) { 192 | case CustomHooks.CUSTOM_CHEST: 193 | addItemToTileInventory(world, new ItemStack(customData1, customData2, 0), x, y, z); 194 | break; 195 | } 196 | } 197 | /* 198 | In this code, we are creating a new ItemStack with an itemID defined by customData1 and with 199 | a stack size defined by customData2. Instead of a set stack size, you could randomize it by 200 | using 'world.rand.nextInt(customData2) + 1' or whatever algorithm you want to use. 201 | 202 | Note we also have to include the damage/metadata value, here '0', because customData1 is not 203 | an Item, but an int, and that's just how the ItemStack constructor works. 204 | 205 | Go back to StructureArrayTutorial and change the custom chest array to: 206 | {CustomHooks.CUSTOM_CHEST,2,Item.appleGold.itemID,16} 207 | 208 | Go ahead and try it out. You should be getting 16 golden apples in your chest. Using this, 209 | you could have any number of chests all with different, specific contents, but as you can 210 | see this method limits you to one type of item per chest. 211 | 212 | A good alternative is to use customData1 to identify the chest, then add a number of items 213 | defined by customData2 from a weight list like vanilla chests use. I suggest defining all 214 | of your subtypes in the CustomHooks reference class. 215 | */ 216 | public class CustomHooks { 217 | public static final int CUSTOM_CHEST = 4096; 218 | 219 | // I use negative values here so I can still use customData1 to define itemIDs in generic CUSTOM_CHESTs 220 | public static final int 221 | CHEST_HOUSE_1 = -1, // These will define two separate sets of contents for our house chest 222 | CHEST_HOUSE_2 = -2; 223 | } 224 | /* 225 | Add this chest to the block array at x=3, right under the other chest we have: 226 | {CustomHooks.CUSTOM_CHEST,2,CustomHooks.CHEST_HOUSE_1} 227 | 228 | Now there should be two chests in the block array, one that's custom and one that sets the 229 | contents to 16 golden apples. 230 | 231 | Note that we're not using customData2 in our sub-chest because I don't plan on setting up 232 | a weighted list at this point. Recall that the '2' is the metadata value of the chest block 233 | which we need for it to be oriented correctly in our house. 234 | 235 | Now we just need to alter the CUSTOM_CHEST case to account for the new possibility of subtypes. 236 | */ 237 | @Override 238 | public void onCustomBlockAdded(World world, int x, int y, int z, int fakeID, int customData1, int customData2) 239 | { 240 | // For now we'll ignore all the other parameters and only use the fakeID, i.e. our custom hook id 241 | switch(fakeID) { 242 | case CustomHooks.CUSTOM_CHEST: 243 | // Depending on how many subtypes you have, you could use a switch or call another 244 | // method you define yourself that handles all chest cases 245 | if (customData1 == CustomHooks.CHEST_HOUSE_1) { 246 | // Let's give our custom house chest some potions! Check the Minecraft wiki 247 | // data values page for potion ids. 248 | addItemToTileInventory(world, new ItemStack(Item.potion,1,8206), x, y, z); 249 | addItemToTileInventory(world, new ItemStack(Item.potion,1,8270), x, y, z); 250 | addItemToTileInventory(world, new ItemStack(Item.potion,1,8193), x, y, z); 251 | addItemToTileInventory(world, new ItemStack(Item.potion,1,16385), x, y, z); 252 | } 253 | else if (customData1 == CustomHooks.CHEST_HOUSE_2) { 254 | // For this one, let's put in our family sword + armor, worn from years of disuse 255 | addItemToTileInventory(world, new ItemStack(Item.swordIron,1,128), x, y, z); 256 | addItemToTileInventory(world, new ItemStack(Item.plateIron,1,128), x, y, z); 257 | addItemToTileInventory(world, new ItemStack(Item.helmetIron,1,72), x, y, z); 258 | addItemToTileInventory(world, new ItemStack(Item.legsIron,1,128), x, y, z); 259 | addItemToTileInventory(world, new ItemStack(Item.bootsIron,1,72), x, y, z); 260 | } 261 | else { 262 | // We can still use customData1/2 as itemID and stack size for generic chests 263 | addItemToTileInventory(world, new ItemStack(customData1, customData2, 0), x, y, z); 264 | } 265 | break; 266 | } 267 | } 268 | /* 269 | Finally, to take advantage of our second custom chest, copy and paste the blockArrayTutorial 270 | in your StructureArrayTutorial class. Append a '1' to the first array name and a '2' to the 271 | second, then change CUSTOM_CHEST_1 to CUSTOM_CHEST_2 in the second array. Now you have to add 272 | the new array to your StructureGenerator static initializer: 273 | */ 274 | /** This is where you add your structures to the List we made at the top */ 275 | static { 276 | Structure structure; 277 | // Our first structure: 278 | structure = new Structure("Tutorial Home 1"); 279 | structure.addBlockArray(StructureArrayTutorial.blockArrayTutorial1); 280 | structure.setFacing(StructureGeneratorBase.WEST); 281 | structures.add(structure); 282 | 283 | // Our second structure is almost exactly the same: 284 | structure = new Structure("Tutorial Home 2"); 285 | structure.addBlockArray(StructureArrayTutorial.blockArrayTutorial2); 286 | structure.setFacing(StructureGeneratorBase.WEST); 287 | structures.add(structure); 288 | } 289 | /* 290 | Now in your block or item that generates the structure, you can choose to generate either 291 | 'YourMod.gen.structures.get(1)' OR 'YourMod.gen.structures.get(2)', or you could make a new 292 | block/item for the second structure. Generate both of them in your world and you'll see they 293 | both have unique chest contents. 294 | */ 295 | /** 296 | * Step 4: Other Custom Hooks / A Summary 297 | */ 298 | /* 299 | Now you know how to set up and use custom hooks for chests, and it's exactly the same for any 300 | other kind of custom block you want to make. Do you want a sign with text on it? Define an id 301 | 'CUSTOM_SIGN = 4097' in your CustomHooks reference class and, if you want, define subtypes 302 | 'SIGN_1 = 1', 'SIGN_2 = 2', etc. Then all that's left is to define what happens when your 303 | custom block is added to the world in the 'onCustomBlockAdded' method. Just add a new case 304 | for each custom hook id. 305 | 306 | Take a look at the source code provided on github, specifically 'StructureGenerator.java' and 307 | 'CustomHooks.java', for further examples and ideas on using Custom Hooks. 308 | 309 | Steps Required for Creating a Custom Hook: 310 | 1. Define your custom hook id, starting from 4096 311 | 2. Define real id to return for your hook in 'getRealBlockID' method 312 | 3. Write code you want to occur when your custom block is placed 313 | 4. Optionally, define subtypes for your hook 314 | 315 | =================== 316 | SOME USEFUL METHODS 317 | =================== 318 | The following are methods designed to make handling onCustomBlockAdded cases easier: 319 | 320 | 1. addItemToTileInventory(World world, ItemStack itemstack, int x, int y, int z) 321 | 322 | Use this method to conveniently add items to any TileEntity that has an inventory 323 | (i.e. implements either IInventory or ISidedInventory). 324 | 325 | Items are added to the first slot available and the method returns false if the 326 | stack was not able to be added entirely or if there was an error. 327 | 328 | 2.1 spawnEntityInStructure(World world, Entity entity, int x, int y, int z) 329 | 330 | Spawns the passed in entity within the structure such that it doesn't spawn inside of 331 | walls by using the method setEntityInStructure below. If no valid location was found, 332 | the entity will still spawn but the method will return false. 333 | 334 | 2.2 setEntityInStructure(World world, Entity entity, int x, int y, int z) 335 | 336 | Sets an entity's location so that it doesn't spawn inside of walls, but doesn't spawn 337 | the entity. Automatically removes placeholder block at coordinates x/y/z. 338 | Returns false if no suitable location found so user can decide whether to spawn or not. 339 | 340 | 3. setHangingEntity(World world, ItemStack hanging, int x, int y, int z) 341 | 342 | Places a hanging entity in the world based on the arguments provided; orientation 343 | will be determined automatically based on the dummy blocks data, so a WALL_MOUNTED 344 | block id (such as Block.torchWood.blockID) must be returned from getRealBlockID(). 345 | 346 | This method returns the direction in which the entity faces for use with the methods 347 | 'setItemFrameStack' and 'setPaintingArt'. It is not needed for wall signs. 348 | 349 | 4. setItemFrameStack(World world, ItemStack itemstack, int x, int y, int z, int direction, 350 | int itemRotation) 351 | 352 | Finds the correct ItemFrame in the world for the coordinates and direction given and 353 | places the itemstack inside with the rotation provided, or default if no itemRotation 354 | value is given. 'direction' parameter is value returned from setHangingEntity 355 | 356 | 5. setPaintingArt(World world, String name, int x, int y, int z, int direction) 357 | 358 | Sets the art for a painting at location x/y/z and sends a packet to update players. 359 | 'direction' parameter is value returned from setHangingEntity 360 | Returns false if 'name' didn't match any EnumArt values. 361 | 362 | 6. setSignText(World world, String[] text, int x, int y, int z) 363 | 364 | Adds the provided text to a sign tile entity at the provided coordinates, or returns 365 | false if no TileEntitySign was found. String[] must be manually set for each sign, as 366 | there is currently no way to store this information within the block array. 367 | 368 | 7.1 setSkullData(World world, String name, int type, int x, int y, int z) 369 | 370 | Sets the skull type and player username (if you can get one) for the tile entity at 371 | the provided coordinates. Returns false if no TileEntitySkull was found. 372 | 373 | 7.2 setSkullData(World world, String name, int type, int rot, int x, int y, int z) 374 | 375 | As above but with additional rotation data (rot). This only applies to skulls sitting 376 | on the floor, not mounted to walls. 377 | */ 378 | -------------------------------------------------------------------------------- /supplementary/StructureArrayTutorialSupplement.java: -------------------------------------------------------------------------------- 1 | package coolalias.structuregen; 2 | 3 | import net.minecraft.block.Block; 4 | 5 | public class StructureArrayTutorial 6 | { 7 | public static final int[][][][] blockArrayTutorial = 8 | { 9 | { // y = 1 10 | { // x = 1 11 | {Block.wood.blockID},{Block.wood.blockID},{Block.doorWood.blockID,0},{Block.wood.blockID},{Block.wood.blockID} 12 | }, 13 | { // x = 2 14 | {Block.wood.blockID},{0},{0},{Block.chest.blockID,2},{Block.wood.blockID} 15 | }, 16 | { // x = 3 17 | {Block.wood.blockID},{0},{0},{0},{Block.wood.blockID} 18 | }, 19 | { // x = 4 20 | {Block.wood.blockID},{Block.bed.blockID,10},{Block.bed.blockID,2},{0},{Block.wood.blockID} 21 | }, 22 | { // x = 5 23 | {Block.wood.blockID},{Block.wood.blockID},{Block.wood.blockID},{Block.wood.blockID},{Block.wood.blockID} 24 | } 25 | }, 26 | { // y = 2 27 | { // x = 1 28 | {Block.wood.blockID},{Block.wood.blockID},{Block.doorWood.blockID,8},{Block.wood.blockID},{Block.wood.blockID} 29 | }, 30 | { // x = 2 31 | {Block.wood.blockID},{Block.torchWood.blockID,1},{0},{0},{Block.wood.blockID} 32 | }, 33 | { // x = 3 34 | {Block.glass.blockID},{0},{0},{0},{Block.glass.blockID} 35 | }, 36 | { // x = 4 37 | {Block.wood.blockID},{0},{0},{0},{Block.wood.blockID} 38 | }, 39 | { // x = 5 40 | {Block.wood.blockID},{Block.wood.blockID},{Block.glass.blockID},{Block.wood.blockID},{Block.wood.blockID} 41 | } 42 | }, 43 | { // y = 3 44 | { // x = 1 45 | {Block.wood.blockID},{Block.wood.blockID},{Block.wood.blockID},{Block.wood.blockID},{Block.wood.blockID} 46 | }, 47 | { // x = 2 48 | {Block.wood.blockID},{Block.wood.blockID},{Block.wood.blockID},{Block.wood.blockID},{Block.wood.blockID} 49 | }, 50 | { // x = 3 51 | {Block.wood.blockID},{Block.wood.blockID},{Block.wood.blockID},{Block.wood.blockID},{Block.wood.blockID} 52 | }, 53 | { // x = 4 54 | {Block.wood.blockID},{Block.wood.blockID},{Block.wood.blockID},{Block.wood.blockID},{Block.wood.blockID} 55 | }, 56 | { // x = 5 57 | {Block.wood.blockID},{Block.wood.blockID},{Block.wood.blockID},{Block.wood.blockID},{Block.wood.blockID} 58 | } 59 | } 60 | }; 61 | } 62 | -------------------------------------------------------------------------------- /textures/gui/custom_inventory.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolAlias/Forge_Tutorials/124a0d756a9839ca0c2f737e2be5d8917563707c/textures/gui/custom_inventory.png -------------------------------------------------------------------------------- /textures/gui/eye.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolAlias/Forge_Tutorials/124a0d756a9839ca0c2f737e2be5d8917563707c/textures/gui/eye.png -------------------------------------------------------------------------------- /textures/gui/inventoryitem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolAlias/Forge_Tutorials/124a0d756a9839ca0c2f737e2be5d8917563707c/textures/gui/inventoryitem.png -------------------------------------------------------------------------------- /textures/gui/mana_bar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolAlias/Forge_Tutorials/124a0d756a9839ca0c2f737e2be5d8917563707c/textures/gui/mana_bar.png -------------------------------------------------------------------------------- /textures/gui/mana_bar2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolAlias/Forge_Tutorials/124a0d756a9839ca0c2f737e2be5d8917563707c/textures/gui/mana_bar2.png --------------------------------------------------------------------------------