diff --git a/src/main/java/thaumcraft/api/ThaumcraftApi.java b/src/main/java/thaumcraft/api/ThaumcraftApi.java new file mode 100644 index 00000000..fbf78801 --- /dev/null +++ b/src/main/java/thaumcraft/api/ThaumcraftApi.java @@ -0,0 +1,588 @@ +package thaumcraft.api; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; + +import net.minecraft.block.Block; +import net.minecraft.enchantment.Enchantment; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.Item; +import net.minecraft.item.Item.ToolMaterial; +import net.minecraft.item.ItemArmor.ArmorMaterial; +import net.minecraft.item.ItemStack; +import net.minecraftforge.common.util.EnumHelper; +import net.minecraftforge.oredict.OreDictionary; +import thaumcraft.api.aspects.Aspect; +import thaumcraft.api.aspects.AspectList; +import thaumcraft.api.crafting.CrucibleRecipe; +import thaumcraft.api.crafting.InfusionEnchantmentRecipe; +import thaumcraft.api.crafting.InfusionRecipe; +import thaumcraft.api.crafting.ShapedArcaneRecipe; +import thaumcraft.api.crafting.ShapelessArcaneRecipe; +import thaumcraft.api.internal.DummyInternalMethodHandler; +import thaumcraft.api.internal.IInternalMethodHandler; +import thaumcraft.api.internal.WeightedRandomLoot; +import thaumcraft.api.research.IScanEventHandler; +import thaumcraft.api.research.ResearchCategories; +import thaumcraft.api.research.ResearchCategoryList; +import thaumcraft.api.research.ResearchItem; +import thaumcraft.api.research.ResearchPage; + + +/** + * @author Azanor + * + * + * IMPORTANT: If you are adding your own aspects to items it is a good idea to do it AFTER Thaumcraft adds its aspects, otherwise odd things may happen. + * + */ +public class ThaumcraftApi { + + //Materials + public static ToolMaterial toolMatThaumium = EnumHelper.addToolMaterial("THAUMIUM", 3, 400, 7F, 2, 22); + public static ToolMaterial toolMatVoid = EnumHelper.addToolMaterial("VOID", 4, 150, 8F, 3, 10); + public static ToolMaterial toolMatElemental = EnumHelper.addToolMaterial("THAUMIUM_ELEMENTAL", 3, 1500, 10F, 3, 18); + public static ArmorMaterial armorMatThaumium = EnumHelper.addArmorMaterial("THAUMIUM", 25, new int[] { 2, 6, 5, 2 }, 25); + public static ArmorMaterial armorMatSpecial = EnumHelper.addArmorMaterial("SPECIAL", 25, new int[] { 1, 3, 2, 1 }, 25); + public static ArmorMaterial armorMatThaumiumFortress = EnumHelper.addArmorMaterial("FORTRESS", 40, new int[] { 3, 7, 6, 3 }, 25); + public static ArmorMaterial armorMatVoid = EnumHelper.addArmorMaterial("VOID", 10, new int[] { 3, 7, 6, 3 }, 10); + public static ArmorMaterial armorMatVoidFortress = EnumHelper.addArmorMaterial("VOIDFORTRESS", 18, new int[] { 4, 8, 7, 4 }, 10); + + //Enchantment references + public static int enchantFrugal; + public static int enchantPotency; + public static int enchantWandFortune; + public static int enchantHaste; + public static int enchantRepair; + + //Miscellaneous + /** + * Portable Hole Block-id Blacklist. + * Simply add the block-id's of blocks you don't want the portable hole to go through. + */ + public static ArrayList portableHoleBlackList = new ArrayList(); + + //Internal (Do not alter this unless you like pretty explosions) + //Calling methods from this will only work properly once Thaumcraft is past the FMLPreInitializationEvent phase. + public static IInternalMethodHandler internalMethods = new DummyInternalMethodHandler(); + + //RESEARCH///////////////////////////////////////// + public static ArrayList scanEventhandlers = new ArrayList(); + public static ArrayList scanEntities = new ArrayList(); + public static class EntityTagsNBT { + public EntityTagsNBT(String name, Object value) { + this.name = name; + this.value = value; + } + public String name; + public Object value; + } + public static class EntityTags { + public EntityTags(String entityName, AspectList aspects, EntityTagsNBT... nbts) { + this.entityName = entityName; + this.nbts = nbts; + this.aspects = aspects; + } + public String entityName; + public EntityTagsNBT[] nbts; + public AspectList aspects; + } + + /** + * not really working atm, so ignore it for now + * @param scanEventHandler + */ + public static void registerScanEventhandler(IScanEventHandler scanEventHandler) { + scanEventhandlers.add(scanEventHandler); + } + + /** + * This is used to add aspects to entities which you can then scan using a thaumometer. + * Also used to calculate vis drops from mobs. + * @param entityName + * @param aspects + * @param nbt you can specify certain nbt keys and their values + * to differentiate between mobs.
For example the normal and wither skeleton: + *
ThaumcraftApi.registerEntityTag("Skeleton", (new AspectList()).add(Aspect.DEATH, 5)); + *
ThaumcraftApi.registerEntityTag("Skeleton", (new AspectList()).add(Aspect.DEATH, 8), new NBTTagByte("SkeletonType",(byte) 1)); + */ + public static void registerEntityTag(String entityName, AspectList aspects, EntityTagsNBT... nbt ) { + scanEntities.add(new EntityTags(entityName,aspects,nbt)); + } + + //RECIPES///////////////////////////////////////// + private static ArrayList craftingRecipes = new ArrayList(); + private static HashMap smeltingBonus = new HashMap(); + + /** + * This method is used to determine what bonus items are generated when the infernal furnace smelts items + * @param in The input of the smelting operation. e.g. new ItemStack(Block.oreGold) + * @param out The bonus item that can be produced from the smelting operation e.g. new ItemStack(nuggetGold,0,0). + * Stacksize should be 0 unless you want to guarantee that at least 1 item is always produced. + */ + public static void addSmeltingBonus(ItemStack in, ItemStack out) { + smeltingBonus.put( + Arrays.asList(in.getItem(),in.getItemDamage()), + new ItemStack(out.getItem(),0,out.getItemDamage())); + } + + /** + * This method is used to determine what bonus items are generated when the infernal furnace smelts items + * @param in The ore dictionary input of the smelting operation. e.g. "oreGold" + * @param out The bonus item that can be produced from the smelting operation e.g. new ItemStack(nuggetGold,0,0). + * Stacksize should be 0 unless you want to guarantee that at least 1 item is always produced. + */ + public static void addSmeltingBonus(String in, ItemStack out) { + smeltingBonus.put( in, new ItemStack(out.getItem(),0,out.getItemDamage())); + } + + /** + * Returns the bonus item produced from a smelting operation in the infernal furnace + * @param in The input of the smelting operation. e.g. new ItemStack(oreGold) + * @return the The bonus item that can be produced + */ + public static ItemStack getSmeltingBonus(ItemStack in) { + ItemStack out = smeltingBonus.get(Arrays.asList(in.getItem(),in.getItemDamage())); + if (out==null) { + out = smeltingBonus.get(Arrays.asList(in.getItem(),OreDictionary.WILDCARD_VALUE)); + } + if (out==null) { + String od = OreDictionary.getOreName( OreDictionary.getOreID(in)); + out = smeltingBonus.get(od); + } + return out; + } + + public static List getCraftingRecipes() { + return craftingRecipes; + } + + /** + * @param research the research key required for this recipe to work. Leave blank if it will work without research + * @param result the recipe output + * @param aspects the vis cost per aspect. + * @param recipe The recipe. Format is exactly the same as vanilla recipes. Input itemstacks are NBT sensitive. + */ + public static ShapedArcaneRecipe addArcaneCraftingRecipe(String research, ItemStack result, AspectList aspects, Object ... recipe) + { + ShapedArcaneRecipe r= new ShapedArcaneRecipe(research, result, aspects, recipe); + craftingRecipes.add(r); + return r; + } + + /** + * @param research the research key required for this recipe to work. Leave blank if it will work without research + * @param result the recipe output + * @param aspects the vis cost per aspect + * @param recipe The recipe. Format is exactly the same as vanilla shapeless recipes. Input itemstacks are NBT sensitive. + */ + public static ShapelessArcaneRecipe addShapelessArcaneCraftingRecipe(String research, ItemStack result, AspectList aspects, Object ... recipe) + { + ShapelessArcaneRecipe r = new ShapelessArcaneRecipe(research, result, aspects, recipe); + craftingRecipes.add(r); + return r; + } + + /** + * @param research the research key required for this recipe to work. Leave blank if it will work without research + * @param result the recipe output. It can either be an itemstack or an nbt compound tag that will be added to the central item + * @param instability a number that represents the N in 1000 chance for the infusion altar to spawn an + * instability effect each second while the crafting is in progress + * @param aspects the essentia cost per aspect. + * @param aspects input the central item to be infused + * @param recipe An array of items required to craft this. Input itemstacks are NBT sensitive. + * Infusion crafting components are automatically "fuzzy" and the oredict will be checked for possible matches. + * + */ + public static InfusionRecipe addInfusionCraftingRecipe(String research, Object result, int instability, AspectList aspects, ItemStack input,ItemStack[] recipe) + { + if (!(result instanceof ItemStack || result instanceof Object[])) return null; + InfusionRecipe r= new InfusionRecipe(research, result, instability, aspects, input, recipe); + craftingRecipes.add(r); + return r; + } + + /** + * @param research the research key required for this recipe to work. Leave blank if it will work without research + * @param enchantment the enchantment that will be applied to the item + * @param instability a number that represents the N in 1000 chance for the infusion altar to spawn an + * instability effect each second while the crafting is in progress + * @param aspects the essentia cost per aspect. + * @param recipe An array of items required to craft this. Input itemstacks are NBT sensitive. + * Infusion crafting components are automatically "fuzzy" and the oredict will be checked for possible matches. + * + */ + public static InfusionEnchantmentRecipe addInfusionEnchantmentRecipe(String research, Enchantment enchantment, int instability, AspectList aspects, ItemStack[] recipe) + { + InfusionEnchantmentRecipe r= new InfusionEnchantmentRecipe(research, enchantment, instability, aspects, recipe); + craftingRecipes.add(r); + return r; + } + + /** + * @param stack the recipe result + * @return the recipe + */ + public static InfusionRecipe getInfusionRecipe(ItemStack res) { + for (Object r:getCraftingRecipes()) { + if (r instanceof InfusionRecipe) { + if (((InfusionRecipe)r).getRecipeOutput() instanceof ItemStack) { + if (((ItemStack) ((InfusionRecipe)r).getRecipeOutput()).isItemEqual(res)) + return (InfusionRecipe)r; + } + } + } + return null; + } + + + /** + * @param key the research key required for this recipe to work. + * @param result the output result + * @param catalyst an itemstack of the catalyst or a string if it is an ore dictionary item + * @param cost the vis cost + * @param tags the aspects required to craft this + */ + public static CrucibleRecipe addCrucibleRecipe(String key, ItemStack result, Object catalyst, AspectList tags) { + CrucibleRecipe rc = new CrucibleRecipe(key, result, catalyst, tags); + getCraftingRecipes().add(rc); + return rc; + } + + + /** + * @param stack the recipe result + * @return the recipe + */ + public static CrucibleRecipe getCrucibleRecipe(ItemStack stack) { + for (Object r:getCraftingRecipes()) { + if (r instanceof CrucibleRecipe) { + if (((CrucibleRecipe)r).getRecipeOutput().isItemEqual(stack)) + return (CrucibleRecipe)r; + } + } + return null; + } + + /** + * @param hash the unique recipe code + * @return the recipe + */ + public static CrucibleRecipe getCrucibleRecipeFromHash(int hash) { + for (Object r:getCraftingRecipes()) { + if (r instanceof CrucibleRecipe) { + if (((CrucibleRecipe)r).hash==hash) + return (CrucibleRecipe)r; + } + } + return null; + } + + /** + * Used by the thaumonomicon drilldown feature. + * @param stack the item + * @return the thaumcraft recipe key that produces that item. + */ + private static HashMap keyCache = new HashMap(); + + public static Object[] getCraftingRecipeKey(EntityPlayer player, ItemStack stack) { + int[] key = new int[] {Item.getIdFromItem(stack.getItem()),stack.getItemDamage()}; + if (keyCache.containsKey(key)) { + if (keyCache.get(key)==null) return null; + if (ThaumcraftApiHelper.isResearchComplete(player.getCommandSenderName(), (String)(keyCache.get(key))[0])) + return keyCache.get(key); + else + return null; + } + for (ResearchCategoryList rcl:ResearchCategories.researchCategories.values()) { + for (ResearchItem ri:rcl.research.values()) { + if (ri.getPages()==null) continue; + for (int a=0;a objectTags = new ConcurrentHashMap(); + public static ConcurrentHashMap groupedObjectTags = new ConcurrentHashMap(); + + /** + * Checks to see if the passed item/block already has aspects associated with it. + * @param id + * @param meta + * @return + */ + public static boolean exists(Item item, int meta) { + AspectList tmp = ThaumcraftApi.objectTags.get(Arrays.asList(item,meta)); + if (tmp==null) { + tmp = ThaumcraftApi.objectTags.get(Arrays.asList(item,OreDictionary.WILDCARD_VALUE)); + if (meta==OreDictionary.WILDCARD_VALUE && tmp==null) { + int index=0; + do { + tmp = ThaumcraftApi.objectTags.get(Arrays.asList(item,index)); + index++; + } while (index<16 && tmp==null); + } + if (tmp==null) return false; + } + + return true; + } + + /** + * Used to assign apsects to the given item/block. Here is an example of the declaration for cobblestone:

+ * ThaumcraftApi.registerObjectTag(new ItemStack(Blocks.cobblestone), (new AspectList()).add(Aspect.ENTROPY, 1).add(Aspect.EARTH, 1)); + * @param item the item passed. Pass OreDictionary.WILDCARD_VALUE if all damage values of this item/block should have the same aspects + * @param aspects A ObjectTags object of the associated aspects + */ + public static void registerObjectTag(ItemStack item, AspectList aspects) { + if (aspects==null) aspects=new AspectList(); + try { + objectTags.put(Arrays.asList(item.getItem(),item.getItemDamage()), aspects); + } catch (Exception e) {} + } + + + /** + * Used to assign apsects to the given item/block. Here is an example of the declaration for cobblestone:

+ * ThaumcraftApi.registerObjectTag(new ItemStack(Blocks.cobblestone), new int[]{0,1}, (new AspectList()).add(Aspect.ENTROPY, 1).add(Aspect.EARTH, 1)); + * @param item + * @param meta A range of meta values if you wish to lump several item meta's together as being the "same" item (i.e. stair orientations) + * @param aspects A ObjectTags object of the associated aspects + */ + public static void registerObjectTag(ItemStack item, int[] meta, AspectList aspects) { + if (aspects==null) aspects=new AspectList(); + try { + objectTags.put(Arrays.asList(item.getItem(),meta[0]), aspects); + for (int m:meta) { + groupedObjectTags.put(Arrays.asList(item.getItem(),m), meta); + } + + } catch (Exception e) {} + } + + /** + * Used to assign apsects to the given ore dictionary item. + * @param oreDict the ore dictionary name + * @param aspects A ObjectTags object of the associated aspects + */ + public static void registerObjectTag(String oreDict, AspectList aspects) { + if (aspects==null) aspects=new AspectList(); + ArrayList ores = OreDictionary.getOres(oreDict); + if (ores!=null && ores.size()>0) { + for (ItemStack ore:ores) { + try { + objectTags.put(Arrays.asList(ore.getItem(), ore.getItemDamage()), aspects); + } catch (Exception e) {} + } + } + } + + /** + * Used to assign aspects to the given item/block. + * Attempts to automatically generate aspect tags by checking registered recipes. + * Here is an example of the declaration for pistons:

+ * ThaumcraftApi.registerComplexObjectTag(new ItemStack(Blocks.cobblestone), (new AspectList()).add(Aspect.MECHANISM, 2).add(Aspect.MOTION, 4)); + * IMPORTANT - this should only be used if you are not happy with the default aspects the object would be assigned. + * @param item, pass OreDictionary.WILDCARD_VALUE to meta if all damage values of this item/block should have the same aspects + * @param aspects A ObjectTags object of the associated aspects + */ + public static void registerComplexObjectTag(ItemStack item, AspectList aspects ) { + if (!exists(item.getItem(),item.getItemDamage())) { + AspectList tmp = ThaumcraftApiHelper.generateTags(item.getItem(), item.getItemDamage()); + if (tmp != null && tmp.size()>0) { + for(Aspect tag:tmp.getAspects()) { + aspects.add(tag, tmp.getAmount(tag)); + } + } + registerObjectTag(item,aspects); + } else { + AspectList tmp = ThaumcraftApiHelper.getObjectAspects(item); + for(Aspect tag:aspects.getAspects()) { + tmp.merge(tag, tmp.getAmount(tag)); + } + registerObjectTag(item,tmp); + } + } + + //WARP /////////////////////////////////////////////////////////////////////////////////////// + private static HashMap warpMap = new HashMap(); + + /** + * This method is used to determine how much warp is gained if the item is crafted. The warp + * added is "sticky" warp + * @param craftresult The item crafted + * @param amount how much warp is gained + */ + public static void addWarpToItem(ItemStack craftresult, int amount) { + warpMap.put(Arrays.asList(craftresult.getItem(),craftresult.getItemDamage()),amount); + } + + /** + * This method is used to determine how much permanent warp is gained if the research is completed + * @param in The item crafted + * @param amount how much warp is gained + */ + public static void addWarpToResearch(String research, int amount) { + warpMap.put(research, amount); + } + + /** + * Returns how much warp is gained from the item or research passed in + * @param in itemstack or string + * @return how much warp it will give + */ + public static int getWarp(Object in) { + if (in==null) return 0; + if (in instanceof ItemStack && warpMap.containsKey(Arrays.asList(((ItemStack)in).getItem(),((ItemStack)in).getItemDamage()))) { + return warpMap.get(Arrays.asList(((ItemStack)in).getItem(),((ItemStack)in).getItemDamage())); + } else + if (in instanceof String && warpMap.containsKey((String)in)) { + return warpMap.get((String)in); + } + return 0; + } + + //LOOT BAGS ////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Used to add possible loot to treasure bags. As a reference, the weight of gold coins are 2000 + * and a diamond is 50. + * The weights are the same for all loot bag types - the only difference is how many items the bag + * contains. + * @param item + * @param weight + * @param bagTypes array of which type of bag to add this loot to. Multiple types can be specified + * 0 = common, 1 = uncommon, 2 = rare + */ + public static void addLootBagItem(ItemStack item, int weight, int... bagTypes) { + if (bagTypes==null || bagTypes.length==0) + WeightedRandomLoot.lootBagCommon.add(new WeightedRandomLoot(item,weight)); + else { + for (int rarity:bagTypes) { + switch(rarity) { + case 0: WeightedRandomLoot.lootBagCommon.add(new WeightedRandomLoot(item,weight)); break; + case 1: WeightedRandomLoot.lootBagUncommon.add(new WeightedRandomLoot(item,weight)); break; + case 2: WeightedRandomLoot.lootBagRare.add(new WeightedRandomLoot(item,weight)); break; + } + } + } + } + + //CROPS ////////////////////////////////////////////////////////////////////////////////////////// + + /** + * To define mod crops you need to use FMLInterModComms in your @Mod.Init method. + * There are two 'types' of crops you can add. Standard crops and clickable crops. + * + * Standard crops work like normal vanilla crops - they grow until a certain metadata + * value is reached and you harvest them by destroying the block and collecting the blocks. + * You need to create and ItemStack that tells the golem what block id and metadata represents + * the crop when fully grown. Sending a metadata of [OreDictionary.WILDCARD_VALUE] will mean the metadata won't get + * checked. + * Example for vanilla wheat: + * FMLInterModComms.sendMessage("Thaumcraft", "harvestStandardCrop", new ItemStack(Block.crops,1,7)); + * + * Clickable crops are crops that you right click to gather their bounty instead of destroying them. + * As for standard crops, you need to create and ItemStack that tells the golem what block id + * and metadata represents the crop when fully grown. The golem will trigger the blocks onBlockActivated method. + * Sending a metadata of [OreDictionary.WILDCARD_VALUE] will mean the metadata won't get checked. + * Example (this will technically do nothing since clicking wheat does nothing, but you get the idea): + * FMLInterModComms.sendMessage("Thaumcraft", "harvestClickableCrop", new ItemStack(Block.crops,1,7)); + * + * Stacked crops (like reeds) are crops that you wish the bottom block should remain after harvesting. + * As for standard crops, you need to create and ItemStack that tells the golem what block id + * and metadata represents the crop when fully grown. Sending a metadata of [OreDictionary.WILDCARD_VALUE] will mean the actualy md won't get + * checked. If it has the order upgrade it will only harvest if the crop is more than one block high. + * Example: + * FMLInterModComms.sendMessage("Thaumcraft", "harvestStackedCrop", new ItemStack(Block.reed,1,7)); + */ + + //NATIVE CLUSTERS ////////////////////////////////////////////////////////////////////////////////// + + /** + * You can define certain ores that will have a chance to produce native clusters via FMLInterModComms + * in your @Mod.Init method using the "nativeCluster" string message. + * The format should be: + * "[ore item/block id],[ore item/block metadata],[cluster item/block id],[cluster item/block metadata],[chance modifier float]" + * + * NOTE: The chance modifier is a multiplier applied to the default chance for that cluster to be produced (default 27.5% for a pickaxe of the core) + * + * Example for vanilla iron ore to produce one of my own native iron clusters (assuming default id's) at double the default chance: + * FMLInterModComms.sendMessage("Thaumcraft", "nativeCluster","15,0,25016,16,2.0"); + */ + + //LAMP OF GROWTH BLACKLIST /////////////////////////////////////////////////////////////////////////// + /** + * You can blacklist crops that should not be effected by the Lamp of Growth via FMLInterModComms + * in your @Mod.Init method using the "lampBlacklist" itemstack message. + * Sending a metadata of [OreDictionary.WILDCARD_VALUE] will mean the metadata won't get checked. + * Example for vanilla wheat: + * FMLInterModComms.sendMessage("Thaumcraft", "lampBlacklist", new ItemStack(Block.crops,1,OreDictionary.WILDCARD_VALUE)); + */ + + //DIMENSION BLACKLIST /////////////////////////////////////////////////////////////////////////// + /** + * You can blacklist a dimension to not spawn certain thaumcraft features + * in your @Mod.Init method using the "dimensionBlacklist" string message in the format "[dimension]:[level]" + * The level values are as follows: + * [0] stop all tc spawning and generation + * [1] allow ore and node generation (and node special features) + * [2] allow mob spawning + * [3] allow ore and node gen + mob spawning (and node special features) + * Example: + * FMLInterModComms.sendMessage("Thaumcraft", "dimensionBlacklist", "15:1"); + */ + + //BIOME BLACKLIST /////////////////////////////////////////////////////////////////////////// + /** + * You can blacklist a biome to not spawn certain thaumcraft features + * in your @Mod.Init method using the "biomeBlacklist" string message in the format "[biome id]:[level]" + * The level values are as follows: + * [0] stop all tc spawning and generation + * [1] allow ore and node generation (and node special features) + * [2] allow mob spawning + * [3] allow ore and node gen + mob spawning (and node special features) + * Example: + * FMLInterModComms.sendMessage("Thaumcraft", "biomeBlacklist", "180:2"); + */ + + //CHAMPION MOB WHITELIST /////////////////////////////////////////////////////////////////////////// + /** + * You can whitelist an entity class so it can rarely spawn champion versions in your @Mod.Init method using + * the "championWhiteList" string message in the format "[Entity]:[level]" + * The entity must extend EntityMob. + * [Entity] is in a similar format to what is used for mob spawners and such (see EntityList.class for vanilla examples). + * The [level] value indicate how rare the champion version will be - the higher the number the more common. + * The number roughly equals the [n] in 100 chance of a mob being a champion version. + * You can give 0 or negative numbers to allow champions to spawn with a very low chance only in particularly dangerous places. + * However anything less than about -2 will probably result in no spawns at all. + * Example: + * FMLInterModComms.sendMessage("Thaumcraft", "championWhiteList", "Thaumcraft.Wisp:1"); + */ +} diff --git a/src/main/java/thaumcraft/api/ThaumcraftApiHelper.java b/src/main/java/thaumcraft/api/ThaumcraftApiHelper.java new file mode 100644 index 00000000..77d55885 --- /dev/null +++ b/src/main/java/thaumcraft/api/ThaumcraftApiHelper.java @@ -0,0 +1,467 @@ +package thaumcraft.api; + +import java.util.HashMap; +import java.util.Iterator; + +import net.minecraft.block.Block; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.MathHelper; +import net.minecraft.util.MovingObjectPosition; +import net.minecraft.util.Vec3; +import net.minecraft.world.IBlockAccess; +import net.minecraft.world.World; +import net.minecraftforge.common.util.ForgeDirection; +import net.minecraftforge.oredict.OreDictionary; +import thaumcraft.api.aspects.Aspect; +import thaumcraft.api.aspects.AspectList; +import thaumcraft.api.aspects.IEssentiaTransport; + +public class ThaumcraftApiHelper { + + public static AspectList cullTags(AspectList temp) { + AspectList temp2 = new AspectList(); + for (Aspect tag:temp.getAspects()) { + if (tag!=null) + temp2.add(tag, temp.getAmount(tag)); + } + while (temp2!=null && temp2.size()>6) { + Aspect lowest = null; + float low = Short.MAX_VALUE; + for (Aspect tag:temp2.getAspects()) { + if (tag==null) continue; + float ta=temp2.getAmount(tag); + if (tag.isPrimal()) { + ta *= .9f; + } else { + if (!tag.getComponents()[0].isPrimal()) { + ta *= 1.1f; + if (!tag.getComponents()[0].getComponents()[0].isPrimal()) { + ta *= 1.05f; + } + if (!tag.getComponents()[0].getComponents()[1].isPrimal()) { + ta *= 1.05f; + } + } + if (!tag.getComponents()[1].isPrimal()) { + ta *= 1.1f; + if (!tag.getComponents()[1].getComponents()[0].isPrimal()) { + ta *= 1.05f; + } + if (!tag.getComponents()[1].getComponents()[1].isPrimal()) { + ta *= 1.05f; + } + } + } + + if (ta allAspects= new HashMap(); + private static HashMap allCompoundAspects= new HashMap(); + + public static AspectList getAllAspects(int amount) { + if (allAspects.get(amount)==null) { + AspectList al = new AspectList(); + for (Aspect aspect:Aspect.aspects.values()) { + al.add(aspect, amount); + } + allAspects.put(amount, al); + } + return allAspects.get(amount); + } + + public static AspectList getAllCompoundAspects(int amount) { + if (allCompoundAspects.get(amount)==null) { + AspectList al = new AspectList(); + for (Aspect aspect:Aspect.getCompoundAspects()) { + al.add(aspect, amount); + } + allCompoundAspects.put(amount, al); + } + return allCompoundAspects.get(amount); + } + + + /** + * Use to subtract vis from a wand for most operations + * Wands store vis differently so "real" vis costs need to be multiplied by 100 before calling this method + * @param wand the wand itemstack + * @param player the player using the wand + * @param cost the cost of the operation. + * @param doit actually subtract the vis from the wand if true - if false just simulate the result + * @param crafting is this a crafting operation or not - if + * false then things like frugal and potency will apply to the costs + * @return was the vis successfully subtracted + */ + public static boolean consumeVisFromWand(ItemStack wand, EntityPlayer player, + AspectList cost, boolean doit, boolean crafting) { + return ThaumcraftApi.internalMethods.consumeVisFromWand(wand, player, cost, doit, crafting); + } + + /** + * Subtract vis for use by a crafting mechanic. Costs are calculated slightly + * differently and things like the frugal enchant is ignored + * Must NOT be multiplied by 100 - send the actual vis cost + * @param wand the wand itemstack + * @param player the player using the wand + * @param cost the cost of the operation. + * @param doit actually subtract the vis from the wand if true - if false just simulate the result + * @return was the vis successfully subtracted + */ + public static boolean consumeVisFromWandCrafting(ItemStack wand, EntityPlayer player, + AspectList cost, boolean doit) { + return ThaumcraftApi.internalMethods.consumeVisFromWandCrafting(wand, player, cost, doit); + } + + /** + * Subtract vis from a wand the player is carrying. Works like consumeVisFromWand in that actual vis + * costs should be multiplied by 100. The costs are handled like crafting however and things like + * frugal don't effect them + * @param player the player using the wand + * @param cost the cost of the operation. + * @return was the vis successfully subtracted + */ + public static boolean consumeVisFromInventory(EntityPlayer player, AspectList cost) { + return ThaumcraftApi.internalMethods.consumeVisFromInventory(player, cost); + } + + + /** + * This adds permanents or temporary warp to a player. It will automatically be synced clientside + * @param player the player using the wand + * @param amount how much warp to add. Negative amounts are only valid for temporary warp + * @param temporary add temporary warp instead of permanent + */ + public static void addWarpToPlayer(EntityPlayer player, int amount, boolean temporary) { + ThaumcraftApi.internalMethods.addWarpToPlayer(player, amount, temporary); + } + + /** + * This "sticky" warp to a player. Sticky warp is permanent warp that can be removed. + * It will automatically be synced clientside + * @param player the player using the wand + * @param amount how much warp to add. Can have negative amounts. + */ + public static void addStickyWarpToPlayer(EntityPlayer player, int amount) { + ThaumcraftApi.internalMethods.addStickyWarpToPlayer(player, amount); + } + + public static MovingObjectPosition rayTraceIgnoringSource(World world, Vec3 v1, Vec3 v2, + boolean bool1, boolean bool2, boolean bool3) + { + if (!Double.isNaN(v1.xCoord) && !Double.isNaN(v1.yCoord) && !Double.isNaN(v1.zCoord)) + { + if (!Double.isNaN(v2.xCoord) && !Double.isNaN(v2.yCoord) && !Double.isNaN(v2.zCoord)) + { + int i = MathHelper.floor_double(v2.xCoord); + int j = MathHelper.floor_double(v2.yCoord); + int k = MathHelper.floor_double(v2.zCoord); + int l = MathHelper.floor_double(v1.xCoord); + int i1 = MathHelper.floor_double(v1.yCoord); + int j1 = MathHelper.floor_double(v1.zCoord); + Block block = world.getBlock(l, i1, j1); + int k1 = world.getBlockMetadata(l, i1, j1); + + MovingObjectPosition movingobjectposition2 = null; + k1 = 200; + + while (k1-- >= 0) + { + if (Double.isNaN(v1.xCoord) || Double.isNaN(v1.yCoord) || Double.isNaN(v1.zCoord)) + { + return null; + } + + if (l == i && i1 == j && j1 == k) + { + continue; + } + + boolean flag6 = true; + boolean flag3 = true; + boolean flag4 = true; + double d0 = 999.0D; + double d1 = 999.0D; + double d2 = 999.0D; + + if (i > l) + { + d0 = (double)l + 1.0D; + } + else if (i < l) + { + d0 = (double)l + 0.0D; + } + else + { + flag6 = false; + } + + if (j > i1) + { + d1 = (double)i1 + 1.0D; + } + else if (j < i1) + { + d1 = (double)i1 + 0.0D; + } + else + { + flag3 = false; + } + + if (k > j1) + { + d2 = (double)j1 + 1.0D; + } + else if (k < j1) + { + d2 = (double)j1 + 0.0D; + } + else + { + flag4 = false; + } + + double d3 = 999.0D; + double d4 = 999.0D; + double d5 = 999.0D; + double d6 = v2.xCoord - v1.xCoord; + double d7 = v2.yCoord - v1.yCoord; + double d8 = v2.zCoord - v1.zCoord; + + if (flag6) + { + d3 = (d0 - v1.xCoord) / d6; + } + + if (flag3) + { + d4 = (d1 - v1.yCoord) / d7; + } + + if (flag4) + { + d5 = (d2 - v1.zCoord) / d8; + } + + boolean flag5 = false; + byte b0; + + if (d3 < d4 && d3 < d5) + { + if (i > l) + { + b0 = 4; + } + else + { + b0 = 5; + } + + v1.xCoord = d0; + v1.yCoord += d7 * d3; + v1.zCoord += d8 * d3; + } + else if (d4 < d5) + { + if (j > i1) + { + b0 = 0; + } + else + { + b0 = 1; + } + + v1.xCoord += d6 * d4; + v1.yCoord = d1; + v1.zCoord += d8 * d4; + } + else + { + if (k > j1) + { + b0 = 2; + } + else + { + b0 = 3; + } + + v1.xCoord += d6 * d5; + v1.yCoord += d7 * d5; + v1.zCoord = d2; + } + + Vec3 vec32 = Vec3.createVectorHelper(v1.xCoord, v1.yCoord, v1.zCoord); + l = (int)(vec32.xCoord = (double)MathHelper.floor_double(v1.xCoord)); + + if (b0 == 5) + { + --l; + ++vec32.xCoord; + } + + i1 = (int)(vec32.yCoord = (double)MathHelper.floor_double(v1.yCoord)); + + if (b0 == 1) + { + --i1; + ++vec32.yCoord; + } + + j1 = (int)(vec32.zCoord = (double)MathHelper.floor_double(v1.zCoord)); + + if (b0 == 3) + { + --j1; + ++vec32.zCoord; + } + + Block block1 = world.getBlock(l, i1, j1); + int l1 = world.getBlockMetadata(l, i1, j1); + + if (!bool2 || block1.getCollisionBoundingBoxFromPool(world, l, i1, j1) != null) + { + if (block1.canCollideCheck(l1, bool1)) + { + MovingObjectPosition movingobjectposition1 = block1.collisionRayTrace(world, l, i1, j1, v1, v2); + + if (movingobjectposition1 != null) + { + return movingobjectposition1; + } + } + else + { + movingobjectposition2 = new MovingObjectPosition(l, i1, j1, b0, v1, false); + } + } + } + + return bool3 ? movingobjectposition2 : null; + } + else + { + return null; + } + } + else + { + return null; + } + } +} diff --git a/src/main/java/thaumcraft/api/aspects/Aspect.java b/src/main/java/thaumcraft/api/aspects/Aspect.java new file mode 100644 index 00000000..0ea13f5e --- /dev/null +++ b/src/main/java/thaumcraft/api/aspects/Aspect.java @@ -0,0 +1,201 @@ +package thaumcraft.api.aspects; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashMap; + +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.StatCollector; + +import org.apache.commons.lang3.text.WordUtils; + +public class Aspect { + + String tag; + Aspect[] components; + int color; + private String chatcolor; + ResourceLocation image; + int blend; + + /** + * Use this constructor to register your own aspects. + * @param tag the key that will be used to reference this aspect, as well as its latin display name + * @param color color to display the tag in + * @param components the aspects this one is formed from + * @param image ResourceLocation pointing to a 32x32 icon of the aspect + * @param blend GL11 blendmode (1 or 771). Used for rendering nodes. Default is 1 + */ + public Aspect(String tag, int color, Aspect[] components, ResourceLocation image, int blend) { + if (aspects.containsKey(tag)) throw new IllegalArgumentException(tag+" already registered!"); + this.tag = tag; + this.components = components; + this.color = color; + this.image = image; + this.blend = blend; + aspects.put(tag, this); + } + + /** + * Shortcut constructor I use for the default aspects - you shouldn't be using this. + */ + public Aspect(String tag, int color, Aspect[] components) { + this(tag,color,components,new ResourceLocation("thaumcraft","textures/aspects/"+tag.toLowerCase()+".png"),1); + } + + /** + * Shortcut constructor I use for the default aspects - you shouldn't be using this. + */ + public Aspect(String tag, int color, Aspect[] components, int blend) { + this(tag,color,components,new ResourceLocation("thaumcraft","textures/aspects/"+tag.toLowerCase()+".png"),blend); + } + + /** + * Shortcut constructor I use for the primal aspects - + * you shouldn't use this as making your own primal aspects will break all the things. + */ + public Aspect(String tag, int color, String chatcolor, int blend) { + this(tag,color,(Aspect[])null, blend); + this.setChatcolor(chatcolor); + } + + public int getColor() { + return color; + } + + public String getName() { + return WordUtils.capitalizeFully(tag); + } + + public String getLocalizedDescription() { + return StatCollector.translateToLocal("tc.aspect."+tag); + } + + public String getTag() { + return tag; + } + + public void setTag(String tag) { + this.tag = tag; + } + + public Aspect[] getComponents() { + return components; + } + + public void setComponents(Aspect[] components) { + this.components = components; + } + + public ResourceLocation getImage() { + return image; + } + + public static Aspect getAspect(String tag) { + return aspects.get(tag); + } + + public int getBlend() { + return blend; + } + + public void setBlend(int blend) { + this.blend = blend; + } + + public boolean isPrimal() { + return getComponents()==null || getComponents().length!=2; + } + + /////////////////////////////// + public static ArrayList getPrimalAspects() { + ArrayList primals = new ArrayList(); + Collection pa = aspects.values(); + for (Aspect aspect:pa) { + if (aspect.isPrimal()) primals.add(aspect); + } + return primals; + } + + public static ArrayList getCompoundAspects() { + ArrayList compounds = new ArrayList(); + Collection pa = aspects.values(); + for (Aspect aspect:pa) { + if (!aspect.isPrimal()) compounds.add(aspect); + } + return compounds; + } + + public String getChatcolor() { + return chatcolor; + } + + public void setChatcolor(String chatcolor) { + this.chatcolor = chatcolor; + } + + + /////////////////////////////// + public static LinkedHashMap aspects = new LinkedHashMap(); + + //PRIMAL + public static final Aspect AIR = new Aspect("aer",0xffff7e,"e",1); + public static final Aspect EARTH = new Aspect("terra",0x56c000,"2",1); + public static final Aspect FIRE = new Aspect("ignis",0xff5a01,"c",1); + public static final Aspect WATER = new Aspect("aqua",0x3cd4fc,"3",1); + public static final Aspect ORDER = new Aspect("ordo",0xd5d4ec,"7",1); + public static final Aspect ENTROPY = new Aspect("perditio",0x404040,"8",771); + + //SECONDARY + public static final Aspect VOID = new Aspect("vacuos",0x888888, new Aspect[] {AIR, ENTROPY},771); + public static final Aspect LIGHT = new Aspect("lux",0xfff663, new Aspect[] {AIR, FIRE}); + public static final Aspect WEATHER = new Aspect("tempestas",0xFFFFFF, new Aspect[] {AIR, WATER}); + public static final Aspect MOTION = new Aspect("motus",0xcdccf4, new Aspect[] {AIR, ORDER}); + public static final Aspect COLD = new Aspect("gelum",0xe1ffff, new Aspect[] {FIRE, ENTROPY}); + public static final Aspect CRYSTAL = new Aspect("vitreus",0x80ffff, new Aspect[] {EARTH, ORDER}); + public static final Aspect LIFE = new Aspect("victus",0xde0005, new Aspect[] {WATER, EARTH}); + public static final Aspect POISON = new Aspect("venenum",0x89f000, new Aspect[] {WATER, ENTROPY}); + public static final Aspect ENERGY = new Aspect("potentia",0xc0ffff, new Aspect[] {ORDER, FIRE}); + public static final Aspect EXCHANGE = new Aspect("permutatio",0x578357, new Aspect[] {ENTROPY, ORDER}); +// public static final Aspect ?? = new Aspect("??",0xcdccf4, new Aspect[] {AIR, EARTH}); +// public static final Aspect ?? = new Aspect("??",0xcdccf4, new Aspect[] {FIRE, EARTH}); +// public static final Aspect ?? = new Aspect("??",0xcdccf4, new Aspect[] {FIRE, WATER}); +// public static final Aspect ?? = new Aspect("??",0xcdccf4, new Aspect[] {ORDER, WATER}); +// public static final Aspect ?? = new Aspect("??",0xcdccf4, new Aspect[] {EARTH, ENTROPY}); + + //TERTIARY + public static final Aspect METAL = new Aspect("metallum",0xb5b5cd, new Aspect[] {EARTH, CRYSTAL}); + public static final Aspect DEATH = new Aspect("mortuus",0x887788, new Aspect[] {LIFE, ENTROPY}); + public static final Aspect FLIGHT = new Aspect("volatus",0xe7e7d7, new Aspect[] {AIR, MOTION}); + public static final Aspect DARKNESS = new Aspect("tenebrae",0x222222, new Aspect[] {VOID, LIGHT}); + public static final Aspect SOUL = new Aspect("spiritus",0xebebfb, new Aspect[] {LIFE, DEATH}); + public static final Aspect HEAL = new Aspect("sano",0xff2f34, new Aspect[] {LIFE, ORDER}); + public static final Aspect TRAVEL = new Aspect("iter",0xe0585b, new Aspect[] {MOTION, EARTH}); + public static final Aspect ELDRITCH = new Aspect("alienis",0x805080, new Aspect[] {VOID, DARKNESS}); + public static final Aspect MAGIC = new Aspect("praecantatio",0x9700c0, new Aspect[] {VOID, ENERGY}); + public static final Aspect AURA = new Aspect("auram",0xffc0ff, new Aspect[] {MAGIC, AIR}); + public static final Aspect TAINT = new Aspect("vitium",0x800080, new Aspect[] {MAGIC, ENTROPY}); + public static final Aspect SLIME = new Aspect("limus",0x01f800, new Aspect[] {LIFE, WATER}); + public static final Aspect PLANT = new Aspect("herba",0x01ac00, new Aspect[] {LIFE, EARTH}); + public static final Aspect TREE = new Aspect("arbor",0x876531, new Aspect[] {AIR, PLANT}); + public static final Aspect BEAST = new Aspect("bestia",0x9f6409, new Aspect[] {MOTION, LIFE}); + public static final Aspect FLESH = new Aspect("corpus",0xee478d, new Aspect[] {DEATH, BEAST}); + public static final Aspect UNDEAD = new Aspect("exanimis",0x3a4000, new Aspect[] {MOTION, DEATH}); + public static final Aspect MIND = new Aspect("cognitio",0xffc2b3, new Aspect[] {FIRE, SOUL}); + public static final Aspect SENSES = new Aspect("sensus",0x0fd9ff, new Aspect[] {AIR, SOUL}); + public static final Aspect MAN = new Aspect("humanus",0xffd7c0, new Aspect[] {BEAST, MIND}); + public static final Aspect CROP = new Aspect("messis",0xe1b371, new Aspect[] {PLANT, MAN}); + public static final Aspect MINE = new Aspect("perfodio",0xdcd2d8, new Aspect[] {MAN, EARTH}); + public static final Aspect TOOL = new Aspect("instrumentum",0x4040ee, new Aspect[] {MAN, ORDER}); + public static final Aspect HARVEST = new Aspect("meto",0xeead82, new Aspect[] {CROP, TOOL}); + public static final Aspect WEAPON = new Aspect("telum",0xc05050, new Aspect[] {TOOL, FIRE}); + public static final Aspect ARMOR = new Aspect("tutamen",0x00c0c0, new Aspect[] {TOOL, EARTH}); + public static final Aspect HUNGER = new Aspect("fames",0x9a0305, new Aspect[] {LIFE, VOID}); + public static final Aspect GREED = new Aspect("lucrum",0xe6be44, new Aspect[] {MAN, HUNGER}); + public static final Aspect CRAFT = new Aspect("fabrico",0x809d80, new Aspect[] {MAN, TOOL}); + public static final Aspect CLOTH = new Aspect("pannus",0xeaeac2, new Aspect[] {TOOL, BEAST}); + public static final Aspect MECHANISM = new Aspect("machina",0x8080a0, new Aspect[] {MOTION, TOOL}); + public static final Aspect TRAP = new Aspect("vinculum",0x9a8080, new Aspect[] {MOTION, ENTROPY}); + + +} diff --git a/src/main/java/thaumcraft/api/aspects/AspectList.java b/src/main/java/thaumcraft/api/aspects/AspectList.java new file mode 100644 index 00000000..b5781410 --- /dev/null +++ b/src/main/java/thaumcraft/api/aspects/AspectList.java @@ -0,0 +1,292 @@ +package thaumcraft.api.aspects; + +import java.io.Serializable; +import java.util.LinkedHashMap; + +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.nbt.NBTTagList; +import thaumcraft.api.ThaumcraftApiHelper; + +public class AspectList implements Serializable { + + public LinkedHashMap aspects = new LinkedHashMap();//aspects associated with this object + + + /** + * this creates a new aspect list with preloaded values based off the aspects of the given item. + * @param the itemstack of the given item + */ + public AspectList(ItemStack stack) { + try { + AspectList temp = ThaumcraftApiHelper.getObjectAspects(stack); + if (temp!=null) + for (Aspect tag:temp.getAspects()) { + add(tag,temp.getAmount(tag)); + } + } catch (Exception e) {} + } + + public AspectList() { + } + + public AspectList copy() { + AspectList out = new AspectList(); + for (Aspect a:this.getAspects()) + out.add(a, this.getAmount(a)); + return out; + } + + /** + * @return the amount of different aspects in this collection + */ + public int size() { + return aspects.size(); + } + + /** + * @return the amount of total vis in this collection + */ + public int visSize() { + int q = 0; + + for (Aspect as:aspects.keySet()) { + q+=this.getAmount(as); + } + + return q; + } + + /** + * @return an array of all the aspects in this collection + */ + public Aspect[] getAspects() { + Aspect[] q = new Aspect[1]; + return aspects.keySet().toArray(q); + } + + /** + * @return an array of all the aspects in this collection + */ + public Aspect[] getPrimalAspects() { + AspectList t = new AspectList(); + for (Aspect as:aspects.keySet()) { + if (as.isPrimal()) { + t.add(as,1); + } + } + Aspect[] q = new Aspect[1]; + return t.aspects.keySet().toArray(q); + } + + /** + * @return an array of all the aspects in this collection sorted by name + */ + public Aspect[] getAspectsSorted() { + try { + Aspect[] out = aspects.keySet().toArray(new Aspect[]{}); + boolean change=false; + do { + change=false; + for(int a=0;a0) { + out[a] = e2; + out[a+1] = e1; + change = true; + break; + } + } + } while (change==true); + return out; + } catch (Exception e) { + return this.getAspects(); + } + } + + /** + * @return an array of all the aspects in this collection sorted by amount + */ + public Aspect[] getAspectsSortedAmount() { + try { + Aspect[] out = aspects.keySet().toArray(new Aspect[1]); + boolean change=false; + do { + change=false; + for(int a=0;a0 && e2>0 && e2>e1) { + Aspect ea = out[a]; + Aspect eb = out[a+1]; + out[a] = eb; + out[a+1] = ea; + change = true; + break; + } + } + } while (change==true); + return out; + } catch (Exception e) { + return this.getAspects(); + } + } + + /** + * @param key + * @return the amount associated with the given aspect in this collection + */ + public int getAmount(Aspect key) { + return aspects.get(key)==null?0:aspects.get(key); + } + + /** + * Reduces the amount of an aspect in this collection by the given amount. + * @param key + * @param amount + * @return + */ + public boolean reduce(Aspect key, int amount) { + if (getAmount(key)>=amount) { + int am = getAmount(key)-amount; + aspects.put(key, am); + return true; + } + return false; + } + + /** + * Reduces the amount of an aspect in this collection by the given amount. + * If reduced to 0 or less the aspect will be removed completely. + * @param key + * @param amount + * @return + */ + public AspectList remove(Aspect key, int amount) { + int am = getAmount(key)-amount; + if (am<=0) aspects.remove(key); else + this.aspects.put(key, am); + return this; + } + + /** + * Simply removes the aspect from the list + * @param key + * @param amount + * @return + */ + public AspectList remove(Aspect key) { + aspects.remove(key); + return this; + } + + /** + * Adds this aspect and amount to the collection. + * If the aspect exists then its value will be increased by the given amount. + * @param aspect + * @param amount + * @return + */ + public AspectList add(Aspect aspect, int amount) { + if (this.aspects.containsKey(aspect)) { + int oldamount = this.aspects.get(aspect); + amount+=oldamount; + } + this.aspects.put( aspect, amount ); + return this; + } + + + /** + * Adds this aspect and amount to the collection. + * If the aspect exists then only the highest of the old or new amount will be used. + * @param aspect + * @param amount + * @return + */ + public AspectList merge(Aspect aspect, int amount) { + if (this.aspects.containsKey(aspect)) { + int oldamount = this.aspects.get(aspect); + if (amount0?aspects:null; + } + return null; + } + + @Override + public void setAspects(ItemStack itemstack, AspectList aspects) { + if (!itemstack.hasTagCompound()) itemstack.setTagCompound(new NBTTagCompound()); + aspects.writeToNBT(itemstack.getTagCompound()); + } +*/ \ No newline at end of file diff --git a/src/main/java/thaumcraft/api/aspects/IEssentiaTransport.java b/src/main/java/thaumcraft/api/aspects/IEssentiaTransport.java new file mode 100644 index 00000000..fecbc160 --- /dev/null +++ b/src/main/java/thaumcraft/api/aspects/IEssentiaTransport.java @@ -0,0 +1,100 @@ +package thaumcraft.api.aspects; + +import net.minecraftforge.common.util.ForgeDirection; + + +/** + * @author Azanor + * This interface is used by tiles that use or transport vis. + * Only tiles that implement this interface will be able to connect to vis conduits or other thaumic devices + */ +public interface IEssentiaTransport { + /** + * Is this tile able to connect to other vis users/sources on the specified side? + * @param face + * @return + */ + public boolean isConnectable(ForgeDirection face); + + /** + * Is this side used to input essentia? + * @param face + * @return + */ + boolean canInputFrom(ForgeDirection face); + + /** + * Is this side used to output essentia? + * @param face + * @return + */ + boolean canOutputTo(ForgeDirection face); + + /** + * Sets the amount of suction this block will apply + * @param suction + */ + public void setSuction(Aspect aspect, int amount); + + /** + * Returns the type of suction this block is applying. + * @param loc + * the location from where the suction is being checked + * @return + * a return type of null indicates the suction is untyped and the first thing available will be drawn + */ + public Aspect getSuctionType(ForgeDirection face); + + /** + * Returns the strength of suction this block is applying. + * @param loc + * the location from where the suction is being checked + * @return + */ + public int getSuctionAmount(ForgeDirection face); + + /** + * remove the specified amount of essentia from this transport tile + * @return how much was actually taken + */ + public int takeEssentia(Aspect aspect, int amount, ForgeDirection face); + + /** + * add the specified amount of essentia to this transport tile + * @return how much was actually added + */ + public int addEssentia(Aspect aspect, int amount, ForgeDirection face); + + /** + * What type of essentia this contains + * @param face + * @return + */ + public Aspect getEssentiaType(ForgeDirection face); + + /** + * How much essentia this block contains + * @param face + * @return + */ + public int getEssentiaAmount(ForgeDirection face); + + + + /** + * Essentia will not be drawn from this container unless the suction exceeds this amount. + * @return the amount + */ + public int getMinimumSuction(); + + /** + * Return true if you want the conduit to extend a little further into the block. + * Used by jars and alembics that have smaller than normal hitboxes + * @return + */ + boolean renderExtendedTube(); + + + +} + diff --git a/src/main/java/thaumcraft/api/crafting/CrucibleRecipe.java b/src/main/java/thaumcraft/api/crafting/CrucibleRecipe.java new file mode 100644 index 00000000..595b6e53 --- /dev/null +++ b/src/main/java/thaumcraft/api/crafting/CrucibleRecipe.java @@ -0,0 +1,91 @@ +package thaumcraft.api.crafting; + +import java.util.ArrayList; + +import net.minecraft.item.ItemStack; +import net.minecraftforge.oredict.OreDictionary; +import thaumcraft.api.ThaumcraftApiHelper; +import thaumcraft.api.aspects.Aspect; +import thaumcraft.api.aspects.AspectList; + +public class CrucibleRecipe { + + private ItemStack recipeOutput; + + public Object catalyst; + public AspectList aspects; + public String key; + + public int hash; + + public CrucibleRecipe(String researchKey, ItemStack result, Object cat, AspectList tags) { + recipeOutput = result; + this.aspects = tags; + this.key = researchKey; + this.catalyst = cat; + if (cat instanceof String) { + this.catalyst = OreDictionary.getOres((String) cat); + } + String hc = researchKey + result.toString(); + for (Aspect tag:tags.getAspects()) { + hc += tag.getTag()+tags.getAmount(tag); + } + if (cat instanceof ItemStack) { + hc += ((ItemStack)cat).toString(); + } else + if (cat instanceof ArrayList && ((ArrayList)catalyst).size()>0) { + for (ItemStack is :(ArrayList)catalyst) { + hc += is.toString(); + } + } + + hash = hc.hashCode(); + } + + + + public boolean matches(AspectList itags, ItemStack cat) { + if (catalyst instanceof ItemStack && + !ThaumcraftApiHelper.itemMatches((ItemStack) catalyst,cat,false)) { + return false; + } else + if (catalyst instanceof ArrayList && ((ArrayList)catalyst).size()>0) { + ItemStack[] ores = ((ArrayList)catalyst).toArray(new ItemStack[]{}); + if (!ThaumcraftApiHelper.containsMatch(false, new ItemStack[]{cat},ores)) return false; + } + if (itags==null) return false; + for (Aspect tag:aspects.getAspects()) { + if (itags.getAmount(tag))catalyst).size()>0) { + ItemStack[] ores = ((ArrayList)catalyst).toArray(new ItemStack[]{}); + if (ThaumcraftApiHelper.containsMatch(false, new ItemStack[]{cat},ores)) return true; + } + return false; + } + + public AspectList removeMatching(AspectList itags) { + AspectList temptags = new AspectList(); + temptags.aspects.putAll(itags.aspects); + + for (Aspect tag:aspects.getAspects()) { + temptags.remove(tag, aspects.getAmount(tag)); + } + + itags = temptags; + return itags; + } + + public ItemStack getRecipeOutput() { + return recipeOutput; + } + + +} diff --git a/src/main/java/thaumcraft/api/crafting/IArcaneRecipe.java b/src/main/java/thaumcraft/api/crafting/IArcaneRecipe.java new file mode 100644 index 00000000..bb5036d2 --- /dev/null +++ b/src/main/java/thaumcraft/api/crafting/IArcaneRecipe.java @@ -0,0 +1,35 @@ +package thaumcraft.api.crafting; + +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.inventory.IInventory; +import net.minecraft.item.ItemStack; +import net.minecraft.world.World; +import thaumcraft.api.aspects.AspectList; + +public interface IArcaneRecipe +{ + + + /** + * Used to check if a recipe matches current crafting inventory + * @param player + */ + boolean matches(IInventory var1, World world, EntityPlayer player); + + /** + * Returns an Item that is the result of this recipe + */ + ItemStack getCraftingResult(IInventory var1); + + /** + * Returns the size of the recipe area + */ + int getRecipeSize(); + + ItemStack getRecipeOutput(); + AspectList getAspects(); + AspectList getAspects(IInventory var1); + String getResearch(); + + +} diff --git a/src/main/java/thaumcraft/api/crafting/IInfusionStabiliser.java b/src/main/java/thaumcraft/api/crafting/IInfusionStabiliser.java new file mode 100644 index 00000000..d137ff24 --- /dev/null +++ b/src/main/java/thaumcraft/api/crafting/IInfusionStabiliser.java @@ -0,0 +1,19 @@ +package thaumcraft.api.crafting; + +import net.minecraft.world.World; + +/** + * + * @author Azanor + * + * Blocks that implement this interface act as infusion crafting stabilisers like candles and skulls + * + */ +public interface IInfusionStabiliser { + + /** + * returns true if the block can stabilise things + */ + public boolean canStabaliseInfusion(World world, int x, int y, int z); + +} diff --git a/src/main/java/thaumcraft/api/crafting/InfusionEnchantmentRecipe.java b/src/main/java/thaumcraft/api/crafting/InfusionEnchantmentRecipe.java new file mode 100644 index 00000000..bdc5f505 --- /dev/null +++ b/src/main/java/thaumcraft/api/crafting/InfusionEnchantmentRecipe.java @@ -0,0 +1,154 @@ +package thaumcraft.api.crafting; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.Map; + +import net.minecraft.enchantment.Enchantment; +import net.minecraft.enchantment.EnchantmentHelper; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.ItemStack; +import net.minecraft.world.World; +import net.minecraftforge.oredict.OreDictionary; +import thaumcraft.api.ThaumcraftApiHelper; +import thaumcraft.api.aspects.AspectList; + +public class InfusionEnchantmentRecipe +{ + + public AspectList aspects; + public String research; + public ItemStack[] components; + public Enchantment enchantment; + public int recipeXP; + public int instability; + + public InfusionEnchantmentRecipe(String research, Enchantment input, int inst, + AspectList aspects2, ItemStack[] recipe) { + this.research = research; + this.enchantment = input; + this.aspects = aspects2; + this.components = recipe; + this.instability = inst; + this.recipeXP = Math.max(1, input.getMinEnchantability(1)/3); + } + + /** + * Used to check if a recipe matches current crafting inventory + * @param player + */ + public boolean matches(ArrayList input, ItemStack central, World world, EntityPlayer player) { + if (research.length()>0 && !ThaumcraftApiHelper.isResearchComplete(player.getCommandSenderName(), research)) { + return false; + } + + if (!enchantment.canApply(central) || !central.getItem().isItemTool(central)) { + return false; + } + + Map map1 = EnchantmentHelper.getEnchantments(central); + Iterator iterator = map1.keySet().iterator(); + while (iterator.hasNext()) + { + int j1 = ((Integer)iterator.next()).intValue(); + Enchantment ench = Enchantment.enchantmentsList[j1]; + if (j1 == enchantment.effectId && + EnchantmentHelper.getEnchantmentLevel(j1, central)>=ench.getMaxLevel()) + return false; + if (enchantment.effectId != ench.effectId && + (!enchantment.canApplyTogether(ench) || + !ench.canApplyTogether(enchantment))) { + return false; + } + } + + ItemStack i2 = null; + + ArrayList ii = new ArrayList(); + for (ItemStack is:input) { + ii.add(is.copy()); + } + + for (ItemStack comp:components) { + boolean b=false; + for (int a=0;a input, ItemStack central, World world, EntityPlayer player) { + if (getRecipeInput()==null) return false; + + if (research.length()>0 && !ThaumcraftApiHelper.isResearchComplete(player.getCommandSenderName(), research)) { + return false; + } + + ItemStack i2 = central.copy(); + if (getRecipeInput().getItemDamage()==OreDictionary.WILDCARD_VALUE) { + i2.setItemDamage(OreDictionary.WILDCARD_VALUE); + } + + if (!areItemStacksEqual(i2, getRecipeInput(), true)) return false; + + ArrayList ii = new ArrayList(); + for (ItemStack is:input) { + ii.add(is.copy()); + } + + for (ItemStack comp:getComponents()) { + boolean b=false; + for (int a=0;a itemMap = new HashMap(); + + for (; idx < recipe.length; idx += 2) + { + Character chr = (Character)recipe[idx]; + Object in = recipe[idx + 1]; + + if (in instanceof ItemStack) + { + itemMap.put(chr, ((ItemStack)in).copy()); + } + else if (in instanceof Item) + { + itemMap.put(chr, new ItemStack((Item)in)); + } + else if (in instanceof Block) + { + itemMap.put(chr, new ItemStack((Block)in, 1, OreDictionary.WILDCARD_VALUE)); + } + else if (in instanceof String) + { + itemMap.put(chr, OreDictionary.getOres((String)in)); + } + else + { + String ret = "Invalid shaped ore recipe: "; + for (Object tmp : recipe) + { + ret += tmp + ", "; + } + ret += output; + throw new RuntimeException(ret); + } + } + + input = new Object[width * height]; + int x = 0; + for (char chr : shape.toCharArray()) + { + input[x++] = itemMap.get(chr); + } + } + + @Override + public ItemStack getCraftingResult(IInventory var1){ return output.copy(); } + + @Override + public int getRecipeSize(){ return input.length; } + + @Override + public ItemStack getRecipeOutput(){ return output; } + + @Override + public boolean matches(IInventory inv, World world, EntityPlayer player) + { + if (research.length()>0 && !ThaumcraftApiHelper.isResearchComplete(player.getCommandSenderName(), research)) { + return false; + } + for (int x = 0; x <= MAX_CRAFT_GRID_WIDTH - width; x++) + { + for (int y = 0; y <= MAX_CRAFT_GRID_HEIGHT - height; ++y) + { + if (checkMatch(inv, x, y, false)) + { + return true; + } + + if (mirrored && checkMatch(inv, x, y, true)) + { + return true; + } + } + } + + return false; + } + + private boolean checkMatch(IInventory inv, int startX, int startY, boolean mirror) + { + for (int x = 0; x < MAX_CRAFT_GRID_WIDTH; x++) + { + for (int y = 0; y < MAX_CRAFT_GRID_HEIGHT; y++) + { + int subX = x - startX; + int subY = y - startY; + Object target = null; + + if (subX >= 0 && subY >= 0 && subX < width && subY < height) + { + if (mirror) + { + target = input[width - subX - 1 + subY * width]; + } + else + { + target = input[subX + subY * width]; + } + } + + ItemStack slot = ThaumcraftApiHelper.getStackInRowAndColumn(inv, x, y); + + if (target instanceof ItemStack) + { + if (!checkItemEquals((ItemStack)target, slot)) + { + return false; + } + } + else if (target instanceof ArrayList) + { + boolean matched = false; + + for (ItemStack item : (ArrayList)target) + { + matched = matched || checkItemEquals(item, slot); + } + + if (!matched) + { + return false; + } + } + else if (target == null && slot != null) + { + return false; + } + } + } + + return true; + } + + private boolean checkItemEquals(ItemStack target, ItemStack input) + { + if (input == null && target != null || input != null && target == null) + { + return false; + } + return (target.getItem() == input.getItem() && + (!target.hasTagCompound() || ThaumcraftApiHelper.areItemStackTagsEqualForCrafting(input,target)) && + (target.getItemDamage() == OreDictionary.WILDCARD_VALUE|| target.getItemDamage() == input.getItemDamage())); + } + + public ShapedArcaneRecipe setMirrored(boolean mirror) + { + mirrored = mirror; + return this; + } + + /** + * Returns the input for this recipe, any mod accessing this value should never + * manipulate the values in this array as it will effect the recipe itself. + * @return The recipes input vales. + */ + public Object[] getInput() + { + return this.input; + } + + @Override + public AspectList getAspects() { + return aspects; + } + + @Override + public AspectList getAspects(IInventory inv) { + return aspects; + } + + @Override + public String getResearch() { + return research; + } +} diff --git a/src/main/java/thaumcraft/api/crafting/ShapelessArcaneRecipe.java b/src/main/java/thaumcraft/api/crafting/ShapelessArcaneRecipe.java new file mode 100644 index 00000000..3b1eaeb8 --- /dev/null +++ b/src/main/java/thaumcraft/api/crafting/ShapelessArcaneRecipe.java @@ -0,0 +1,161 @@ +package thaumcraft.api.crafting; + +import java.util.ArrayList; +import java.util.Iterator; + +import net.minecraft.block.Block; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.inventory.IInventory; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.world.World; +import net.minecraftforge.oredict.OreDictionary; +import thaumcraft.api.ThaumcraftApiHelper; +import thaumcraft.api.aspects.AspectList; + +public class ShapelessArcaneRecipe implements IArcaneRecipe +{ + private ItemStack output = null; + private ArrayList input = new ArrayList(); + + public AspectList aspects = null; + public String research; + + public ShapelessArcaneRecipe(String research, Block result, AspectList aspects, Object... recipe){ this(research,new ItemStack(result),aspects, recipe); } + public ShapelessArcaneRecipe(String research, Item result, AspectList aspects, Object... recipe){ this(research,new ItemStack(result),aspects, recipe); } + + public ShapelessArcaneRecipe(String research, ItemStack result, AspectList aspects, Object... recipe) + { + output = result.copy(); + this.research = research; + this.aspects = aspects; + for (Object in : recipe) + { + if (in instanceof ItemStack) + { + input.add(((ItemStack)in).copy()); + } + else if (in instanceof Item) + { + input.add(new ItemStack((Item)in)); + } + else if (in instanceof Block) + { + input.add(new ItemStack((Block)in)); + } + else if (in instanceof String) + { + input.add(OreDictionary.getOres((String)in)); + } + else + { + String ret = "Invalid shapeless ore recipe: "; + for (Object tmp : recipe) + { + ret += tmp + ", "; + } + ret += output; + throw new RuntimeException(ret); + } + } + } + + @Override + public int getRecipeSize(){ return input.size(); } + + @Override + public ItemStack getRecipeOutput(){ return output; } + + @Override + public ItemStack getCraftingResult(IInventory var1){ return output.copy(); } + + @Override + public boolean matches(IInventory var1, World world, EntityPlayer player) + { + if (research.length()>0 && !ThaumcraftApiHelper.isResearchComplete(player.getCommandSenderName(), research)) { + return false; + } + + ArrayList required = new ArrayList(input); + + for (int x = 0; x < 9; x++) + { + ItemStack slot = var1.getStackInSlot(x); + + if (slot != null) + { + boolean inRecipe = false; + Iterator req = required.iterator(); + + while (req.hasNext()) + { + boolean match = false; + + Object next = req.next(); + + if (next instanceof ItemStack) + { + match = checkItemEquals((ItemStack)next, slot); + } + else if (next instanceof ArrayList) + { + for (ItemStack item : (ArrayList)next) + { + match = match || checkItemEquals(item, slot); + } + } + + if (match) + { + inRecipe = true; + required.remove(next); + break; + } + } + + if (!inRecipe) + { + return false; + } + } + } + + return required.isEmpty(); + } + + private boolean checkItemEquals(ItemStack target, ItemStack input) + { + if (input == null && target != null || input != null && target == null) + { + return false; + } + return (target.getItem() == input.getItem() && + (!target.hasTagCompound() || ThaumcraftApiHelper.areItemStackTagsEqualForCrafting(input,target)) && + (target.getItemDamage() == OreDictionary.WILDCARD_VALUE|| target.getItemDamage() == input.getItemDamage())); + } + + /** + * Returns the input for this recipe, any mod accessing this value should never + * manipulate the values in this array as it will effect the recipe itself. + * @return The recipes input vales. + */ + public ArrayList getInput() + { + return this.input; + } + + @Override + public AspectList getAspects() { + return aspects; + } + + @Override + public AspectList getAspects(IInventory inv) { + return aspects; + } + + @Override + public String getResearch() { + return research; + } +} diff --git a/src/main/java/thaumcraft/api/internal/DummyInternalMethodHandler.java b/src/main/java/thaumcraft/api/internal/DummyInternalMethodHandler.java new file mode 100644 index 00000000..9af8c30e --- /dev/null +++ b/src/main/java/thaumcraft/api/internal/DummyInternalMethodHandler.java @@ -0,0 +1,78 @@ +package thaumcraft.api.internal; + +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import thaumcraft.api.aspects.Aspect; +import thaumcraft.api.aspects.AspectList; + +public class DummyInternalMethodHandler implements IInternalMethodHandler { + + @Override + public void generateVisEffect(int dim, int x, int y, int z, int x2, int y2, int z2, int color) { + + } + + @Override + public boolean isResearchComplete(String username, String researchkey) { + return false; + } + + @Override + public boolean hasDiscoveredAspect(String username, Aspect aspect) { + return false; + } + + @Override + public AspectList getDiscoveredAspects(String username) { + return null; + } + + @Override + public ItemStack getStackInRowAndColumn(Object instance, int row, int column) { + return null; + } + + @Override + public AspectList getObjectAspects(ItemStack is) { + return null; + } + + @Override + public AspectList getBonusObjectTags(ItemStack is, AspectList ot) { + return null; + } + + @Override + public AspectList generateTags(Item item, int meta) { + return null; + } + + @Override + public boolean consumeVisFromWand(ItemStack wand, EntityPlayer player, + AspectList cost, boolean doit, boolean crafting) { + return false; + } + + @Override + public boolean consumeVisFromWandCrafting(ItemStack wand, + EntityPlayer player, AspectList cost, boolean doit) { + return false; + } + + @Override + public boolean consumeVisFromInventory(EntityPlayer player, AspectList cost) { + return false; + } + + @Override + public void addWarpToPlayer(EntityPlayer player, int amount, boolean temporary) { + } + + @Override + public void addStickyWarpToPlayer(EntityPlayer player, int amount) { + } + + + +} diff --git a/src/main/java/thaumcraft/api/internal/IInternalMethodHandler.java b/src/main/java/thaumcraft/api/internal/IInternalMethodHandler.java new file mode 100644 index 00000000..73fce669 --- /dev/null +++ b/src/main/java/thaumcraft/api/internal/IInternalMethodHandler.java @@ -0,0 +1,25 @@ +package thaumcraft.api.internal; + +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import thaumcraft.api.aspects.Aspect; +import thaumcraft.api.aspects.AspectList; + +public interface IInternalMethodHandler { + + public void generateVisEffect(int dim, int x, int y, int z, int x2, int y2, int z2, int color); + public boolean isResearchComplete(String username, String researchkey); + public ItemStack getStackInRowAndColumn(Object instance, int row, int column); + public AspectList getObjectAspects(ItemStack is); + public AspectList getBonusObjectTags(ItemStack is,AspectList ot); + public AspectList generateTags(Item item, int meta); + public boolean consumeVisFromWand(ItemStack wand, EntityPlayer player, AspectList cost, boolean doit, boolean crafting); + public boolean consumeVisFromWandCrafting(ItemStack wand,EntityPlayer player, AspectList cost, boolean doit); + public boolean consumeVisFromInventory(EntityPlayer player, AspectList cost); + public void addWarpToPlayer(EntityPlayer player, int amount,boolean temporary); + public void addStickyWarpToPlayer(EntityPlayer player, int amount); + public boolean hasDiscoveredAspect(String username, Aspect aspect); + public AspectList getDiscoveredAspects(String username); + +} diff --git a/src/main/java/thaumcraft/api/internal/WeightedRandomLoot.java b/src/main/java/thaumcraft/api/internal/WeightedRandomLoot.java new file mode 100644 index 00000000..4ac6ee7d --- /dev/null +++ b/src/main/java/thaumcraft/api/internal/WeightedRandomLoot.java @@ -0,0 +1,24 @@ +package thaumcraft.api.internal; + +import java.util.ArrayList; + +import net.minecraft.item.ItemStack; +import net.minecraft.util.WeightedRandom; + + +public class WeightedRandomLoot extends WeightedRandom.Item { + + /** The Item/Block ID to generate in the bag. */ + public ItemStack item; + + public WeightedRandomLoot(ItemStack stack, int weight) + { + super(weight); + this.item = stack; + } + + public static ArrayList lootBagCommon = new ArrayList(); + public static ArrayList lootBagUncommon = new ArrayList(); + public static ArrayList lootBagRare = new ArrayList(); + +} diff --git a/src/main/java/thaumcraft/api/package-info.java b/src/main/java/thaumcraft/api/package-info.java new file mode 100644 index 00000000..cceaf891 --- /dev/null +++ b/src/main/java/thaumcraft/api/package-info.java @@ -0,0 +1,4 @@ +@API(owner = "Thaumcraft", apiVersion = "4.2.2.0", provides = "Thaumcraft|API") +package thaumcraft.api; + +import cpw.mods.fml.common.API; diff --git a/src/main/java/thaumcraft/api/research/IScanEventHandler.java b/src/main/java/thaumcraft/api/research/IScanEventHandler.java new file mode 100644 index 00000000..d0efac5f --- /dev/null +++ b/src/main/java/thaumcraft/api/research/IScanEventHandler.java @@ -0,0 +1,9 @@ +package thaumcraft.api.research; + +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.ItemStack; +import net.minecraft.world.World; + +public interface IScanEventHandler { + ScanResult scanPhenomena(ItemStack stack, World world, EntityPlayer player); +} diff --git a/src/main/java/thaumcraft/api/research/ResearchCategories.java b/src/main/java/thaumcraft/api/research/ResearchCategories.java new file mode 100644 index 00000000..82309b36 --- /dev/null +++ b/src/main/java/thaumcraft/api/research/ResearchCategories.java @@ -0,0 +1,101 @@ +package thaumcraft.api.research; + +import java.util.Collection; +import java.util.LinkedHashMap; + +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.StatCollector; + +import org.apache.logging.log4j.Level; + +import cpw.mods.fml.common.FMLLog; + +public class ResearchCategories { + + //Research + public static LinkedHashMap researchCategories = new LinkedHashMap (); + + /** + * @param key + * @return the research item linked to this key + */ + public static ResearchCategoryList getResearchList(String key) { + return researchCategories.get(key); + } + + /** + * @param key + * @return the name of the research category linked to this key. + * Must be stored as localization information in the LanguageRegistry. + */ + public static String getCategoryName(String key) { + return StatCollector.translateToLocal("tc.research_category."+key); + } + + /** + * @param key the research key + * @return the ResearchItem object. + */ + public static ResearchItem getResearch(String key) { + Collection rc = researchCategories.values(); + for (Object cat:rc) { + Collection rl = ((ResearchCategoryList)cat).research.values(); + for (Object ri:rl) { + if ((((ResearchItem)ri).key).equals(key)) return (ResearchItem)ri; + } + } + return null; + } + + /** + * This should only be done at the PostInit stage + * @param key the key used for this category + * @param icon the icon to be used for the research category tab + * @param background the resource location of the background image to use for this category + * @return the name of the research linked to this key + */ + public static void registerCategory(String key, ResourceLocation icon, ResourceLocation background) { + if (getResearchList(key)==null) { + ResearchCategoryList rl = new ResearchCategoryList(icon, background); + researchCategories.put(key, rl); + } + } + + public static void addResearch(ResearchItem ri) { + ResearchCategoryList rl = getResearchList(ri.category); + if (rl!=null && !rl.research.containsKey(ri.key)) { + + if (!ri.isVirtual()) { + for (ResearchItem rr:rl.research.values()) { + if (rr.displayColumn == ri.displayColumn && rr.displayRow == ri.displayRow) { + FMLLog.log(Level.FATAL, "[Thaumcraft] Research ["+ri.getName()+"] not added as it overlaps with existing research ["+rr.getName()+"]"); + return; + } + } + } + + + rl.research.put(ri.key, ri); + + if (ri.displayColumn < rl.minDisplayColumn) + { + rl.minDisplayColumn = ri.displayColumn; + } + + if (ri.displayRow < rl.minDisplayRow) + { + rl.minDisplayRow = ri.displayRow; + } + + if (ri.displayColumn > rl.maxDisplayColumn) + { + rl.maxDisplayColumn = ri.displayColumn; + } + + if (ri.displayRow > rl.maxDisplayRow) + { + rl.maxDisplayRow = ri.displayRow; + } + } + } +} diff --git a/src/main/java/thaumcraft/api/research/ResearchCategoryList.java b/src/main/java/thaumcraft/api/research/ResearchCategoryList.java new file mode 100644 index 00000000..7eed0101 --- /dev/null +++ b/src/main/java/thaumcraft/api/research/ResearchCategoryList.java @@ -0,0 +1,37 @@ +package thaumcraft.api.research; + +import java.util.HashMap; +import java.util.Map; + +import net.minecraft.util.ResourceLocation; + +public class ResearchCategoryList { + + /** Is the smallest column used on the GUI. */ + public int minDisplayColumn; + + /** Is the smallest row used on the GUI. */ + public int minDisplayRow; + + /** Is the biggest column used on the GUI. */ + public int maxDisplayColumn; + + /** Is the biggest row used on the GUI. */ + public int maxDisplayRow; + + /** display variables **/ + public ResourceLocation icon; + public ResourceLocation background; + + public ResearchCategoryList(ResourceLocation icon, ResourceLocation background) { + this.icon = icon; + this.background = background; + } + + //Research + public Map research = new HashMap(); + + + + +} diff --git a/src/main/java/thaumcraft/api/research/ResearchItem.java b/src/main/java/thaumcraft/api/research/ResearchItem.java new file mode 100644 index 00000000..1d5fd8bf --- /dev/null +++ b/src/main/java/thaumcraft/api/research/ResearchItem.java @@ -0,0 +1,369 @@ +package thaumcraft.api.research; + +import net.minecraft.item.ItemStack; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.StatCollector; +import thaumcraft.api.aspects.Aspect; +import thaumcraft.api.aspects.AspectList; + +public class ResearchItem +{ + /** + * A short string used as a key for this research. Must be unique + */ + public final String key; + + /** + * A short string used as a reference to the research category to which this must be added. + */ + public final String category; + + /** + * The aspect tags and their values required to complete this research + */ + public final AspectList tags; + + /** + * This links to any research that needs to be completed before this research can be discovered or learnt. + */ + public String[] parents = null; + + /** + * Like parent above, but a line will not be displayed in the thaumonomicon linking them. Just used to prevent clutter. + */ + public String[] parentsHidden = null; + /** + * any research linked to this that will be unlocked automatically when this research is complete + */ + public String[] siblings = null; + + /** + * the horizontal position of the research icon + */ + public final int displayColumn; + + /** + * the vertical position of the research icon + */ + public final int displayRow; + + /** + * the icon to be used for this research + */ + public final ItemStack icon_item; + + /** + * the icon to be used for this research + */ + public final ResourceLocation icon_resource; + + /** + * How large the research grid is. Valid values are 1 to 3. + */ + private int complexity; + + /** + * Special research has a spiky border. Used for important research milestones. + */ + private boolean isSpecial; + + /** + * Research that can be directly purchased with RP in normal research difficulty. + */ + private boolean isSecondary; + + /** + * This indicates if the research should use a circular icon border. Usually used for "passive" research + * that doesn't have recipes and grants passive effects, or that unlock automatically. + */ + private boolean isRound; + + /** + * Stub research cannot be discovered by normal means, but can be unlocked via the sibling system. + */ + private boolean isStub; + + /** + * This indicated that the research is completely hidden and cannot be discovered by any + * player-controlled means. The recipes will never show up in the thaumonomicon. + * Usually used to unlock "hidden" recipes via sibling unlocking, like + * the various cap and rod combos for wands. + */ + private boolean isVirtual; + + + + /** + * Concealed research does not display in the thaumonomicon until parent researches are discovered. + */ + private boolean isConcealed; + + /** + * Hidden research can only be discovered via scanning or knowledge fragments + */ + private boolean isHidden; + + /** + * This is the same as isHidden, except it cannot be discovered with knowledge fragments, only scanning. + */ + private boolean isLost; + + /** + * These research items will automatically unlock for all players on game start + */ + private boolean isAutoUnlock; + + /** + * Scanning these items will have a chance of revealing hidden knowledge in the thaumonomicon + */ + private ItemStack[] itemTriggers; + + /** + * Scanning these entities will have a chance of revealing hidden knowledge in the thaumonomicon + */ + private String[] entityTriggers; + + /** + * Scanning things with these aspects will have a chance of revealing hidden knowledge in the thaumonomicon + */ + private Aspect[] aspectTriggers; + + private ResearchPage[] pages = null; + + public ResearchItem(String key, String category) + { + this.key = key; + this.category = category; + this.tags = new AspectList(); + this.icon_resource = null; + this.icon_item = null; + this.displayColumn = 0; + this.displayRow = 0; + this.setVirtual(); + + } + + public ResearchItem(String key, String category, AspectList tags, int col, int row, int complex, ResourceLocation icon) + { + this.key = key; + this.category = category; + this.tags = tags; + this.icon_resource = icon; + this.icon_item = null; + this.displayColumn = col; + this.displayRow = row; + this.complexity = complex; + if (complexity < 1) this.complexity = 1; + if (complexity > 3) this.complexity = 3; + } + + public ResearchItem(String key, String category, AspectList tags, int col, int row, int complex, ItemStack icon) + { + this.key = key; + this.category = category; + this.tags = tags; + this.icon_item = icon; + this.icon_resource = null; + this.displayColumn = col; + this.displayRow = row; + this.complexity = complex; + if (complexity < 1) this.complexity = 1; + if (complexity > 3) this.complexity = 3; + } + + public ResearchItem setSpecial() + { + this.isSpecial = true; + return this; + } + + public ResearchItem setStub() + { + this.isStub = true; + return this; + } + + public ResearchItem setLost() + { + this.isLost = true; + return this; + } + + public ResearchItem setConcealed() + { + this.isConcealed = true; + return this; + } + + public ResearchItem setHidden() + { + this.isHidden = true; + return this; + } + + public ResearchItem setVirtual() + { + this.isVirtual = true; + return this; + } + + public ResearchItem setParents(String... par) + { + this.parents = par; + return this; + } + + + + public ResearchItem setParentsHidden(String... par) + { + this.parentsHidden = par; + return this; + } + + public ResearchItem setSiblings(String... sib) + { + this.siblings = sib; + return this; + } + + public ResearchItem setPages(ResearchPage... par) + { + this.pages = par; + return this; + } + + public ResearchPage[] getPages() { + return pages; + } + + public ResearchItem setItemTriggers(ItemStack... par) + { + this.itemTriggers = par; + return this; + } + + public ResearchItem setEntityTriggers(String... par) + { + this.entityTriggers = par; + return this; + } + + public ResearchItem setAspectTriggers(Aspect... par) + { + this.aspectTriggers = par; + return this; + } + + public ItemStack[] getItemTriggers() { + return itemTriggers; + } + + public String[] getEntityTriggers() { + return entityTriggers; + } + + public Aspect[] getAspectTriggers() { + return aspectTriggers; + } + + public ResearchItem registerResearchItem() + { + ResearchCategories.addResearch(this); + return this; + } + + public String getName() + { + return StatCollector.translateToLocal("tc.research_name."+key); + } + + public String getText() + { + return StatCollector.translateToLocal("tc.research_text."+key); + } + + public boolean isSpecial() + { + return this.isSpecial; + } + + public boolean isStub() + { + return this.isStub; + } + + public boolean isLost() + { + return this.isLost; + } + + public boolean isConcealed() + { + return this.isConcealed; + } + + public boolean isHidden() + { + return this.isHidden; + } + + public boolean isVirtual() + { + return this.isVirtual; + } + + public boolean isAutoUnlock() { + return isAutoUnlock; + } + + public ResearchItem setAutoUnlock() + { + this.isAutoUnlock = true; + return this; + } + + public boolean isRound() { + return isRound; + } + + public ResearchItem setRound() { + this.isRound = true; + return this; + } + + public boolean isSecondary() { + return isSecondary; + } + + public ResearchItem setSecondary() { + this.isSecondary = true; + return this; + } + + public int getComplexity() { + return complexity; + } + + public ResearchItem setComplexity(int complexity) { + this.complexity = complexity; + return this; + } + + /** + * @return the aspect aspects ordinal with the highest value. Used to determine scroll color and similar things + */ + public Aspect getResearchPrimaryTag() { + Aspect aspect=null; + int highest=0; + if (tags!=null) + for (Aspect tag:tags.getAspects()) { + if (tags.getAmount(tag)>highest) { + aspect=tag; + highest=tags.getAmount(tag); + }; + } + return aspect; + } + +} diff --git a/src/main/java/thaumcraft/api/research/ResearchPage.java b/src/main/java/thaumcraft/api/research/ResearchPage.java new file mode 100644 index 00000000..fdada843 --- /dev/null +++ b/src/main/java/thaumcraft/api/research/ResearchPage.java @@ -0,0 +1,193 @@ +package thaumcraft.api.research; + +import java.util.List; + +import net.minecraft.item.ItemStack; +import net.minecraft.item.crafting.FurnaceRecipes; +import net.minecraft.item.crafting.IRecipe; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.StatCollector; +import thaumcraft.api.aspects.AspectList; +import thaumcraft.api.crafting.CrucibleRecipe; +import thaumcraft.api.crafting.IArcaneRecipe; +import thaumcraft.api.crafting.InfusionEnchantmentRecipe; +import thaumcraft.api.crafting.InfusionRecipe; + +public class ResearchPage { + public static enum PageType + { + TEXT, + TEXT_CONCEALED, + IMAGE, + CRUCIBLE_CRAFTING, + ARCANE_CRAFTING, + ASPECTS, + NORMAL_CRAFTING, + INFUSION_CRAFTING, + COMPOUND_CRAFTING, + INFUSION_ENCHANTMENT, + SMELTING + } + + public PageType type = PageType.TEXT; + + public String text=null; + public String research=null; + public ResourceLocation image=null; + public AspectList aspects=null; + public Object recipe=null; + public ItemStack recipeOutput=null; + + /** + * @param text this can (but does not have to) be a reference to a localization variable, not the actual text. + */ + public ResearchPage(String text) { + this.type = PageType.TEXT; + this.text = text; + } + + /** + * @param research this page will only be displayed if the player has discovered this research + * @param text this can (but does not have to) be a reference to a localization variable, not the actual text. + */ + public ResearchPage(String research, String text) { + this.type = PageType.TEXT_CONCEALED; + this.research = research; + this.text = text; + } + + /** + * @param recipe a vanilla crafting recipe. + */ + public ResearchPage(IRecipe recipe) { + this.type = PageType.NORMAL_CRAFTING; + this.recipe = recipe; + this.recipeOutput = recipe.getRecipeOutput(); + } + + /** + * @param recipe a collection of vanilla crafting recipes. + */ + public ResearchPage(IRecipe[] recipe) { + this.type = PageType.NORMAL_CRAFTING; + this.recipe = recipe; + } + + /** + * @param recipe a collection of arcane crafting recipes. + */ + public ResearchPage(IArcaneRecipe[] recipe) { + this.type = PageType.ARCANE_CRAFTING; + this.recipe = recipe; + } + + /** + * @param recipe a collection of arcane crafting recipes. + */ + public ResearchPage(CrucibleRecipe[] recipe) { + this.type = PageType.CRUCIBLE_CRAFTING; + this.recipe = recipe; + } + + /** + * @param recipe a collection of infusion crafting recipes. + */ + public ResearchPage(InfusionRecipe[] recipe) { + this.type = PageType.INFUSION_CRAFTING; + this.recipe = recipe; + } + + /** + * @param recipe a compound crafting recipe. + */ + public ResearchPage(List recipe) { + this.type = PageType.COMPOUND_CRAFTING; + this.recipe = recipe; + } + + /** + * @param recipe an arcane worktable crafting recipe. + */ + public ResearchPage(IArcaneRecipe recipe) { + this.type = PageType.ARCANE_CRAFTING; + this.recipe = recipe; + this.recipeOutput = recipe.getRecipeOutput(); + } + + /** + * @param recipe an alchemy crafting recipe. + */ + public ResearchPage(CrucibleRecipe recipe) { + this.type = PageType.CRUCIBLE_CRAFTING; + this.recipe = recipe; + this.recipeOutput = recipe.getRecipeOutput(); + } + + /** + * @param recipe a furnace smelting crafting recipe. + */ + public ResearchPage(ItemStack input) { + this.type = PageType.SMELTING; + this.recipe = input; + this.recipeOutput = FurnaceRecipes.smelting().getSmeltingResult(input); + } + + /** + * @param recipe an infusion crafting recipe. + */ + public ResearchPage(InfusionRecipe recipe) { + this.type = PageType.INFUSION_CRAFTING; + this.recipe = recipe; + if (recipe.getRecipeOutput() instanceof ItemStack) { + this.recipeOutput = (ItemStack) recipe.getRecipeOutput(); + } else { + this.recipeOutput = recipe.getRecipeInput(); + } + } + + /** + * @param recipe an infusion crafting recipe. + */ + public ResearchPage(InfusionEnchantmentRecipe recipe) { + this.type = PageType.INFUSION_ENCHANTMENT; + this.recipe = recipe; +// if (recipe.recipeOutput instanceof ItemStack) { +// this.recipeOutput = (ItemStack) recipe.recipeOutput; +// } else { +// this.recipeOutput = recipe.recipeInput; +// } + } + + /** + * @param image + * @param caption this can (but does not have to) be a reference to a localization variable, not the actual text. + */ + public ResearchPage(ResourceLocation image, String caption) { + this.type = PageType.IMAGE; + this.image = image; + this.text = caption; + } + + /** + * This function should really not be called directly - used internally + */ + public ResearchPage(AspectList as) { + this.type = PageType.ASPECTS; + this.aspects = as; + } + + /** + * returns a localized text of the text field (if one exists). Returns the text field itself otherwise. + * @return + */ + public String getTranslatedText() { + String ret=""; + if (text != null) { + ret = StatCollector.translateToLocal(text); + if (ret.isEmpty()) ret = text; + } + return ret; + } + + +} diff --git a/src/main/java/thaumcraft/api/research/ScanResult.java b/src/main/java/thaumcraft/api/research/ScanResult.java new file mode 100644 index 00000000..e1498f31 --- /dev/null +++ b/src/main/java/thaumcraft/api/research/ScanResult.java @@ -0,0 +1,39 @@ +package thaumcraft.api.research; + +import net.minecraft.entity.Entity; + +public class ScanResult { + public byte type = 0; //1=blocks,2=entities,3=phenomena + public int id; + public int meta; + public Entity entity; + public String phenomena; + + public ScanResult(byte type, int blockId, int blockMeta, Entity entity, + String phenomena) { + super(); + this.type = type; + this.id = blockId; + this.meta = blockMeta; + this.entity = entity; + this.phenomena = phenomena; + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof ScanResult) { + ScanResult sr = (ScanResult) obj; + if (type != sr.type) + return false; + if (type == 1 + && (id != sr.id || meta != sr.meta)) + return false; + if (type == 2 && entity.getEntityId() != sr.entity.getEntityId()) + return false; + if (type == 3 && !phenomena.equals(sr.phenomena)) + return false; + } + return true; + } + +}