summaryrefslogtreecommitdiff
path: root/src/api/java
diff options
context:
space:
mode:
authorLance5057 <Lance5057@gmail.com>2016-04-14 01:15:14 -0500
committerLance5057 <Lance5057@gmail.com>2016-04-14 01:15:14 -0500
commit03b859cf0b85c9ad5ce10d71164634091b0acb4b (patch)
tree4f7e67164f5a7e8b771c28e2d3d4062a30dbd564 /src/api/java
parent9d618ac01c99846faeda71bc4c4c5f5324aeef51 (diff)
Added Thaum api, new modifiers, hauberd -> hauberk, fixed finishing anvil bug
Diffstat (limited to 'src/api/java')
-rw-r--r--src/api/java/thaumcraft/api/BlockCoordinates.java108
-rw-r--r--src/api/java/thaumcraft/api/IArchitect.java27
-rw-r--r--src/api/java/thaumcraft/api/IGoggles.java22
-rw-r--r--src/api/java/thaumcraft/api/IRepairable.java13
-rw-r--r--src/api/java/thaumcraft/api/IRepairableExtended.java17
-rw-r--r--src/api/java/thaumcraft/api/IRunicArmor.java22
-rw-r--r--src/api/java/thaumcraft/api/IScribeTools.java14
-rw-r--r--src/api/java/thaumcraft/api/IVisDiscountGear.java20
-rw-r--r--src/api/java/thaumcraft/api/IWarpingGear.java22
-rw-r--r--src/api/java/thaumcraft/api/ItemApi.java70
-rw-r--r--src/api/java/thaumcraft/api/ItemRunic.java21
-rw-r--r--src/api/java/thaumcraft/api/ThaumcraftApi.java588
-rw-r--r--src/api/java/thaumcraft/api/ThaumcraftApiHelper.java467
-rw-r--r--src/api/java/thaumcraft/api/TileThaumcraft.java63
-rw-r--r--src/api/java/thaumcraft/api/WorldCoordinates.java117
-rw-r--r--src/api/java/thaumcraft/api/aspects/Aspect.java201
-rw-r--r--src/api/java/thaumcraft/api/aspects/AspectList.java292
-rw-r--r--src/api/java/thaumcraft/api/aspects/AspectSourceHelper.java58
-rw-r--r--src/api/java/thaumcraft/api/aspects/IAspectContainer.java80
-rw-r--r--src/api/java/thaumcraft/api/aspects/IAspectSource.java16
-rw-r--r--src/api/java/thaumcraft/api/aspects/IEssentiaContainerItem.java37
-rw-r--r--src/api/java/thaumcraft/api/aspects/IEssentiaTransport.java100
-rw-r--r--src/api/java/thaumcraft/api/crafting/CrucibleRecipe.java91
-rw-r--r--src/api/java/thaumcraft/api/crafting/IArcaneRecipe.java35
-rw-r--r--src/api/java/thaumcraft/api/crafting/IInfusionStabiliser.java19
-rw-r--r--src/api/java/thaumcraft/api/crafting/InfusionEnchantmentRecipe.java154
-rw-r--r--src/api/java/thaumcraft/api/crafting/InfusionRecipe.java133
-rw-r--r--src/api/java/thaumcraft/api/crafting/ShapedArcaneRecipe.java261
-rw-r--r--src/api/java/thaumcraft/api/crafting/ShapelessArcaneRecipe.java161
-rw-r--r--src/api/java/thaumcraft/api/damagesource/DamageSourceIndirectThaumcraftEntity.java32
-rw-r--r--src/api/java/thaumcraft/api/damagesource/DamageSourceThaumcraft.java47
-rw-r--r--src/api/java/thaumcraft/api/entities/IEldritchMob.java5
-rw-r--r--src/api/java/thaumcraft/api/entities/ITaintedMob.java5
-rw-r--r--src/api/java/thaumcraft/api/internal/DummyInternalMethodHandler.java78
-rw-r--r--src/api/java/thaumcraft/api/internal/IInternalMethodHandler.java25
-rw-r--r--src/api/java/thaumcraft/api/internal/WeightedRandomLoot.java24
-rw-r--r--src/api/java/thaumcraft/api/nodes/INode.java53
-rw-r--r--src/api/java/thaumcraft/api/nodes/IRevealer.java22
-rw-r--r--src/api/java/thaumcraft/api/nodes/NodeModifier.java6
-rw-r--r--src/api/java/thaumcraft/api/nodes/NodeType.java6
-rw-r--r--src/api/java/thaumcraft/api/package-info.java4
-rw-r--r--src/api/java/thaumcraft/api/potions/PotionFluxTaint.java67
-rw-r--r--src/api/java/thaumcraft/api/potions/PotionVisExhaust.java48
-rw-r--r--src/api/java/thaumcraft/api/research/IScanEventHandler.java9
-rw-r--r--src/api/java/thaumcraft/api/research/ResearchCategories.java101
-rw-r--r--src/api/java/thaumcraft/api/research/ResearchCategoryList.java37
-rw-r--r--src/api/java/thaumcraft/api/research/ResearchItem.java369
-rw-r--r--src/api/java/thaumcraft/api/research/ResearchPage.java193
-rw-r--r--src/api/java/thaumcraft/api/research/ScanResult.java39
-rw-r--r--src/api/java/thaumcraft/api/visnet/TileVisNode.java188
-rw-r--r--src/api/java/thaumcraft/api/visnet/VisNetHandler.java282
-rw-r--r--src/api/java/thaumcraft/api/wands/FocusUpgradeType.java114
-rw-r--r--src/api/java/thaumcraft/api/wands/IWandRodOnUpdate.java16
-rw-r--r--src/api/java/thaumcraft/api/wands/IWandTriggerManager.java15
-rw-r--r--src/api/java/thaumcraft/api/wands/IWandable.java25
-rw-r--r--src/api/java/thaumcraft/api/wands/ItemFocusBasic.java276
-rw-r--r--src/api/java/thaumcraft/api/wands/StaffRod.java48
-rw-r--r--src/api/java/thaumcraft/api/wands/WandCap.java129
-rw-r--r--src/api/java/thaumcraft/api/wands/WandRod.java158
-rw-r--r--src/api/java/thaumcraft/api/wands/WandTriggerRegistry.java126
60 files changed, 5776 insertions, 0 deletions
diff --git a/src/api/java/thaumcraft/api/BlockCoordinates.java b/src/api/java/thaumcraft/api/BlockCoordinates.java
new file mode 100644
index 0000000..27a28f8
--- /dev/null
+++ b/src/api/java/thaumcraft/api/BlockCoordinates.java
@@ -0,0 +1,108 @@
+package thaumcraft.api;
+
+import net.minecraft.nbt.NBTTagCompound;
+import net.minecraft.tileentity.TileEntity;
+
+public class BlockCoordinates implements Comparable
+{
+ public int x;
+
+ /** the y coordinate */
+ public int y;
+
+ /** the z coordinate */
+ public int z;
+
+ public BlockCoordinates() {}
+
+ public BlockCoordinates(int par1, int par2, int par3)
+ {
+ this.x = par1;
+ this.y = par2;
+ this.z = par3;
+ }
+
+ public BlockCoordinates(TileEntity tile)
+ {
+ this.x = tile.xCoord;
+ this.y = tile.yCoord;
+ this.z = tile.zCoord;
+ }
+
+ public BlockCoordinates(BlockCoordinates par1ChunkCoordinates)
+ {
+ this.x = par1ChunkCoordinates.x;
+ this.y = par1ChunkCoordinates.y;
+ this.z = par1ChunkCoordinates.z;
+ }
+
+ public boolean equals(Object par1Obj)
+ {
+ if (!(par1Obj instanceof BlockCoordinates))
+ {
+ return false;
+ }
+ else
+ {
+ BlockCoordinates coordinates = (BlockCoordinates)par1Obj;
+ return this.x == coordinates.x && this.y == coordinates.y && this.z == coordinates.z ;
+ }
+ }
+
+ public int hashCode()
+ {
+ return this.x + this.y << 8 + this.z << 16;
+ }
+
+ /**
+ * Compare the coordinate with another coordinate
+ */
+ public int compareWorldCoordinate(BlockCoordinates par1)
+ {
+ return this.y == par1.y ? (this.z == par1.z ? this.x - par1.x : this.z - par1.z) : this.y - par1.y;
+ }
+
+ public void set(int par1, int par2, int par3, int d)
+ {
+ this.x = par1;
+ this.y = par2;
+ this.z = par3;
+ }
+
+ /**
+ * Returns the squared distance between this coordinates and the coordinates given as argument.
+ */
+ public float getDistanceSquared(int par1, int par2, int par3)
+ {
+ float f = (float)(this.x - par1);
+ float f1 = (float)(this.y - par2);
+ float f2 = (float)(this.z - par3);
+ return f * f + f1 * f1 + f2 * f2;
+ }
+
+ /**
+ * Return the squared distance between this coordinates and the ChunkCoordinates given as argument.
+ */
+ public float getDistanceSquaredToWorldCoordinates(BlockCoordinates par1ChunkCoordinates)
+ {
+ return this.getDistanceSquared(par1ChunkCoordinates.x, par1ChunkCoordinates.y, par1ChunkCoordinates.z);
+ }
+
+ public int compareTo(Object par1Obj)
+ {
+ return this.compareWorldCoordinate((BlockCoordinates)par1Obj);
+ }
+
+ public void readNBT(NBTTagCompound nbt) {
+ this.x = nbt.getInteger("b_x");
+ this.y = nbt.getInteger("b_y");
+ this.z = nbt.getInteger("b_z");
+ }
+
+ public void writeNBT(NBTTagCompound nbt) {
+ nbt.setInteger("b_x",x);
+ nbt.setInteger("b_y",y);
+ nbt.setInteger("b_z",z);
+ }
+
+}
diff --git a/src/api/java/thaumcraft/api/IArchitect.java b/src/api/java/thaumcraft/api/IArchitect.java
new file mode 100644
index 0000000..c733af5
--- /dev/null
+++ b/src/api/java/thaumcraft/api/IArchitect.java
@@ -0,0 +1,27 @@
+package thaumcraft.api;
+
+import java.util.ArrayList;
+
+import net.minecraft.entity.player.EntityPlayer;
+import net.minecraft.item.ItemStack;
+import net.minecraft.world.World;
+
+public interface IArchitect {
+
+ /**
+ * Returns a list of blocks that should be highlighted in world.
+ */
+ public ArrayList<BlockCoordinates> getArchitectBlocks(ItemStack stack, World world,
+ int x, int y, int z, int side, EntityPlayer player);
+
+ /**
+ * which axis should be displayed.
+ */
+ public boolean showAxis(ItemStack stack, World world, EntityPlayer player, int side, EnumAxis axis);
+
+ public enum EnumAxis {
+ X, // east / west
+ Y, // up / down
+ Z; // north / south
+ }
+}
diff --git a/src/api/java/thaumcraft/api/IGoggles.java b/src/api/java/thaumcraft/api/IGoggles.java
new file mode 100644
index 0000000..2f53d81
--- /dev/null
+++ b/src/api/java/thaumcraft/api/IGoggles.java
@@ -0,0 +1,22 @@
+package thaumcraft.api;
+
+import net.minecraft.entity.EntityLivingBase;
+import net.minecraft.item.ItemStack;
+
+/**
+ *
+ * @author Azanor
+ *
+ * Equipped head slot items that extend this class will be able to perform most functions that
+ * goggles of revealing can apart from view nodes which is handled by IRevealer.
+ *
+ */
+
+public interface IGoggles {
+
+ /*
+ * If this method returns true things like block essentia contents will be shown.
+ */
+ public boolean showIngamePopups(ItemStack itemstack, EntityLivingBase player);
+
+}
diff --git a/src/api/java/thaumcraft/api/IRepairable.java b/src/api/java/thaumcraft/api/IRepairable.java
new file mode 100644
index 0000000..48c6dff
--- /dev/null
+++ b/src/api/java/thaumcraft/api/IRepairable.java
@@ -0,0 +1,13 @@
+package thaumcraft.api;
+
+
+
+/**
+ * @author Azanor
+ * Items, armor and tools with this interface can receive the Repair enchantment.
+ * Repairs 1 point of durability every 10 seconds (2 for repair II)
+ */
+public interface IRepairable {
+
+
+}
diff --git a/src/api/java/thaumcraft/api/IRepairableExtended.java b/src/api/java/thaumcraft/api/IRepairableExtended.java
new file mode 100644
index 0000000..3382712
--- /dev/null
+++ b/src/api/java/thaumcraft/api/IRepairableExtended.java
@@ -0,0 +1,17 @@
+package thaumcraft.api;
+
+import net.minecraft.entity.player.EntityPlayer;
+import net.minecraft.item.ItemStack;
+
+
+
+/**
+ * @author Azanor
+ * Items, armor and tools with this interface can receive the Repair enchantment.
+ * Repairs 1 point of durability every 10 seconds (2 for repair II)
+ */
+public interface IRepairableExtended extends IRepairable {
+
+ public boolean doRepair(ItemStack stack, EntityPlayer player, int enchantlevel);
+
+}
diff --git a/src/api/java/thaumcraft/api/IRunicArmor.java b/src/api/java/thaumcraft/api/IRunicArmor.java
new file mode 100644
index 0000000..5dd3110
--- /dev/null
+++ b/src/api/java/thaumcraft/api/IRunicArmor.java
@@ -0,0 +1,22 @@
+package thaumcraft.api;
+
+import net.minecraft.item.ItemStack;
+
+/**
+ *
+ * @author Azanor
+ *
+ * Armor or bauble slot items that implement this interface can provide runic shielding.
+ * Recharging, hardening, etc. is handled internally by thaumcraft.
+ *
+ */
+
+public interface IRunicArmor {
+
+ /**
+ * returns how much charge this item can provide. This is the base shielding value - any hardening is stored and calculated internally.
+ */
+ public int getRunicCharge(ItemStack itemstack);
+
+
+}
diff --git a/src/api/java/thaumcraft/api/IScribeTools.java b/src/api/java/thaumcraft/api/IScribeTools.java
new file mode 100644
index 0000000..8800fa5
--- /dev/null
+++ b/src/api/java/thaumcraft/api/IScribeTools.java
@@ -0,0 +1,14 @@
+package thaumcraft.api;
+
+
+/**
+ *
+ * @author Azanor
+ *
+ * Interface used to identify scribing tool items used in research table
+ *
+ */
+
+public interface IScribeTools {
+
+}
diff --git a/src/api/java/thaumcraft/api/IVisDiscountGear.java b/src/api/java/thaumcraft/api/IVisDiscountGear.java
new file mode 100644
index 0000000..3793ea3
--- /dev/null
+++ b/src/api/java/thaumcraft/api/IVisDiscountGear.java
@@ -0,0 +1,20 @@
+package thaumcraft.api;
+
+import net.minecraft.entity.player.EntityPlayer;
+import net.minecraft.item.ItemStack;
+import thaumcraft.api.aspects.Aspect;
+
+
+
+
+/**
+ * @author Azanor
+ * ItemArmor with this interface will grant a discount to the vis cost of actions the wearer performs with casting wands.
+ * The amount returned is the percentage by which the cost is discounted. There is a built-int max discount of 50%, but
+ * individual items really shouldn't have a discount more than 5%
+ */
+public interface IVisDiscountGear {
+
+ int getVisDiscount(ItemStack stack, EntityPlayer player, Aspect aspect);
+
+}
diff --git a/src/api/java/thaumcraft/api/IWarpingGear.java b/src/api/java/thaumcraft/api/IWarpingGear.java
new file mode 100644
index 0000000..e7415ab
--- /dev/null
+++ b/src/api/java/thaumcraft/api/IWarpingGear.java
@@ -0,0 +1,22 @@
+package thaumcraft.api;
+
+import net.minecraft.entity.player.EntityPlayer;
+import net.minecraft.item.ItemStack;
+
+/**
+ *
+ * @author Azanor
+ *
+ * Armor, held items or bauble slot items that implement this interface add warp when equipped or held.
+ *
+ */
+
+public interface IWarpingGear {
+
+ /**
+ * returns how much warp this item adds while worn or held.
+ */
+ public int getWarp(ItemStack itemstack, EntityPlayer player);
+
+
+}
diff --git a/src/api/java/thaumcraft/api/ItemApi.java b/src/api/java/thaumcraft/api/ItemApi.java
new file mode 100644
index 0000000..25dda28
--- /dev/null
+++ b/src/api/java/thaumcraft/api/ItemApi.java
@@ -0,0 +1,70 @@
+package thaumcraft.api;
+
+import net.minecraft.block.Block;
+import net.minecraft.item.Item;
+import net.minecraft.item.ItemStack;
+import cpw.mods.fml.common.FMLLog;
+
+/**
+ * @author Azanor
+ *
+ * This is used to gain access to the items in my mod.
+ * I only give some examples and it will probably still
+ * require a bit of work for you to get hold of everything you need.
+ *
+ */
+public class ItemApi {
+
+ public static ItemStack getItem(String itemString, int meta) {
+ ItemStack item = null;
+
+ try {
+ String itemClass = "thaumcraft.common.config.ConfigItems";
+ Object obj = Class.forName(itemClass).getField(itemString).get(null);
+ if (obj instanceof Item) {
+ item = new ItemStack((Item) obj,1,meta);
+ } else if (obj instanceof ItemStack) {
+ item = (ItemStack) obj;
+ }
+ } catch (Exception ex) {
+ FMLLog.warning("[Thaumcraft] Could not retrieve item identified by: " + itemString);
+ }
+
+ return item;
+ }
+
+ public static ItemStack getBlock(String itemString, int meta) {
+ ItemStack item = null;
+
+ try {
+ String itemClass = "thaumcraft.common.config.ConfigBlocks";
+ Object obj = Class.forName(itemClass).getField(itemString).get(null);
+ if (obj instanceof Block) {
+ item = new ItemStack((Block) obj,1,meta);
+ } else if (obj instanceof ItemStack) {
+ item = (ItemStack) obj;
+ }
+ } catch (Exception ex) {
+ FMLLog.warning("[Thaumcraft] Could not retrieve block identified by: " + itemString);
+ }
+
+ return item;
+ }
+
+ /**
+ *
+ * Some examples
+ *
+ * Casting Wands:
+ * itemWandCasting
+ *
+ * Resources:
+ * itemEssence, itemWispEssence, itemResource, itemShard, itemNugget,
+ * itemNuggetChicken, itemNuggetBeef, itemNuggetPork, itemTripleMeatTreat
+ *
+ * Research:
+ * itemResearchNotes, itemInkwell, itemThaumonomicon
+ *
+ */
+
+}
diff --git a/src/api/java/thaumcraft/api/ItemRunic.java b/src/api/java/thaumcraft/api/ItemRunic.java
new file mode 100644
index 0000000..80251f5
--- /dev/null
+++ b/src/api/java/thaumcraft/api/ItemRunic.java
@@ -0,0 +1,21 @@
+package thaumcraft.api;
+
+import net.minecraft.item.Item;
+import net.minecraft.item.ItemStack;
+
+public class ItemRunic extends Item implements IRunicArmor {
+
+ int charge;
+
+ public ItemRunic (int charge)
+ {
+ super();
+ this.charge = charge;
+ }
+
+ @Override
+ public int getRunicCharge(ItemStack itemstack) {
+ return charge;
+ }
+
+}
diff --git a/src/api/java/thaumcraft/api/ThaumcraftApi.java b/src/api/java/thaumcraft/api/ThaumcraftApi.java
new file mode 100644
index 0000000..fbf7880
--- /dev/null
+++ b/src/api/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<Block> portableHoleBlackList = new ArrayList<Block>();
+
+ //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<IScanEventHandler> scanEventhandlers = new ArrayList<IScanEventHandler>();
+ public static ArrayList<EntityTags> scanEntities = new ArrayList<EntityTags>();
+ 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. <br>For example the normal and wither skeleton:
+ * <br>ThaumcraftApi.registerEntityTag("Skeleton", (new AspectList()).add(Aspect.DEATH, 5));
+ * <br>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<Object,ItemStack> smeltingBonus = new HashMap<Object,ItemStack>();
+
+ /**
+ * 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<int[],Object[]> keyCache = new HashMap<int[],Object[]>();
+
+ 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<ri.getPages().length;a++) {
+ ResearchPage page = ri.getPages()[a];
+ if (page.recipe!=null && page.recipe instanceof CrucibleRecipe[]) {
+ CrucibleRecipe[] crs = (CrucibleRecipe[]) page.recipe;
+ for (CrucibleRecipe cr:crs) {
+ if (cr.getRecipeOutput().isItemEqual(stack)) {
+ keyCache.put(key,new Object[] {ri.key,a});
+ if (ThaumcraftApiHelper.isResearchComplete(player.getCommandSenderName(), ri.key))
+ return new Object[] {ri.key,a};
+ }
+ }
+ } else
+ if (page.recipeOutput!=null && stack !=null && page.recipeOutput.isItemEqual(stack)) {
+ keyCache.put(key,new Object[] {ri.key,a});
+ if (ThaumcraftApiHelper.isResearchComplete(player.getCommandSenderName(), ri.key))
+ return new Object[] {ri.key,a};
+ else
+ return null;
+ }
+ }
+ }
+ }
+ keyCache.put(key,null);
+ return null;
+ }
+
+ //ASPECTS////////////////////////////////////////
+
+ public static ConcurrentHashMap<List,AspectList> objectTags = new ConcurrentHashMap<List,AspectList>();
+ public static ConcurrentHashMap<List,int[]> groupedObjectTags = new ConcurrentHashMap<List,int[]>();
+
+ /**
+ * 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:<p>
+ * <i>ThaumcraftApi.registerObjectTag(new ItemStack(Blocks.cobblestone), (new AspectList()).add(Aspect.ENTROPY, 1).add(Aspect.EARTH, 1));</i>
+ * @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:<p>
+ * <i>ThaumcraftApi.registerObjectTag(new ItemStack(Blocks.cobblestone), new int[]{0,1}, (new AspectList()).add(Aspect.ENTROPY, 1).add(Aspect.EARTH, 1));</i>
+ * @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<ItemStack> 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:<p>
+ * <i>ThaumcraftApi.registerComplexObjectTag(new ItemStack(Blocks.cobblestone), (new AspectList()).add(Aspect.MECHANISM, 2).add(Aspect.MOTION, 4));</i>
+ * 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<Object,Integer> warpMap = new HashMap<Object,Integer>();
+
+ /**
+ * 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/api/java/thaumcraft/api/ThaumcraftApiHelper.java b/src/api/java/thaumcraft/api/ThaumcraftApiHelper.java
new file mode 100644
index 0000000..77d5588
--- /dev/null
+++ b/src/api/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<low) {
+ low = ta;
+ lowest = tag;
+ }
+ }
+ temp2.aspects.remove(lowest);
+ }
+ return temp2;
+ }
+
+ public static boolean areItemsEqual(ItemStack s1,ItemStack s2)
+ {
+ if (s1.isItemStackDamageable() && s2.isItemStackDamageable())
+ {
+ return s1.getItem() == s2.getItem();
+ } else
+ return s1.getItem() == s2.getItem() && s1.getItemDamage() == s2.getItemDamage();
+ }
+
+ public static boolean isResearchComplete(String username, String researchkey) {
+ return ThaumcraftApi.internalMethods.isResearchComplete(username, researchkey);
+ }
+
+ public static boolean hasDiscoveredAspect(String username, Aspect aspect) {
+ return ThaumcraftApi.internalMethods.hasDiscoveredAspect(username, aspect);
+ }
+
+ public static AspectList getDiscoveredAspects(String username) {
+ return ThaumcraftApi.internalMethods.getDiscoveredAspects(username);
+ }
+
+ public static ItemStack getStackInRowAndColumn(Object instance, int row, int column) {
+ return ThaumcraftApi.internalMethods.getStackInRowAndColumn(instance, row, column);
+ }
+
+ public static AspectList getObjectAspects(ItemStack is) {
+ return ThaumcraftApi.internalMethods.getObjectAspects(is);
+ }
+
+ public static AspectList getBonusObjectTags(ItemStack is,AspectList ot) {
+ return ThaumcraftApi.internalMethods.getBonusObjectTags(is, ot);
+ }
+
+ public static AspectList generateTags(Item item, int meta) {
+ return ThaumcraftApi.internalMethods.generateTags(item, meta);
+ }
+
+ public static boolean containsMatch(boolean strict, ItemStack[] inputs, ItemStack... targets)
+ {
+ for (ItemStack input : inputs)
+ {
+ for (ItemStack target : targets)
+ {
+ if (itemMatches(target, input, strict))
+ {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ public static boolean areItemStackTagsEqualForCrafting(ItemStack slotItem,ItemStack recipeItem)
+ {
+ if (recipeItem == null || slotItem == null) return false;
+ if (recipeItem.stackTagCompound!=null && slotItem.stackTagCompound==null ) return false;
+ if (recipeItem.stackTagCompound==null ) return true;
+
+ Iterator iterator = recipeItem.stackTagCompound.func_150296_c().iterator();
+ while (iterator.hasNext())
+ {
+ String s = (String)iterator.next();
+ if (slotItem.stackTagCompound.hasKey(s)) {
+ if (!slotItem.stackTagCompound.getTag(s).toString().equals(
+ recipeItem.stackTagCompound.getTag(s).toString())) {
+ return false;
+ }
+ } else {
+ return false;
+ }
+
+ }
+ return true;
+ }
+
+ public static boolean itemMatches(ItemStack target, ItemStack input, boolean strict)
+ {
+ if (input == null && target != null || input != null && target == null)
+ {
+ return false;
+ }
+ return (target.getItem() == input.getItem() &&
+ ((target.getItemDamage() == OreDictionary.WILDCARD_VALUE && !strict) || target.getItemDamage() == input.getItemDamage()));
+ }
+
+
+ public static TileEntity getConnectableTile(World world, int x, int y, int z, ForgeDirection face) {
+ TileEntity te = world.getTileEntity(x+face.offsetX, y+face.offsetY, z+face.offsetZ);
+ if (te instanceof IEssentiaTransport && ((IEssentiaTransport)te).isConnectable(face.getOpposite()))
+ return te;
+ else
+ return null;
+ }
+
+ public static TileEntity getConnectableTile(IBlockAccess world, int x, int y, int z, ForgeDirection face) {
+ TileEntity te = world.getTileEntity(x+face.offsetX, y+face.offsetY, z+face.offsetZ);
+ if (te instanceof IEssentiaTransport && ((IEssentiaTransport)te).isConnectable(face.getOpposite()))
+ return te;
+ else
+ return null;
+ }
+
+ private static HashMap<Integer, AspectList> allAspects= new HashMap<Integer, AspectList>();
+ private static HashMap<Integer, AspectList> allCompoundAspects= new HashMap<Integer, AspectList>();
+
+ 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/api/java/thaumcraft/api/TileThaumcraft.java b/src/api/java/thaumcraft/api/TileThaumcraft.java
new file mode 100644
index 0000000..56ccae8
--- /dev/null
+++ b/src/api/java/thaumcraft/api/TileThaumcraft.java
@@ -0,0 +1,63 @@
+package thaumcraft.api;
+
+import net.minecraft.nbt.NBTTagCompound;
+import net.minecraft.network.NetworkManager;
+import net.minecraft.network.Packet;
+import net.minecraft.network.play.server.S35PacketUpdateTileEntity;
+import net.minecraft.tileentity.TileEntity;
+
+/**
+ *
+ * @author azanor
+ *
+ * Custom tile entity class I use for most of my tile entities. Setup in such a way that only
+ * the nbt data within readCustomNBT / writeCustomNBT will be sent to the client when the tile
+ * updates. Apart from all the normal TE data that gets sent that is.
+ *
+ */
+public class TileThaumcraft extends TileEntity {
+
+ //NBT stuff
+
+ @Override
+ public void readFromNBT(NBTTagCompound nbttagcompound)
+ {
+ super.readFromNBT(nbttagcompound);
+ readCustomNBT(nbttagcompound);
+ }
+
+ public void readCustomNBT(NBTTagCompound nbttagcompound)
+ {
+ //TODO
+ }
+
+ @Override
+ public void writeToNBT(NBTTagCompound nbttagcompound)
+ {
+ super.writeToNBT(nbttagcompound);
+ writeCustomNBT(nbttagcompound);
+ }
+
+ public void writeCustomNBT(NBTTagCompound nbttagcompound)
+ {
+ //TODO
+ }
+
+ //Client Packet stuff
+ @Override
+ public Packet getDescriptionPacket() {
+ NBTTagCompound nbttagcompound = new NBTTagCompound();
+ this.writeCustomNBT(nbttagcompound);
+ return new S35PacketUpdateTileEntity(this.xCoord, this.yCoord, this.zCoord, -999, nbttagcompound);
+ }
+
+ @Override
+ public void onDataPacket(NetworkManager net, S35PacketUpdateTileEntity pkt) {
+ super.onDataPacket(net, pkt);
+ this.readCustomNBT(pkt.func_148857_g());
+ }
+
+
+
+
+}
diff --git a/src/api/java/thaumcraft/api/WorldCoordinates.java b/src/api/java/thaumcraft/api/WorldCoordinates.java
new file mode 100644
index 0000000..6c620af
--- /dev/null
+++ b/src/api/java/thaumcraft/api/WorldCoordinates.java
@@ -0,0 +1,117 @@
+package thaumcraft.api;
+
+import net.minecraft.nbt.NBTTagCompound;
+import net.minecraft.tileentity.TileEntity;
+
+public class WorldCoordinates implements Comparable
+{
+ public int x;
+
+ /** the y coordinate */
+ public int y;
+
+ /** the z coordinate */
+ public int z;
+
+ public int dim;
+
+ public WorldCoordinates() {}
+
+ public WorldCoordinates(int par1, int par2, int par3, int d)
+ {
+ this.x = par1;
+ this.y = par2;
+ this.z = par3;
+ this.dim = d;
+ }
+
+ public WorldCoordinates(TileEntity tile)
+ {
+ this.x = tile.xCoord;
+ this.y = tile.yCoord;
+ this.z = tile.zCoord;
+ this.dim = tile.getWorldObj().provider.dimensionId;
+ }
+
+ public WorldCoordinates(WorldCoordinates par1ChunkCoordinates)
+ {
+ this.x = par1ChunkCoordinates.x;
+ this.y = par1ChunkCoordinates.y;
+ this.z = par1ChunkCoordinates.z;
+ this.dim = par1ChunkCoordinates.dim;
+ }
+
+ public boolean equals(Object par1Obj)
+ {
+ if (!(par1Obj instanceof WorldCoordinates))
+ {
+ return false;
+ }
+ else
+ {
+ WorldCoordinates coordinates = (WorldCoordinates)par1Obj;
+ return this.x == coordinates.x && this.y == coordinates.y && this.z == coordinates.z && this.dim == coordinates.dim ;
+ }
+ }
+
+ public int hashCode()
+ {
+ return this.x + this.y << 8 + this.z << 16 + this.dim << 24;
+ }
+
+ /**
+ * Compare the coordinate with another coordinate
+ */
+ public int compareWorldCoordinate(WorldCoordinates par1)
+ {
+ return this.dim == par1.dim ? (
+ this.y == par1.y ? (this.z == par1.z ? this.x - par1.x : this.z - par1.z) : this.y - par1.y) : -1;
+ }
+
+ public void set(int par1, int par2, int par3, int d)
+ {
+ this.x = par1;
+ this.y = par2;
+ this.z = par3;
+ this.dim = d;
+ }
+
+ /**
+ * Returns the squared distance between this coordinates and the coordinates given as argument.
+ */
+ public float getDistanceSquared(int par1, int par2, int par3)
+ {
+ float f = (float)(this.x - par1);
+ float f1 = (float)(this.y - par2);
+ float f2 = (float)(this.z - par3);
+ return f * f + f1 * f1 + f2 * f2;
+ }
+
+ /**
+ * Return the squared distance between this coordinates and the ChunkCoordinates given as argument.
+ */
+ public float getDistanceSquaredToWorldCoordinates(WorldCoordinates par1ChunkCoordinates)
+ {
+ return this.getDistanceSquared(par1ChunkCoordinates.x, par1ChunkCoordinates.y, par1ChunkCoordinates.z);
+ }
+
+ public int compareTo(Object par1Obj)
+ {
+ return this.compareWorldCoordinate((WorldCoordinates)par1Obj);
+ }
+
+ public void readNBT(NBTTagCompound nbt) {
+ this.x = nbt.getInteger("w_x");
+ this.y = nbt.getInteger("w_y");
+ this.z = nbt.getInteger("w_z");
+ this.dim = nbt.getInteger("w_d");
+ }
+
+ public void writeNBT(NBTTagCompound nbt) {
+ nbt.setInteger("w_x",x);
+ nbt.setInteger("w_y",y);
+ nbt.setInteger("w_z",z);
+ nbt.setInteger("w_d",dim);
+ }
+
+}
diff --git a/src/api/java/thaumcraft/api/aspects/Aspect.java b/src/api/java/thaumcraft/api/aspects/Aspect.java
new file mode 100644
index 0000000..0ea13f5
--- /dev/null
+++ b/src/api/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<Aspect> getPrimalAspects() {
+ ArrayList<Aspect> primals = new ArrayList<Aspect>();
+ Collection<Aspect> pa = aspects.values();
+ for (Aspect aspect:pa) {
+ if (aspect.isPrimal()) primals.add(aspect);
+ }
+ return primals;
+ }
+
+ public static ArrayList<Aspect> getCompoundAspects() {
+ ArrayList<Aspect> compounds = new ArrayList<Aspect>();
+ Collection<Aspect> 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<String,Aspect> aspects = new LinkedHashMap<String,Aspect>();
+
+ //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/api/java/thaumcraft/api/aspects/AspectList.java b/src/api/java/thaumcraft/api/aspects/AspectList.java
new file mode 100644
index 0000000..b578141
--- /dev/null
+++ b/src/api/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<Aspect,Integer> aspects = new LinkedHashMap<Aspect,Integer>();//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;a<out.length-1;a++) {
+ Aspect e1 = out[a];
+ Aspect e2 = out[a+1];
+ if (e1!=null && e2!=null && e1.getTag().compareTo(e2.getTag())>0) {
+ 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;a<out.length-1;a++) {
+ int e1 = getAmount(out[a]);
+ int e2 = getAmount(out[a+1]);
+ if (e1>0 && 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 (amount<oldamount) amount=oldamount;
+
+ }
+ this.aspects.put( aspect, amount );
+ return this;
+ }
+
+ public AspectList add(AspectList in) {
+ for (Aspect a:in.getAspects())
+ this.add(a, in.getAmount(a));
+ return this;
+ }
+
+ public AspectList merge(AspectList in) {
+ for (Aspect a:in.getAspects())
+ this.merge(a, in.getAmount(a));
+ return this;
+ }
+
+ /**
+ * Reads the list of aspects from nbt
+ * @param nbttagcompound
+ * @return
+ */
+ public void readFromNBT(NBTTagCompound nbttagcompound)
+ {
+ aspects.clear();
+ NBTTagList tlist = nbttagcompound.getTagList("Aspects",(byte)10);
+ for (int j = 0; j < tlist.tagCount(); j++) {
+ NBTTagCompound rs = (NBTTagCompound) tlist.getCompoundTagAt(j);
+ if (rs.hasKey("key")) {
+ add( Aspect.getAspect(rs.getString("key")),
+ rs.getInteger("amount"));
+ }
+ }
+ }
+
+ public void readFromNBT(NBTTagCompound nbttagcompound, String label)
+ {
+ aspects.clear();
+ NBTTagList tlist = nbttagcompound.getTagList(label,(byte)10);
+ for (int j = 0; j < tlist.tagCount(); j++) {
+ NBTTagCompound rs = (NBTTagCompound) tlist.getCompoundTagAt(j);
+ if (rs.hasKey("key")) {
+ add( Aspect.getAspect(rs.getString("key")),
+ rs.getInteger("amount"));
+ }
+ }
+ }
+
+ /**
+ * Writes the list of aspects to nbt
+ * @param nbttagcompound
+ * @return
+ */
+ public void writeToNBT(NBTTagCompound nbttagcompound)
+ {
+ NBTTagList tlist = new NBTTagList();
+ nbttagcompound.setTag("Aspects", tlist);
+ for (Aspect aspect : getAspects())
+ if (aspect != null) {
+ NBTTagCompound f = new NBTTagCompound();
+ f.setString("key", aspect.getTag());
+ f.setInteger("amount", getAmount(aspect));
+ tlist.appendTag(f);
+ }
+ }
+
+ public void writeToNBT(NBTTagCompound nbttagcompound, String label)
+ {
+ NBTTagList tlist = new NBTTagList();
+ nbttagcompound.setTag(label, tlist);
+ for (Aspect aspect : getAspects())
+ if (aspect != null) {
+ NBTTagCompound f = new NBTTagCompound();
+ f.setString("key", aspect.getTag());
+ f.setInteger("amount", getAmount(aspect));
+ tlist.appendTag(f);
+ }
+ }
+
+}
diff --git a/src/api/java/thaumcraft/api/aspects/AspectSourceHelper.java b/src/api/java/thaumcraft/api/aspects/AspectSourceHelper.java
new file mode 100644
index 0000000..f22d8ce
--- /dev/null
+++ b/src/api/java/thaumcraft/api/aspects/AspectSourceHelper.java
@@ -0,0 +1,58 @@
+package thaumcraft.api.aspects;
+
+import java.lang.reflect.Method;
+
+import net.minecraft.tileentity.TileEntity;
+import net.minecraftforge.common.util.ForgeDirection;
+import cpw.mods.fml.common.FMLLog;
+
+public class AspectSourceHelper {
+
+ static Method drainEssentia;
+ static Method findEssentia;
+ /**
+ * This method is what is used to drain essentia from jars and other sources for things like
+ * infusion crafting or powering the arcane furnace. A record of possible sources are kept track of
+ * and refreshed as needed around the calling tile entity. This also renders the essentia trail particles.
+ * Only 1 essentia is drained at a time
+ * @param tile the tile entity that is draining the essentia
+ * @param aspect the aspect that you are looking for
+ * @param direction the direction from which you wish to drain. Forgedirection.Unknown simply seeks in all directions.
+ * @param range how many blocks you wish to search for essentia sources.
+ * @return boolean returns true if essentia was found and removed from a source.
+ */
+ public static boolean drainEssentia(TileEntity tile, Aspect aspect, ForgeDirection direction, int range) {
+ try {
+ if(drainEssentia == null) {
+ Class fake = Class.forName("thaumcraft.common.lib.events.EssentiaHandler");
+ drainEssentia = fake.getMethod("drainEssentia", TileEntity.class, Aspect.class, ForgeDirection.class, int.class);
+ }
+ return (Boolean) drainEssentia.invoke(null, tile, aspect, direction, range);
+ } catch(Exception ex) {
+ FMLLog.warning("[Thaumcraft API] Could not invoke thaumcraft.common.lib.events.EssentiaHandler method drainEssentia");
+ }
+ return false;
+ }
+
+ /**
+ * This method returns if there is any essentia of the passed type that can be drained. It in no way checks how
+ * much there is, only if an essentia container nearby contains at least 1 point worth.
+ * @param tile the tile entity that is checking the essentia
+ * @param aspect the aspect that you are looking for
+ * @param direction the direction from which you wish to drain. Forgedirection.Unknown simply seeks in all directions.
+ * @param range how many blocks you wish to search for essentia sources.
+ * @return boolean returns true if essentia was found and removed from a source.
+ */
+ public static boolean findEssentia(TileEntity tile, Aspect aspect, ForgeDirection direction, int range) {
+ try {
+ if(findEssentia == null) {
+ Class fake = Class.forName("thaumcraft.common.lib.events.EssentiaHandler");
+ findEssentia = fake.getMethod("findEssentia", TileEntity.class, Aspect.class, ForgeDirection.class, int.class);
+ }
+ return (Boolean) findEssentia.invoke(null, tile, aspect, direction, range);
+ } catch(Exception ex) {
+ FMLLog.warning("[Thaumcraft API] Could not invoke thaumcraft.common.lib.events.EssentiaHandler method findEssentia");
+ }
+ return false;
+ }
+}
diff --git a/src/api/java/thaumcraft/api/aspects/IAspectContainer.java b/src/api/java/thaumcraft/api/aspects/IAspectContainer.java
new file mode 100644
index 0000000..bb34ae8
--- /dev/null
+++ b/src/api/java/thaumcraft/api/aspects/IAspectContainer.java
@@ -0,0 +1,80 @@
+package thaumcraft.api.aspects;
+
+
+/**
+ *
+ * @author azanor
+ *
+ * Used by blocks like the crucible and alembic to hold their aspects.
+ * Tiles extending this interface will have their aspects show up when viewed by goggles of revealing
+ *
+ */
+public interface IAspectContainer {
+ public AspectList getAspects();
+
+
+ public void setAspects(AspectList aspects);
+
+
+ /**
+ * This method is used to determine of a specific aspect can be added to this container.
+ * @param tag
+ * @return true or false
+ */
+ public boolean doesContainerAccept(Aspect tag);
+
+ /**
+ * This method is used to add a certain amount of an aspect to the tile entity.
+ * @param tag
+ * @param amount
+ * @return the amount of aspect left over that could not be added.
+ */
+ public int addToContainer(Aspect tag, int amount);
+
+ /**
+ * Removes a certain amount of a specific aspect from the tile entity
+ * @param tag
+ * @param amount
+ * @return true if that amount of aspect was available and was removed
+ */
+ public boolean takeFromContainer(Aspect tag, int amount);
+
+ /**
+ * removes a bunch of different aspects and amounts from the tile entity.
+ * @param ot the ObjectTags object that contains the aspects and their amounts.
+ * @return true if all the aspects and their amounts were available and successfully removed
+ *
+ * Going away in the next major patch
+ */
+ @Deprecated
+ public boolean takeFromContainer(AspectList ot);
+
+ /**
+ * Checks if the tile entity contains the listed amount (or more) of the aspect
+ * @param tag
+ * @param amount
+ * @return
+ */
+ public boolean doesContainerContainAmount(Aspect tag,int amount);
+
+ /**
+ * Checks if the tile entity contains all the listed aspects and their amounts
+ * @param ot the ObjectTags object that contains the aspects and their amounts.
+ * @return
+ *
+ * Going away in the next major patch
+ */
+ @Deprecated
+ public boolean doesContainerContain(AspectList ot);
+
+ /**
+ * Returns how much of the aspect this tile entity contains
+ * @param tag
+ * @return the amount of that aspect found
+ */
+ public int containerContains(Aspect tag);
+
+}
+
+
+
diff --git a/src/api/java/thaumcraft/api/aspects/IAspectSource.java b/src/api/java/thaumcraft/api/aspects/IAspectSource.java
new file mode 100644
index 0000000..c01f569
--- /dev/null
+++ b/src/api/java/thaumcraft/api/aspects/IAspectSource.java
@@ -0,0 +1,16 @@
+package thaumcraft.api.aspects;
+
+
+
+/**
+ * @author Azanor
+ *
+ * This interface is implemented by tile entites (or possibly anything else) like jars
+ * so that they can act as an essentia source for blocks like the infusion altar.
+ *
+ */
+public interface IAspectSource extends IAspectContainer {
+
+
+
+}
diff --git a/src/api/java/thaumcraft/api/aspects/IEssentiaContainerItem.java b/src/api/java/thaumcraft/api/aspects/IEssentiaContainerItem.java
new file mode 100644
index 0000000..6a82c0e
--- /dev/null
+++ b/src/api/java/thaumcraft/api/aspects/IEssentiaContainerItem.java
@@ -0,0 +1,37 @@
+package thaumcraft.api.aspects;
+
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.NBTTagCompound;
+
+/**
+ *
+ * @author azanor
+ *
+ * Used by wispy essences and essentia phials to hold their aspects.
+ * Useful for similar item containers that store their aspect information in nbt form so TC
+ * automatically picks up the aspects they contain
+ *
+ */
+public interface IEssentiaContainerItem {
+ public AspectList getAspects(ItemStack itemstack);
+ public void setAspects(ItemStack itemstack, AspectList aspects);
+}
+
+//Example implementation
+/*
+ @Override
+ public AspectList getAspects(ItemStack itemstack) {
+ if (itemstack.hasTagCompound()) {
+ AspectList aspects = new AspectList();
+ aspects.readFromNBT(itemstack.getTagCompound());
+ return aspects.size()>0?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/api/java/thaumcraft/api/aspects/IEssentiaTransport.java b/src/api/java/thaumcraft/api/aspects/IEssentiaTransport.java
new file mode 100644
index 0000000..fecbc16
--- /dev/null
+++ b/src/api/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/api/java/thaumcraft/api/crafting/CrucibleRecipe.java b/src/api/java/thaumcraft/api/crafting/CrucibleRecipe.java
new file mode 100644
index 0000000..595b6e5
--- /dev/null
+++ b/src/api/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<ItemStack>)catalyst).size()>0) {
+ for (ItemStack is :(ArrayList<ItemStack>)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<ItemStack>)catalyst).size()>0) {
+ ItemStack[] ores = ((ArrayList<ItemStack>)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)<aspects.getAmount(tag)) return false;
+ }
+ return true;
+ }
+
+ public boolean catalystMatches(ItemStack cat) {
+ if (catalyst instanceof ItemStack && ThaumcraftApiHelper.itemMatches((ItemStack) catalyst,cat,false)) {
+ return true;
+ } else
+ if (catalyst instanceof ArrayList && ((ArrayList<ItemStack>)catalyst).size()>0) {
+ ItemStack[] ores = ((ArrayList<ItemStack>)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/api/java/thaumcraft/api/crafting/IArcaneRecipe.java b/src/api/java/thaumcraft/api/crafting/IArcaneRecipe.java
new file mode 100644
index 0000000..bb5036d
--- /dev/null
+++ b/src/api/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/api/java/thaumcraft/api/crafting/IInfusionStabiliser.java b/src/api/java/thaumcraft/api/crafting/IInfusionStabiliser.java
new file mode 100644
index 0000000..d137ff2
--- /dev/null
+++ b/src/api/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/api/java/thaumcraft/api/crafting/InfusionEnchantmentRecipe.java b/src/api/java/thaumcraft/api/crafting/InfusionEnchantmentRecipe.java
new file mode 100644
index 0000000..bdc5f50
--- /dev/null
+++ b/src/api/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<ItemStack> 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<ItemStack> ii = new ArrayList<ItemStack>();
+ for (ItemStack is:input) {
+ ii.add(is.copy());
+ }
+
+ for (ItemStack comp:components) {
+ boolean b=false;
+ for (int a=0;a<ii.size();a++) {
+ i2 = ii.get(a).copy();
+ if (comp.getItemDamage()==OreDictionary.WILDCARD_VALUE) {
+ i2.setItemDamage(OreDictionary.WILDCARD_VALUE);
+ }
+ if (areItemStacksEqual(i2, comp,true)) {
+ ii.remove(a);
+ b=true;
+ break;
+ }
+ }
+ if (!b) return false;
+ }
+// System.out.println(ii.size());
+ return ii.size()==0?true:false;
+ }
+
+ protected boolean areItemStacksEqual(ItemStack stack0, ItemStack stack1, boolean fuzzy)
+ {
+ if (stack0==null && stack1!=null) return false;
+ if (stack0!=null && stack1==null) return false;
+ if (stack0==null && stack1==null) return true;
+ boolean t1=ThaumcraftApiHelper.areItemStackTagsEqualForCrafting(stack0, stack1);
+ if (!t1) return false;
+ if (fuzzy) {
+ Integer od = OreDictionary.getOreID(stack0);
+ if (od!=-1) {
+ ItemStack[] ores = OreDictionary.getOres(od).toArray(new ItemStack[]{});
+ if (ThaumcraftApiHelper.containsMatch(false, new ItemStack[]{stack1}, ores))
+ return true;
+ }
+ }
+ return stack0.getItem() != stack1.getItem() ? false : (stack0.getItemDamage() != stack1.getItemDamage() ? false : stack0.stackSize <= stack0.getMaxStackSize() );
+ }
+
+
+ public Enchantment getEnchantment() {
+ return enchantment;
+
+ }
+
+ public AspectList getAspects() {
+ return aspects;
+
+ }
+
+ public String getResearch() {
+ return research;
+
+ }
+
+ public int calcInstability(ItemStack recipeInput) {
+ int i = 0;
+ Map map1 = EnchantmentHelper.getEnchantments(recipeInput);
+ Iterator iterator = map1.keySet().iterator();
+ while (iterator.hasNext())
+ {
+ int j1 = ((Integer)iterator.next()).intValue();
+ i += EnchantmentHelper.getEnchantmentLevel(j1, recipeInput);
+ }
+ return (i/2) + instability;
+ }
+
+ public int calcXP(ItemStack recipeInput) {
+ return recipeXP * (1+EnchantmentHelper.getEnchantmentLevel(enchantment.effectId, recipeInput));
+ }
+
+ public float getEssentiaMod(ItemStack recipeInput) {
+ float mod = EnchantmentHelper.getEnchantmentLevel(enchantment.effectId, recipeInput);
+ Map map1 = EnchantmentHelper.getEnchantments(recipeInput);
+ Iterator iterator = map1.keySet().iterator();
+ while (iterator.hasNext())
+ {
+ int j1 = ((Integer)iterator.next()).intValue();
+ if (j1 != enchantment.effectId)
+ mod += EnchantmentHelper.getEnchantmentLevel(j1, recipeInput) * .1f;
+ }
+ return mod;
+ }
+
+}
diff --git a/src/api/java/thaumcraft/api/crafting/InfusionRecipe.java b/src/api/java/thaumcraft/api/crafting/InfusionRecipe.java
new file mode 100644
index 0000000..d786b81
--- /dev/null
+++ b/src/api/java/thaumcraft/api/crafting/InfusionRecipe.java
@@ -0,0 +1,133 @@
+package thaumcraft.api.crafting;
+
+import java.util.ArrayList;
+
+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 InfusionRecipe
+{
+ protected AspectList aspects;
+ protected String research;
+ private ItemStack[] components;
+ private ItemStack recipeInput;
+ protected Object recipeOutput;
+ protected int instability;
+
+ public InfusionRecipe(String research, Object output, int inst,
+ AspectList aspects2, ItemStack input, ItemStack[] recipe) {
+ this.research = research;
+ this.recipeOutput = output;
+ this.recipeInput = input;
+ this.aspects = aspects2;
+ this.components = recipe;
+ this.instability = inst;
+ }
+
+ /**
+ * Used to check if a recipe matches current crafting inventory
+ * @param player
+ */
+ public boolean matches(ArrayList<ItemStack> 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<ItemStack> ii = new ArrayList<ItemStack>();
+ for (ItemStack is:input) {
+ ii.add(is.copy());
+ }
+
+ for (ItemStack comp:getComponents()) {
+ boolean b=false;
+ for (int a=0;a<ii.size();a++) {
+ i2 = ii.get(a).copy();
+ if (comp.getItemDamage()==OreDictionary.WILDCARD_VALUE) {
+ i2.setItemDamage(OreDictionary.WILDCARD_VALUE);
+ }
+ if (areItemStacksEqual(i2, comp,true)) {
+ ii.remove(a);
+ b=true;
+ break;
+ }
+ }
+ if (!b) return false;
+ }
+ return ii.size()==0?true:false;
+ }
+
+ public static boolean areItemStacksEqual(ItemStack stack0, ItemStack stack1, boolean fuzzy)
+ {
+ if (stack0==null && stack1!=null) return false;
+ if (stack0!=null && stack1==null) return false;
+ if (stack0==null && stack1==null) return true;
+
+ //nbt
+ boolean t1=ThaumcraftApiHelper.areItemStackTagsEqualForCrafting(stack0, stack1);
+ if (!t1) return false;
+
+ if (fuzzy) {
+ Integer od = OreDictionary.getOreID(stack0);
+ if (od!=-1) {
+ ItemStack[] ores = OreDictionary.getOres(od).toArray(new ItemStack[]{});
+ if (ThaumcraftApiHelper.containsMatch(false, new ItemStack[]{stack1}, ores))
+ return true;
+ }
+ }
+
+ //damage
+ boolean damage = stack0.getItemDamage() == stack1.getItemDamage() ||
+ stack1.getItemDamage() == OreDictionary.WILDCARD_VALUE;
+
+ return stack0.getItem() != stack1.getItem() ? false : (!damage ? false : stack0.stackSize <= stack0.getMaxStackSize() );
+ }
+
+ public Object getRecipeOutput() {
+ return getRecipeOutput(this.getRecipeInput());
+ }
+
+ public AspectList getAspects() {
+ return getAspects(this.getRecipeInput());
+ }
+
+ public int getInstability() {
+ return getInstability(this.getRecipeInput());
+ }
+
+ public String getResearch() {
+ return research;
+ }
+
+ public ItemStack getRecipeInput() {
+ return recipeInput;
+ }
+
+ public ItemStack[] getComponents() {
+ return components;
+ }
+
+ public Object getRecipeOutput(ItemStack input) {
+ return recipeOutput;
+ }
+
+ public AspectList getAspects(ItemStack input) {
+ return aspects;
+ }
+
+ public int getInstability(ItemStack input) {
+ return instability;
+ }
+}
diff --git a/src/api/java/thaumcraft/api/crafting/ShapedArcaneRecipe.java b/src/api/java/thaumcraft/api/crafting/ShapedArcaneRecipe.java
new file mode 100644
index 0000000..1490110
--- /dev/null
+++ b/src/api/java/thaumcraft/api/crafting/ShapedArcaneRecipe.java
@@ -0,0 +1,261 @@
+package thaumcraft.api.crafting;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+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 ShapedArcaneRecipe implements IArcaneRecipe
+{
+ //Added in for future ease of change, but hard coded for now.
+ private static final int MAX_CRAFT_GRID_WIDTH = 3;
+ private static final int MAX_CRAFT_GRID_HEIGHT = 3;
+
+ public ItemStack output = null;
+ public Object[] input = null;
+ public AspectList aspects = null;
+ public String research;
+ public int width = 0;
+ public int height = 0;
+ private boolean mirrored = true;
+
+ public ShapedArcaneRecipe(String research, Block result, AspectList aspects, Object... recipe){ this(research, new ItemStack(result), aspects, recipe); }
+ public ShapedArcaneRecipe(String research, Item result, AspectList aspects, Object... recipe){ this(research, new ItemStack(result), aspects, recipe); }
+ public ShapedArcaneRecipe(String research, ItemStack result, AspectList aspects, Object... recipe)
+ {
+ output = result.copy();
+ this.research = research;
+ this.aspects = aspects;
+ String shape = "";
+
+ int idx = 0;
+
+ if (recipe[idx] instanceof Boolean)
+ {
+ mirrored = (Boolean)recipe[idx];
+ if (recipe[idx+1] instanceof Object[])
+ {
+ recipe = (Object[])recipe[idx+1];
+ }
+ else
+ {
+ idx = 1;
+ }
+ }
+
+ if (recipe[idx] instanceof String[])
+ {
+ String[] parts = ((String[])recipe[idx++]);
+
+ for (String s : parts)
+ {
+ width = s.length();
+ shape += s;
+ }
+
+ height = parts.length;
+ }
+ else
+ {
+ while (recipe[idx] instanceof String)
+ {
+ String s = (String)recipe[idx++];
+ shape += s;
+ width = s.length();
+ height++;
+ }
+ }
+
+ if (width * height != shape.length())
+ {
+ String ret = "Invalid shaped ore recipe: ";
+ for (Object tmp : recipe)
+ {
+ ret += tmp + ", ";
+ }
+ ret += output;
+ throw new RuntimeException(ret);
+ }
+
+ HashMap<Character, Object> itemMap = new HashMap<Character, Object>();
+
+ 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<ItemStack>)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/api/java/thaumcraft/api/crafting/ShapelessArcaneRecipe.java b/src/api/java/thaumcraft/api/crafting/ShapelessArcaneRecipe.java
new file mode 100644
index 0000000..3b1eaeb
--- /dev/null
+++ b/src/api/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<ItemStack>)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/api/java/thaumcraft/api/damagesource/DamageSourceIndirectThaumcraftEntity.java b/src/api/java/thaumcraft/api/damagesource/DamageSourceIndirectThaumcraftEntity.java
new file mode 100644
index 0000000..1562d05
--- /dev/null
+++ b/src/api/java/thaumcraft/api/damagesource/DamageSourceIndirectThaumcraftEntity.java
@@ -0,0 +1,32 @@
+package thaumcraft.api.damagesource;
+
+import net.minecraft.entity.Entity;
+import net.minecraft.util.DamageSource;
+import net.minecraft.util.EntityDamageSourceIndirect;
+
+public class DamageSourceIndirectThaumcraftEntity extends EntityDamageSourceIndirect {
+
+ private boolean fireDamage;
+ private float hungerDamage;
+ private boolean isUnblockable;
+
+
+ public DamageSourceIndirectThaumcraftEntity(String par1Str,
+ Entity par2Entity, Entity par3Entity) {
+ super(par1Str, par2Entity, par3Entity);
+ }
+
+
+ public DamageSource setFireDamage()
+ {
+ this.fireDamage = true;
+ return this;
+ }
+
+ public DamageSource setDamageBypassesArmor()
+ {
+ this.isUnblockable = true;
+ this.hungerDamage = 0.0F;
+ return this;
+ }
+}
diff --git a/src/api/java/thaumcraft/api/damagesource/DamageSourceThaumcraft.java b/src/api/java/thaumcraft/api/damagesource/DamageSourceThaumcraft.java
new file mode 100644
index 0000000..bb55672
--- /dev/null
+++ b/src/api/java/thaumcraft/api/damagesource/DamageSourceThaumcraft.java
@@ -0,0 +1,47 @@
+package thaumcraft.api.damagesource;
+
+import net.minecraft.entity.EntityLivingBase;
+import net.minecraft.util.DamageSource;
+import net.minecraft.util.EntityDamageSource;
+
+public class DamageSourceThaumcraft extends DamageSource
+{
+
+ public static DamageSource taint = new DamageSourceThaumcraft("taint").setDamageBypassesArmor().setMagicDamage();
+ public static DamageSource tentacle = new DamageSourceThaumcraft("tentacle");
+ public static DamageSource swarm = new DamageSourceThaumcraft("swarm");
+ public static DamageSource dissolve = new DamageSourceThaumcraft("dissolve").setDamageBypassesArmor();
+
+ protected DamageSourceThaumcraft(String par1Str) {
+ super(par1Str);
+ }
+
+ /** This kind of damage can be blocked or not. */
+ private boolean isUnblockable = false;
+ private boolean isDamageAllowedInCreativeMode = false;
+ private float hungerDamage = 0.3F;
+
+ /** This kind of damage is based on fire or not. */
+ private boolean fireDamage;
+
+ /** This kind of damage is based on a projectile or not. */
+ private boolean projectile;
+
+ /**
+ * Whether this damage source will have its damage amount scaled based on the current difficulty.
+ */
+ private boolean difficultyScaled;
+ private boolean magicDamage = false;
+ private boolean explosion = false;
+
+ public static DamageSource causeSwarmDamage(EntityLivingBase par0EntityLiving)
+ {
+ return new EntityDamageSource("swarm", par0EntityLiving);
+ }
+
+ public static DamageSource causeTentacleDamage(EntityLivingBase par0EntityLiving)
+ {
+ return new EntityDamageSource("tentacle", par0EntityLiving);
+ }
+
+}
diff --git a/src/api/java/thaumcraft/api/entities/IEldritchMob.java b/src/api/java/thaumcraft/api/entities/IEldritchMob.java
new file mode 100644
index 0000000..79fc75b
--- /dev/null
+++ b/src/api/java/thaumcraft/api/entities/IEldritchMob.java
@@ -0,0 +1,5 @@
+package thaumcraft.api.entities;
+
+public interface IEldritchMob {
+
+}
diff --git a/src/api/java/thaumcraft/api/entities/ITaintedMob.java b/src/api/java/thaumcraft/api/entities/ITaintedMob.java
new file mode 100644
index 0000000..83fb1fc
--- /dev/null
+++ b/src/api/java/thaumcraft/api/entities/ITaintedMob.java
@@ -0,0 +1,5 @@
+package thaumcraft.api.entities;
+
+public interface ITaintedMob {
+
+}
diff --git a/src/api/java/thaumcraft/api/internal/DummyInternalMethodHandler.java b/src/api/java/thaumcraft/api/internal/DummyInternalMethodHandler.java
new file mode 100644
index 0000000..9af8c30
--- /dev/null
+++ b/src/api/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/api/java/thaumcraft/api/internal/IInternalMethodHandler.java b/src/api/java/thaumcraft/api/internal/IInternalMethodHandler.java
new file mode 100644
index 0000000..73fce66
--- /dev/null
+++ b/src/api/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/api/java/thaumcraft/api/internal/WeightedRandomLoot.java b/src/api/java/thaumcraft/api/internal/WeightedRandomLoot.java
new file mode 100644
index 0000000..4ac6ee7
--- /dev/null
+++ b/src/api/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<WeightedRandomLoot> lootBagCommon = new ArrayList<WeightedRandomLoot>();
+ public static ArrayList<WeightedRandomLoot> lootBagUncommon = new ArrayList<WeightedRandomLoot>();
+ public static ArrayList<WeightedRandomLoot> lootBagRare = new ArrayList<WeightedRandomLoot>();
+
+}
diff --git a/src/api/java/thaumcraft/api/nodes/INode.java b/src/api/java/thaumcraft/api/nodes/INode.java
new file mode 100644
index 0000000..61da1f7
--- /dev/null
+++ b/src/api/java/thaumcraft/api/nodes/INode.java
@@ -0,0 +1,53 @@
+package thaumcraft.api.nodes;
+
+import thaumcraft.api.aspects.Aspect;
+import thaumcraft.api.aspects.AspectList;
+import thaumcraft.api.aspects.IAspectContainer;
+
+public interface INode extends IAspectContainer {
+
+ /**
+ * Unique identifier to distinguish nodes. Normal node id's are based on world id and coordinates
+ * @return
+ */
+ public String getId();
+
+ public AspectList getAspectsBase();
+
+ /**
+ * Return the type of node
+ * @return
+ */
+ public NodeType getNodeType();
+
+ /**
+ * Set the type of node
+ * @return
+ */
+ public void setNodeType(NodeType nodeType);
+
+ /**
+ * Set the node modifier
+ * @return
+ */
+ public void setNodeModifier(NodeModifier nodeModifier);
+
+ /**
+ * Return the node modifier
+ * @return
+ */
+ public NodeModifier getNodeModifier();
+
+ /**
+ * Return the maximum capacity of each aspect the node can hold
+ * @return
+ */
+ public int getNodeVisBase(Aspect aspect);
+
+ /**
+ * Set the maximum capacity of each aspect the node can hold
+ * @return
+ */
+ public void setNodeVisBase(Aspect aspect, short nodeVisBase);
+
+}
diff --git a/src/api/java/thaumcraft/api/nodes/IRevealer.java b/src/api/java/thaumcraft/api/nodes/IRevealer.java
new file mode 100644
index 0000000..14a19b5
--- /dev/null
+++ b/src/api/java/thaumcraft/api/nodes/IRevealer.java
@@ -0,0 +1,22 @@
+package thaumcraft.api.nodes;
+
+import net.minecraft.entity.EntityLivingBase;
+import net.minecraft.item.ItemStack;
+
+/**
+ *
+ * @author Azanor
+ *
+ * Equipped head slot items that extend this class will make nodes visible in world.
+ *
+ */
+
+public interface IRevealer {
+
+ /*
+ * If this method returns true the nodes will be visible.
+ */
+ public boolean showNodes(ItemStack itemstack, EntityLivingBase player);
+
+
+}
diff --git a/src/api/java/thaumcraft/api/nodes/NodeModifier.java b/src/api/java/thaumcraft/api/nodes/NodeModifier.java
new file mode 100644
index 0000000..885b867
--- /dev/null
+++ b/src/api/java/thaumcraft/api/nodes/NodeModifier.java
@@ -0,0 +1,6 @@
+package thaumcraft.api.nodes;
+
+public enum NodeModifier
+{
+ BRIGHT, PALE, FADING
+} \ No newline at end of file
diff --git a/src/api/java/thaumcraft/api/nodes/NodeType.java b/src/api/java/thaumcraft/api/nodes/NodeType.java
new file mode 100644
index 0000000..355324b
--- /dev/null
+++ b/src/api/java/thaumcraft/api/nodes/NodeType.java
@@ -0,0 +1,6 @@
+package thaumcraft.api.nodes;
+
+public enum NodeType
+{
+ NORMAL, UNSTABLE, DARK, TAINTED, HUNGRY, PURE
+} \ No newline at end of file
diff --git a/src/api/java/thaumcraft/api/package-info.java b/src/api/java/thaumcraft/api/package-info.java
new file mode 100644
index 0000000..cceaf89
--- /dev/null
+++ b/src/api/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/api/java/thaumcraft/api/potions/PotionFluxTaint.java b/src/api/java/thaumcraft/api/potions/PotionFluxTaint.java
new file mode 100644
index 0000000..b950de0
--- /dev/null
+++ b/src/api/java/thaumcraft/api/potions/PotionFluxTaint.java
@@ -0,0 +1,67 @@
+package thaumcraft.api.potions;
+
+import net.minecraft.client.Minecraft;
+import net.minecraft.entity.EntityLivingBase;
+import net.minecraft.entity.player.EntityPlayer;
+import net.minecraft.potion.Potion;
+import net.minecraft.util.ResourceLocation;
+import thaumcraft.api.damagesource.DamageSourceThaumcraft;
+import thaumcraft.api.entities.ITaintedMob;
+import cpw.mods.fml.relauncher.Side;
+import cpw.mods.fml.relauncher.SideOnly;
+
+public class PotionFluxTaint extends Potion
+{
+ public static PotionFluxTaint instance = null; // will be instantiated at runtime
+ private int statusIconIndex = -1;
+
+ public PotionFluxTaint(int par1, boolean par2, int par3)
+ {
+ super(par1,par2,par3);
+ setIconIndex(0, 0);
+ }
+
+ public static void init()
+ {
+ instance.setPotionName("potion.fluxtaint");
+ instance.setIconIndex(3, 1);
+ instance.setEffectiveness(0.25D);
+ }
+
+ @Override
+ public boolean isBadEffect() {
+ return true;
+ }
+
+ @Override
+ @SideOnly(Side.CLIENT)
+ public int getStatusIconIndex() {
+ Minecraft.getMinecraft().renderEngine.bindTexture(rl);
+ return super.getStatusIconIndex();
+ }
+
+ static final ResourceLocation rl = new ResourceLocation("thaumcraft","textures/misc/potions.png");
+
+ @Override
+ public void performEffect(EntityLivingBase target, int par2) {
+ if (target instanceof ITaintedMob) {
+ target.heal(1);
+ } else
+ if (!target.isEntityUndead() && !(target instanceof EntityPlayer))
+ {
+ target.attackEntityFrom(DamageSourceThaumcraft.taint, 1);
+ }
+ else
+ if (!target.isEntityUndead() && (target.getMaxHealth() > 1 || (target instanceof EntityPlayer)))
+ {
+ target.attackEntityFrom(DamageSourceThaumcraft.taint, 1);
+ }
+ }
+
+ public boolean isReady(int par1, int par2)
+ {
+ int k = 40 >> par2;
+ return k > 0 ? par1 % k == 0 : true;
+ }
+
+}
diff --git a/src/api/java/thaumcraft/api/potions/PotionVisExhaust.java b/src/api/java/thaumcraft/api/potions/PotionVisExhaust.java
new file mode 100644
index 0000000..cd7fc18
--- /dev/null
+++ b/src/api/java/thaumcraft/api/potions/PotionVisExhaust.java
@@ -0,0 +1,48 @@
+package thaumcraft.api.potions;
+
+import net.minecraft.client.Minecraft;
+import net.minecraft.entity.EntityLivingBase;
+import net.minecraft.potion.Potion;
+import net.minecraft.util.ResourceLocation;
+import cpw.mods.fml.relauncher.Side;
+import cpw.mods.fml.relauncher.SideOnly;
+
+public class PotionVisExhaust extends Potion
+{
+ public static PotionVisExhaust instance = null; // will be instantiated at runtime
+ private int statusIconIndex = -1;
+
+ public PotionVisExhaust(int par1, boolean par2, int par3)
+ {
+ super(par1,par2,par3);
+ setIconIndex(0, 0);
+ }
+
+ public static void init()
+ {
+ instance.setPotionName("potion.visexhaust");
+ instance.setIconIndex(5, 1);
+ instance.setEffectiveness(0.25D);
+ }
+
+ @Override
+ public boolean isBadEffect() {
+ return true;
+ }
+
+ @Override
+ @SideOnly(Side.CLIENT)
+ public int getStatusIconIndex() {
+ Minecraft.getMinecraft().renderEngine.bindTexture(rl);
+ return super.getStatusIconIndex();
+ }
+
+ static final ResourceLocation rl = new ResourceLocation("thaumcraft","textures/misc/potions.png");
+
+ @Override
+ public void performEffect(EntityLivingBase target, int par2) {
+
+ }
+
+
+}
diff --git a/src/api/java/thaumcraft/api/research/IScanEventHandler.java b/src/api/java/thaumcraft/api/research/IScanEventHandler.java
new file mode 100644
index 0000000..d0efac5
--- /dev/null
+++ b/src/api/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/api/java/thaumcraft/api/research/ResearchCategories.java b/src/api/java/thaumcraft/api/research/ResearchCategories.java
new file mode 100644
index 0000000..82309b3
--- /dev/null
+++ b/src/api/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 <String, ResearchCategoryList> researchCategories = new LinkedHashMap <String,ResearchCategoryList>();
+
+ /**
+ * @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/api/java/thaumcraft/api/research/ResearchCategoryList.java b/src/api/java/thaumcraft/api/research/ResearchCategoryList.java
new file mode 100644
index 0000000..7eed010
--- /dev/null
+++ b/src/api/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<String, ResearchItem> research = new HashMap<String,ResearchItem>();
+
+
+
+
+}
diff --git a/src/api/java/thaumcraft/api/research/ResearchItem.java b/src/api/java/thaumcraft/api/research/ResearchItem.java
new file mode 100644
index 0000000..1d5fd8b
--- /dev/null
+++ b/src/api/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/api/java/thaumcraft/api/research/ResearchPage.java b/src/api/java/thaumcraft/api/research/ResearchPage.java
new file mode 100644
index 0000000..fdada84
--- /dev/null
+++ b/src/api/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/api/java/thaumcraft/api/research/ScanResult.java b/src/api/java/thaumcraft/api/research/ScanResult.java
new file mode 100644
index 0000000..e1498f3
--- /dev/null
+++ b/src/api/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;
+ }
+
+}
diff --git a/src/api/java/thaumcraft/api/visnet/TileVisNode.java b/src/api/java/thaumcraft/api/visnet/TileVisNode.java
new file mode 100644
index 0000000..204879e
--- /dev/null
+++ b/src/api/java/thaumcraft/api/visnet/TileVisNode.java
@@ -0,0 +1,188 @@
+package thaumcraft.api.visnet;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import thaumcraft.api.TileThaumcraft;
+import thaumcraft.api.WorldCoordinates;
+import thaumcraft.api.aspects.Aspect;
+
+/**
+ * @author Azanor
+ *
+ * The tile entity used by nodes in the vis energy network. A node is either a source (like an aura node),
+ * a transport relay or vis receiver (like the infernal furnace).
+ *
+ */
+public abstract class TileVisNode extends TileThaumcraft {
+
+ WeakReference<TileVisNode> parent = null;
+ ArrayList<WeakReference<TileVisNode>> children = new ArrayList<WeakReference<TileVisNode>>();
+
+ /**
+ * @return the WorldCoordinates location of where this node is located
+ */
+ public WorldCoordinates getLocation() {
+ return new WorldCoordinates(this);
+ }
+
+ /**
+ * @return the number of blocks away this node will check for parent nodes to connect to.
+ */
+ public abstract int getRange();
+
+ /**
+ * @return true if this is the source or root node of the vis network.
+ */
+ public abstract boolean isSource();
+
+ /**
+ * This method should never be called directly. Use VisNetHandler.drainVis() instead
+ * @param aspect what aspect to drain
+ * @param vis how much to drain
+ * @return how much was actually drained
+ */
+ public int consumeVis(Aspect aspect, int vis) {
+ if (VisNetHandler.isNodeValid(getParent())) {
+ int out = getParent().get().consumeVis(aspect, vis);
+ if (out>0) {
+ triggerConsumeEffect(aspect);
+ }
+ return out;
+ }
+ return 0;
+ }
+
+ public void removeThisNode() {
+ for (WeakReference<TileVisNode> n:getChildren()) {
+ if (n!=null && n.get()!=null) {
+ n.get().removeThisNode();
+ }
+ }
+
+ children = new ArrayList<WeakReference<TileVisNode>>();
+ if (VisNetHandler.isNodeValid(this.getParent())) {
+ this.getParent().get().nodeRefresh=true;
+ }
+ this.setParent(null);
+ this.parentChanged();
+
+ if (this.isSource()) {
+ HashMap<WorldCoordinates, WeakReference<TileVisNode>> sourcelist = VisNetHandler.sources.get(worldObj.provider.dimensionId);
+ if (sourcelist==null) {
+ sourcelist = new HashMap<WorldCoordinates, WeakReference<TileVisNode>>();
+ }
+ sourcelist.remove(getLocation());
+ VisNetHandler.sources.put( worldObj.provider.dimensionId, sourcelist );
+ }
+
+ worldObj.markBlockForUpdate(xCoord, yCoord, zCoord);
+ }
+
+
+
+ @Override
+ public void invalidate() {
+ removeThisNode();
+ super.invalidate();
+ }
+
+ public void triggerConsumeEffect(Aspect aspect) { }
+
+ /**
+ * @return
+ */
+ public WeakReference<TileVisNode> getParent() {
+ return parent;
+ }
+
+ /**
+ * @return
+ */
+ public WeakReference<TileVisNode> getRootSource() {
+ return VisNetHandler.isNodeValid(getParent()) ?
+ getParent().get().getRootSource() : this.isSource() ?
+ new WeakReference(this) : null;
+ }
+
+ /**
+ * @param parent
+ */
+ public void setParent(WeakReference<TileVisNode> parent) {
+ this.parent = parent;
+ }
+
+ /**
+ * @return
+ */
+ public ArrayList<WeakReference<TileVisNode>> getChildren() {
+ return children;
+ }
+
+ @Override
+ public boolean canUpdate() {
+ return true;
+ }
+
+ protected int nodeCounter = 0;
+ private boolean nodeRegged = false;
+ public boolean nodeRefresh = false;
+
+ @Override
+ public void updateEntity() {
+
+ if (!worldObj.isRemote && ((nodeCounter++) % 40==0 || nodeRefresh)) {
+ //check for changes
+ if (!nodeRefresh && children.size()>0) {
+ for (WeakReference<TileVisNode> n:children) {
+ if (n==null || n.get()==null || !VisNetHandler.canNodeBeSeen(this, n.get())) {
+ nodeRefresh=true;
+ break;
+ }
+ }
+ }
+
+ //refresh linked nodes
+ if (nodeRefresh) {
+ for (WeakReference<TileVisNode> n:children) {
+ if (n.get()!=null) {
+ n.get().nodeRefresh=true;
+ }
+ }
+ children.clear();
+ parent=null;
+ }
+
+ //redo stuff
+ if (isSource() && !nodeRegged) {
+ VisNetHandler.addSource(getWorldObj(), this);
+ nodeRegged = true;
+ } else
+ if (!isSource() && !VisNetHandler.isNodeValid(getParent())) {
+ setParent(VisNetHandler.addNode(getWorldObj(), this));
+ nodeRefresh=true;
+ }
+
+ if (nodeRefresh) {
+ worldObj.markBlockForUpdate(xCoord, yCoord, zCoord);
+ parentChanged();
+ }
+ nodeRefresh=false;
+ }
+
+ }
+
+ public void parentChanged() { }
+
+ /**
+ * @return the type of shard this is attuned to:
+ * none -1, air 0, fire 1, water 2, earth 3, order 4, entropy 5
+ * Should return -1 for most implementations
+ */
+ public byte getAttunement() {
+ return -1;
+ }
+
+
+}
diff --git a/src/api/java/thaumcraft/api/visnet/VisNetHandler.java b/src/api/java/thaumcraft/api/visnet/VisNetHandler.java
new file mode 100644
index 0000000..7ac4c69
--- /dev/null
+++ b/src/api/java/thaumcraft/api/visnet/VisNetHandler.java
@@ -0,0 +1,282 @@
+package thaumcraft.api.visnet;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import net.minecraft.util.MovingObjectPosition;
+import net.minecraft.util.MovingObjectPosition.MovingObjectType;
+import net.minecraft.util.Vec3;
+import net.minecraft.world.World;
+import thaumcraft.api.ThaumcraftApi;
+import thaumcraft.api.ThaumcraftApiHelper;
+import thaumcraft.api.WorldCoordinates;
+import thaumcraft.api.aspects.Aspect;
+
+public class VisNetHandler {
+
+ // NODE DRAINING
+ /**
+ * This method drains vis from a relay or source near the passed in
+ * location. The amount received can be less than the amount requested so
+ * take that into account.
+ *
+ * @param world
+ * @param x the x position of the draining block or entity
+ * @param y the y position of the draining block or entity
+ * @param z the z position of the draining block or entity
+ * @param aspect what aspect to drain
+ * @param amount how much to drain
+ * @return how much was actually drained
+ */
+ public static int drainVis(World world, int x, int y, int z, Aspect aspect, int amount) {
+
+ int drainedAmount = 0;
+
+ WorldCoordinates drainer = new WorldCoordinates(x, y, z,
+ world.provider.dimensionId);
+ if (!nearbyNodes.containsKey(drainer)) {
+ calculateNearbyNodes(world, x, y, z);
+ }
+
+ ArrayList<WeakReference<TileVisNode>> nodes = nearbyNodes.get(drainer);
+ if (nodes!=null && nodes.size()>0)
+ for (WeakReference<TileVisNode> noderef : nodes) {
+
+ TileVisNode node = noderef.get();
+
+ if (node == null) continue;
+
+ int a = node.consumeVis(aspect, amount);
+ drainedAmount += a;
+ amount -= a;
+ if (a>0) {
+ int color = Aspect.getPrimalAspects().indexOf(aspect);
+ generateVisEffect(world.provider.dimensionId, x, y, z, node.xCoord, node.yCoord, node.zCoord, color);
+ }
+ if (amount <= 0) {
+ break;
+ }
+ }
+
+ return drainedAmount;
+ }
+
+ public static void generateVisEffect(int dim, int x, int y, int z, int x2, int y2, int z2, int color) {
+ ThaumcraftApi.internalMethods.generateVisEffect(dim, x, y, z, x2, y2, z2, color);
+ }
+
+ public static HashMap<Integer, HashMap<WorldCoordinates, WeakReference<TileVisNode>>> sources = new HashMap<Integer, HashMap<WorldCoordinates, WeakReference<TileVisNode>>>();
+
+ public static void addSource(World world, TileVisNode vs) {
+ HashMap<WorldCoordinates, WeakReference<TileVisNode>> sourcelist = sources
+ .get(world.provider.dimensionId);
+ if (sourcelist == null) {
+ sourcelist = new HashMap<WorldCoordinates, WeakReference<TileVisNode>>();
+ }
+ sourcelist.put(vs.getLocation(), new WeakReference(vs));
+ sources.put(world.provider.dimensionId, sourcelist);
+ nearbyNodes.clear();
+ }
+
+ public static boolean isNodeValid(WeakReference<TileVisNode> node) {
+ if (node == null || node.get() == null || node.get().isInvalid())
+ return false;
+ return true;
+ }
+
+ public static WeakReference<TileVisNode> addNode(World world, TileVisNode vn) {
+ WeakReference ref = new WeakReference(vn);
+
+ HashMap<WorldCoordinates, WeakReference<TileVisNode>> sourcelist = sources
+ .get(world.provider.dimensionId);
+ if (sourcelist == null) {
+ sourcelist = new HashMap<WorldCoordinates, WeakReference<TileVisNode>>();
+ return null;
+ }
+
+ ArrayList<Object[]> nearby = new ArrayList<Object[]>();
+
+ for (WeakReference<TileVisNode> root : sourcelist.values()) {
+ if (!isNodeValid(root))
+ continue;
+
+ TileVisNode source = root.get();
+
+ float r = inRange(world, vn.getLocation(), source.getLocation(),
+ vn.getRange());
+ if (r > 0) {
+ nearby.add(new Object[] { source, r - vn.getRange() * 2 });
+ }
+
+ nearby = findClosestNodes(vn, source, nearby);
+ cache.clear();
+ }
+
+ float dist = Float.MAX_VALUE;
+ TileVisNode closest = null;
+ if (nearby.size() > 0) {
+ for (Object[] o : nearby) {
+ if ((Float) o[1] < dist &&
+ (vn.getAttunement() == -1 || ((TileVisNode) o[0]).getAttunement() == -1 ||
+ vn.getAttunement() == ((TileVisNode) o[0]).getAttunement())//) {
+ && canNodeBeSeen(vn,(TileVisNode)o[0])) {
+ dist = (Float) o[1];
+ closest = (TileVisNode) o[0];
+ }
+ }
+ }
+ if (closest != null) {
+ closest.getChildren().add(ref);
+ nearbyNodes.clear();
+ return new WeakReference(closest);
+ }
+
+ return null;
+ }
+
+ static ArrayList<WorldCoordinates> cache = new ArrayList<WorldCoordinates>();
+ public static ArrayList<Object[]> findClosestNodes(TileVisNode target,
+ TileVisNode parent, ArrayList<Object[]> in) {
+
+ if (cache.size() > 512 || cache.contains(new WorldCoordinates(parent))) return in;
+ cache.add(new WorldCoordinates(parent));
+
+ for (WeakReference<TileVisNode> childWR : parent.getChildren()) {
+ TileVisNode child = childWR.get();
+
+ if (child != null && !child.equals(target) && !child.equals(parent)) {
+ float r2 = inRange(child.getWorldObj(), child.getLocation(),
+ target.getLocation(), target.getRange());
+ if (r2 > 0) {
+ in.add(new Object[] { child, r2 });
+ }
+
+ in = findClosestNodes(target, child, in);
+ }
+ }
+ return in;
+ }
+
+ private static float inRange(World world, WorldCoordinates cc1,
+ WorldCoordinates cc2, int range) {
+ float distance = cc1.getDistanceSquaredToWorldCoordinates(cc2);
+ return distance > range * range ? -1 : distance;
+ }
+
+ private static HashMap<WorldCoordinates, ArrayList<WeakReference<TileVisNode>>> nearbyNodes = new HashMap<WorldCoordinates, ArrayList<WeakReference<TileVisNode>>>();
+
+ private static void calculateNearbyNodes(World world, int x, int y, int z) {
+
+ HashMap<WorldCoordinates, WeakReference<TileVisNode>> sourcelist = sources
+ .get(world.provider.dimensionId);
+ if (sourcelist == null) {
+ sourcelist = new HashMap<WorldCoordinates, WeakReference<TileVisNode>>();
+ return;
+ }
+
+ ArrayList<WeakReference<TileVisNode>> cn = new ArrayList<WeakReference<TileVisNode>>();
+ WorldCoordinates drainer = new WorldCoordinates(x, y, z,
+ world.provider.dimensionId);
+
+ ArrayList<Object[]> nearby = new ArrayList<Object[]>();
+
+ for (WeakReference<TileVisNode> root : sourcelist.values()) {
+
+ if (!isNodeValid(root))
+ continue;
+
+ TileVisNode source = root.get();
+
+ TileVisNode closest = null;
+ float range = Float.MAX_VALUE;
+
+ float r = inRange(world, drainer, source.getLocation(),
+ source.getRange());
+ if (r > 0) {
+ range = r;
+ closest = source;
+ }
+
+ ArrayList<WeakReference<TileVisNode>> children = new ArrayList<WeakReference<TileVisNode>>();
+ children = getAllChildren(source,children);
+
+ for (WeakReference<TileVisNode> child : children) {
+ TileVisNode n = child.get();
+ if (n != null && !n.equals(root)) {
+
+ float r2 = inRange(n.getWorldObj(), n.getLocation(),
+ drainer, n.getRange());
+ if (r2 > 0 && r2 < range) {
+ range = r2;
+ closest = n;
+ }
+ }
+ }
+
+ if (closest != null) {
+
+ cn.add(new WeakReference(closest));
+ }
+ }
+
+ nearbyNodes.put(drainer, cn);
+ }
+
+ private static ArrayList<WeakReference<TileVisNode>> getAllChildren(TileVisNode source, ArrayList<WeakReference<TileVisNode>> list) {
+ for (WeakReference<TileVisNode> child : source.getChildren()) {
+ TileVisNode n = child.get();
+
+ if (n != null && n.getWorldObj()!=null && isChunkLoaded(n.getWorldObj(), n.xCoord, n.zCoord)) {
+ list.add(child);
+ list = getAllChildren(n,list);
+ }
+ }
+ return list;
+ }
+
+ public static boolean isChunkLoaded(World world, int x, int z) {
+ int xx = x >> 4;
+ int zz = z >> 4;
+ return world.getChunkProvider().chunkExists(xx, zz);
+ }
+
+ public static boolean canNodeBeSeen(TileVisNode source,TileVisNode target)
+ {
+ MovingObjectPosition mop = ThaumcraftApiHelper.rayTraceIgnoringSource(source.getWorldObj(),
+ Vec3.createVectorHelper(source.xCoord+.5, source.yCoord+.5,source.zCoord+.5),
+ Vec3.createVectorHelper(target.xCoord+.5, target.yCoord+.5,target.zCoord+.5),
+ false, true, false);
+ return mop == null || (mop.typeOfHit==MovingObjectType.BLOCK &&
+ mop.blockX==target.xCoord && mop.blockY==target.yCoord && mop.blockZ==target.zCoord);
+ }
+
+ // public static HashMap<WorldCoordinates,WeakReference<TileVisNode>>
+ // noderef = new HashMap<WorldCoordinates,WeakReference<TileVisNode>>();
+ //
+ // public static TileVisNode getClosestNodeWithinRadius(World world, int x,
+ // int y, int z, int radius) {
+ // TileVisNode out = null;
+ // WorldCoordinates wc = null;
+ // float cd = Float.MAX_VALUE;
+ // for (int sx = x - radius; sx <= x + radius; sx++) {
+ // for (int sy = y - radius; sy <= y + radius; sy++) {
+ // for (int sz = z - radius; sz <= z + radius; sz++) {
+ // wc = new WorldCoordinates(sx,sy,sz,world.provider.dimensionId);
+ // if (noderef.containsKey(wc)) {
+ // float d = wc.getDistanceSquared(x, y, z);
+ // if (d<radius*radius && noderef.get(wc).get()!=null &&
+ // !noderef.get(wc).get().isReceiver() &&
+ // isNodeValid(noderef.get(wc).get().getParent())
+ // ) {
+ // out = noderef.get(wc).get();
+ // cd = d;
+ // }
+ // }
+ // }
+ // }
+ // }
+ // return out;
+ // }
+
+}
diff --git a/src/api/java/thaumcraft/api/wands/FocusUpgradeType.java b/src/api/java/thaumcraft/api/wands/FocusUpgradeType.java
new file mode 100644
index 0000000..5c4269e
--- /dev/null
+++ b/src/api/java/thaumcraft/api/wands/FocusUpgradeType.java
@@ -0,0 +1,114 @@
+package thaumcraft.api.wands;
+
+import net.minecraft.util.ResourceLocation;
+import net.minecraft.util.StatCollector;
+
+import org.apache.logging.log4j.LogManager;
+
+import thaumcraft.api.aspects.Aspect;
+import thaumcraft.api.aspects.AspectList;
+
+public class FocusUpgradeType {
+
+ public static FocusUpgradeType[] types = new FocusUpgradeType[20];
+
+ public short id;
+
+ public ResourceLocation icon;
+
+ public String name;
+
+ public String text;
+
+ /**
+ * What aspects are used to calculate the cost of this upgrade. The amounts given is ignored, just the type is used for the calculation.
+ */
+ public AspectList aspects;
+
+
+
+ public FocusUpgradeType(int id, ResourceLocation icon, String name, String text, AspectList aspects) {
+ this.id = (short) id;
+ this.icon = icon;
+ this.name = name;
+ this.text = text;
+ this.aspects = aspects;
+
+ if (id<types.length && types[id]!=null) {
+ LogManager.getLogger("THAUMCRAFT").fatal("Focus Upgrade id "+id+" already occupied. Ignoring.");
+ return;
+ }
+
+ // allocate space
+ if (id>=types.length) {
+ FocusUpgradeType[] temp = new FocusUpgradeType[id+1];
+ System.arraycopy(types, 0, temp, 0, types.length);
+ types = temp;
+ }
+
+ types[id] = this;
+ }
+
+ public String getLocalizedName() {
+ return StatCollector.translateToLocal(name);
+ }
+
+ public String getLocalizedText() {
+ return StatCollector.translateToLocal(text);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof FocusUpgradeType) {
+ return this.id == ((FocusUpgradeType)obj).id;
+ } else return false;
+ }
+
+ // basic upgrade types
+ public static FocusUpgradeType potency = new FocusUpgradeType( 0,
+ new ResourceLocation("thaumcraft", "textures/foci/potency.png"),
+ "focus.upgrade.potency.name","focus.upgrade.potency.text",
+ new AspectList().add(Aspect.WEAPON,1));
+
+ public static FocusUpgradeType frugal = new FocusUpgradeType( 1,
+ new ResourceLocation("thaumcraft", "textures/foci/frugal.png"),
+ "focus.upgrade.frugal.name","focus.upgrade.frugal.text",
+ new AspectList().add(Aspect.HUNGER,1));
+
+ public static FocusUpgradeType treasure = new FocusUpgradeType( 2,
+ new ResourceLocation("thaumcraft", "textures/foci/treasure.png"),
+ "focus.upgrade.treasure.name","focus.upgrade.treasure.text",
+ new AspectList().add(Aspect.GREED,1));
+
+ public static FocusUpgradeType enlarge = new FocusUpgradeType( 3,
+ new ResourceLocation("thaumcraft", "textures/foci/enlarge.png"),
+ "focus.upgrade.enlarge.name","focus.upgrade.enlarge.text",
+ new AspectList().add(Aspect.TRAVEL,1));
+
+ public static FocusUpgradeType alchemistsfire = new FocusUpgradeType( 4,
+ new ResourceLocation("thaumcraft", "textures/foci/alchemistsfire.png"),
+ "focus.upgrade.alchemistsfire.name","focus.upgrade.alchemistsfire.text",
+ new AspectList().add(Aspect.ENERGY,1).add(Aspect.SLIME,1));
+
+ public static FocusUpgradeType alchemistsfrost = new FocusUpgradeType( 5,
+ new ResourceLocation("thaumcraft", "textures/foci/alchemistsfrost.png"),
+ "focus.upgrade.alchemistsfrost.name","focus.upgrade.alchemistsfrost.text",
+ new AspectList().add(Aspect.COLD,1).add(Aspect.TRAP,1));
+
+ public static FocusUpgradeType architect = new FocusUpgradeType( 6,
+ new ResourceLocation("thaumcraft", "textures/foci/architect.png"),
+ "focus.upgrade.architect.name","focus.upgrade.architect.text",
+ new AspectList().add(Aspect.CRAFT,1));
+
+ public static FocusUpgradeType extend = new FocusUpgradeType( 7,
+ new ResourceLocation("thaumcraft", "textures/foci/extend.png"),
+ "focus.upgrade.extend.name","focus.upgrade.extend.text",
+ new AspectList().add(Aspect.EXCHANGE,1));
+
+ public static FocusUpgradeType silktouch = new FocusUpgradeType( 8,
+ new ResourceLocation("thaumcraft", "textures/foci/silktouch.png"),
+ "focus.upgrade.silktouch.name","focus.upgrade.silktouch.text",
+ new AspectList().add(Aspect.GREED,1));
+
+
+}
diff --git a/src/api/java/thaumcraft/api/wands/IWandRodOnUpdate.java b/src/api/java/thaumcraft/api/wands/IWandRodOnUpdate.java
new file mode 100644
index 0000000..4ef8c84
--- /dev/null
+++ b/src/api/java/thaumcraft/api/wands/IWandRodOnUpdate.java
@@ -0,0 +1,16 @@
+package thaumcraft.api.wands;
+
+import net.minecraft.entity.player.EntityPlayer;
+import net.minecraft.item.ItemStack;
+
+/**
+ *
+ * @author azanor
+ *
+ * Implemented by a class that you wish to be called whenever a wand with this rod performs its
+ * update tick.
+ *
+ */
+public interface IWandRodOnUpdate {
+ void onUpdate(ItemStack itemstack, EntityPlayer player);
+}
diff --git a/src/api/java/thaumcraft/api/wands/IWandTriggerManager.java b/src/api/java/thaumcraft/api/wands/IWandTriggerManager.java
new file mode 100644
index 0000000..7c299de
--- /dev/null
+++ b/src/api/java/thaumcraft/api/wands/IWandTriggerManager.java
@@ -0,0 +1,15 @@
+package thaumcraft.api.wands;
+
+import net.minecraft.entity.player.EntityPlayer;
+import net.minecraft.item.ItemStack;
+import net.minecraft.world.World;
+
+public interface IWandTriggerManager {
+
+ /**
+ * This class will be called by wands with the proper parameters. It is up to you to decide what to do with them.
+ */
+ public boolean performTrigger(World world, ItemStack wand, EntityPlayer player,
+ int x, int y, int z, int side, int event);
+
+}
diff --git a/src/api/java/thaumcraft/api/wands/IWandable.java b/src/api/java/thaumcraft/api/wands/IWandable.java
new file mode 100644
index 0000000..aeb9bac
--- /dev/null
+++ b/src/api/java/thaumcraft/api/wands/IWandable.java
@@ -0,0 +1,25 @@
+package thaumcraft.api.wands;
+
+import net.minecraft.entity.player.EntityPlayer;
+import net.minecraft.item.ItemStack;
+import net.minecraft.world.World;
+
+/**
+ *
+ * @author azanor
+ *
+ * Add this to a tile entity that you wish wands to interact with in some way.
+ *
+ */
+
+public interface IWandable {
+
+ public int onWandRightClick(World world, ItemStack wandstack, EntityPlayer player, int x, int y, int z, int side, int md);
+
+ public ItemStack onWandRightClick(World world, ItemStack wandstack, EntityPlayer player);
+
+ public void onUsingWandTick(ItemStack wandstack, EntityPlayer player, int count);
+
+ public void onWandStoppedUsing(ItemStack wandstack, World world, EntityPlayer player, int count);
+
+}
diff --git a/src/api/java/thaumcraft/api/wands/ItemFocusBasic.java b/src/api/java/thaumcraft/api/wands/ItemFocusBasic.java
new file mode 100644
index 0000000..e8938e9
--- /dev/null
+++ b/src/api/java/thaumcraft/api/wands/ItemFocusBasic.java
@@ -0,0 +1,276 @@
+package thaumcraft.api.wands;
+
+import java.text.DecimalFormat;
+import java.util.LinkedHashMap;
+import java.util.List;
+
+import net.minecraft.entity.Entity;
+import net.minecraft.entity.player.EntityPlayer;
+import net.minecraft.item.EnumRarity;
+import net.minecraft.item.Item;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.NBTTagCompound;
+import net.minecraft.nbt.NBTTagList;
+import net.minecraft.util.EnumChatFormatting;
+import net.minecraft.util.IIcon;
+import net.minecraft.util.MovingObjectPosition;
+import net.minecraft.util.StatCollector;
+import net.minecraft.world.World;
+import thaumcraft.api.aspects.Aspect;
+import thaumcraft.api.aspects.AspectList;
+import cpw.mods.fml.relauncher.Side;
+import cpw.mods.fml.relauncher.SideOnly;
+
+public class ItemFocusBasic extends Item {
+
+ public ItemFocusBasic ()
+ {
+ super();
+ maxStackSize = 1;
+ canRepair=false;
+ this.setMaxDamage(0);
+ }
+
+ public IIcon icon;
+
+ @SideOnly(Side.CLIENT)
+ @Override
+ public IIcon getIconFromDamage(int par1) {
+ return icon;
+ }
+
+ @Override
+ public boolean isDamageable() {
+ return false;
+ }
+
+ @Override
+ public void addInformation(ItemStack stack,EntityPlayer player, List list, boolean par4) {
+ AspectList al = this.getVisCost(stack);
+ if (al!=null && al.size()>0) {
+ list.add(StatCollector.translateToLocal(isVisCostPerTick(stack)?"item.Focus.cost2":"item.Focus.cost1"));
+ for (Aspect aspect:al.getAspectsSorted()) {
+ DecimalFormat myFormatter = new DecimalFormat("#####.##");
+ String amount = myFormatter.format(al.getAmount(aspect)/100f);
+ list.add(" \u00A7"+aspect.getChatcolor()+aspect.getName()+"\u00A7r x "+ amount);
+ }
+ }
+ addFocusInformation(stack,player,list,par4);
+ }
+
+ public void addFocusInformation(ItemStack focusstack,EntityPlayer player, List list, boolean par4) {
+ LinkedHashMap<Short, Integer> map = new LinkedHashMap<Short, Integer>();
+ for (short id:this.getAppliedUpgrades(focusstack)) {
+ if (id>=0) {
+ int amt = 1;
+ if (map.containsKey(id)) {
+ amt = map.get(id) + 1;
+ }
+ map.put(id, amt);
+ }
+ }
+ for (Short id:map.keySet()) {
+ list.add(EnumChatFormatting.DARK_PURPLE +FocusUpgradeType.types[id].getLocalizedName()+
+ (map.get(id)>1?" "+StatCollector.translateToLocal("enchantment.level." + map.get(id)):""));
+ }
+ }
+
+ /**
+ * Purely for display on the focus tooltip (see addInformation method above)
+ */
+ public boolean isVisCostPerTick(ItemStack focusstack) {
+ return false;
+ }
+
+ @Override
+ public EnumRarity getRarity(ItemStack focusstack)
+ {
+ return EnumRarity.rare;
+ }
+
+ /**
+ * What color will the focus orb be rendered on the held wand
+ */
+ public int getFocusColor(ItemStack focusstack) {
+ return 0;
+ }
+
+
+ /**
+ * Does the focus have ornamentation like the focus of the nine hells. Ornamentation is a standard icon rendered in a cross around the focus
+ */
+ public IIcon getOrnament(ItemStack focusstack) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ /**
+ * An icon to be rendered inside the focus itself
+ */
+ public IIcon getFocusDepthLayerIcon(ItemStack focusstack) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ public enum WandFocusAnimation {
+ WAVE, CHARGE;
+ }
+
+ public WandFocusAnimation getAnimation(ItemStack focusstack) {
+ return WandFocusAnimation.WAVE;
+ }
+
+ /**
+ * Just insert two alphanumeric characters before this string in your focus item class
+ */
+ public String getSortingHelper(ItemStack focusstack) {
+ String out="";
+ for (short id:this.getAppliedUpgrades(focusstack)) {
+ out = out + id;
+ }
+ return out;
+ }
+
+
+ /**
+ * How much vis does this focus consume per activation.
+ */
+ public AspectList getVisCost(ItemStack focusstack) {
+ return null;
+ }
+
+ /**
+ * This returns how many milliseconds must pass before the focus can be activated again.
+ */
+ public int getActivationCooldown(ItemStack focusstack) {
+ return 0;
+ }
+
+ /**
+ * Used by foci like equal trade to determine their area in artchitect mode
+ */
+ public int getMaxAreaSize(ItemStack focusstack) {
+ return 1;
+ }
+
+ /**
+ * What upgrades can be applied to this focus for ranks 1 to 5
+ */
+ public FocusUpgradeType[] getPossibleUpgradesByRank(ItemStack focusstack, int rank) {
+ return null;
+ }
+
+ /**
+ * What upgrades does the focus currently have
+ */
+ public short[] getAppliedUpgrades(ItemStack focusstack) {
+ short[] l = new short[] {-1,-1,-1,-1,-1};
+ NBTTagList nbttaglist = getFocusUpgradeTagList(focusstack);
+ if (nbttaglist == null)
+ {
+ return l;
+ }
+ else
+ {
+ for (int j = 0; j < nbttaglist.tagCount(); ++j)
+ {
+ if (j>=5) break;
+ l[j] = nbttaglist.getCompoundTagAt(j).getShort("id");
+ }
+
+ return l;
+ }
+ }
+
+ public boolean applyUpgrade(ItemStack focusstack, FocusUpgradeType type, int rank) {
+ short[] upgrades = getAppliedUpgrades(focusstack);
+ if (upgrades[rank-1]!=-1 || rank<1 || rank>5) {
+ return false;
+ }
+ upgrades[rank-1] = type.id;
+ setFocusUpgradeTagList(focusstack, upgrades);
+ return true;
+ }
+
+ /**
+ * Use this method to define custom logic about which upgrades can be applied. This can be used to set up upgrade "trees"
+ * that make certain upgrades available only when others are unlocked first, when certain research is completed, or similar logic.
+ *
+ */
+ public boolean canApplyUpgrade(ItemStack focusstack, EntityPlayer player, FocusUpgradeType type, int rank) {
+ return true;
+ }
+
+ /**
+ * Does this focus have the passed upgrade type
+ */
+ public boolean isUpgradedWith(ItemStack focusstack, FocusUpgradeType focusUpgradetype) {
+ return getUpgradeLevel(focusstack,focusUpgradetype)>0;
+ }
+
+ /**
+ * What level is the passed upgrade type on the focus. If it is not present it returns 0
+ */
+ public int getUpgradeLevel(ItemStack focusstack, FocusUpgradeType focusUpgradetype) {
+ short[] list = getAppliedUpgrades(focusstack);
+ int level=0;
+ for (short id:list) {
+ if (id == focusUpgradetype.id)
+ {
+ level++;
+ }
+ }
+ return level;
+ }
+
+ public ItemStack onFocusRightClick(ItemStack wandstack, World world,EntityPlayer player, MovingObjectPosition movingobjectposition) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ public void onUsingFocusTick(ItemStack wandstack, EntityPlayer player,int count) {
+ // TODO Auto-generated method stub
+ }
+
+ public void onPlayerStoppedUsingFocus(ItemStack wandstack, World world, EntityPlayer player, int count) {
+ // TODO Auto-generated method stub
+
+ }
+
+ public boolean onFocusBlockStartBreak(ItemStack wandstack, int x, int y,int z, EntityPlayer player) {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ /**
+ * Internal helper methods
+ */
+ private NBTTagList getFocusUpgradeTagList(ItemStack focusstack)
+ {
+ return focusstack.stackTagCompound == null ? null : focusstack.stackTagCompound.getTagList("upgrade", 10);
+ }
+
+ private void setFocusUpgradeTagList(ItemStack focusstack, short[] upgrades) {
+ if (!focusstack.hasTagCompound())
+ focusstack.setTagCompound(new NBTTagCompound());
+ NBTTagCompound nbttagcompound = focusstack.getTagCompound();
+ NBTTagList tlist = new NBTTagList();
+ nbttagcompound.setTag("upgrade", tlist);
+ for (short id : upgrades) {
+ NBTTagCompound f = new NBTTagCompound();
+ f.setShort("id", id);
+ tlist.appendTag(f);
+ }
+ }
+
+ @Override
+ public void onUpdate(ItemStack stack, World world, Entity entity, int p_77663_4_, boolean p_77663_5_) {
+ if (stack.stackTagCompound !=null && stack.stackTagCompound.hasKey("ench")) {
+ stack.stackTagCompound.removeTag("ench");
+ }
+ super.onUpdate(stack, world, entity, p_77663_4_, p_77663_5_);
+ }
+
+
+
+}
diff --git a/src/api/java/thaumcraft/api/wands/StaffRod.java b/src/api/java/thaumcraft/api/wands/StaffRod.java
new file mode 100644
index 0000000..e7ae90f
--- /dev/null
+++ b/src/api/java/thaumcraft/api/wands/StaffRod.java
@@ -0,0 +1,48 @@
+package thaumcraft.api.wands;
+
+import net.minecraft.item.ItemStack;
+import net.minecraft.util.ResourceLocation;
+
+/**
+ *
+ * @author Azanor
+ *
+ * This class is used to keep the material information for the various rods.
+ * It is also used to generate the wand recipes ingame.
+ *
+ */
+public class StaffRod extends WandRod {
+
+ boolean runes=false;
+
+ public StaffRod(String tag, int capacity, ItemStack item, int craftCost) {
+ super(tag+"_staff", capacity, item, craftCost);
+ this.texture = new ResourceLocation("thaumcraft","textures/models/wand_rod_"+tag+".png");
+ }
+
+ public StaffRod(String tag, int capacity, ItemStack item, int craftCost,
+ IWandRodOnUpdate onUpdate, ResourceLocation texture) {
+ super(tag+"_staff", capacity, item, craftCost, onUpdate, texture);
+ }
+
+ public StaffRod(String tag, int capacity, ItemStack item, int craftCost,
+ IWandRodOnUpdate onUpdate) {
+ super(tag+"_staff", capacity, item, craftCost, onUpdate);
+ this.texture = new ResourceLocation("thaumcraft","textures/models/wand_rod_"+tag+".png");
+ }
+
+ public StaffRod(String tag, int capacity, ItemStack item, int craftCost,
+ ResourceLocation texture) {
+ super(tag+"_staff", capacity, item, craftCost, texture);
+ }
+
+ public boolean hasRunes() {
+ return runes;
+ }
+
+ public void setRunes(boolean hasRunes) {
+ this.runes = hasRunes;
+ }
+
+
+}
diff --git a/src/api/java/thaumcraft/api/wands/WandCap.java b/src/api/java/thaumcraft/api/wands/WandCap.java
new file mode 100644
index 0000000..17b4581
--- /dev/null
+++ b/src/api/java/thaumcraft/api/wands/WandCap.java
@@ -0,0 +1,129 @@
+package thaumcraft.api.wands;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+
+import net.minecraft.item.ItemStack;
+import net.minecraft.util.ResourceLocation;
+import thaumcraft.api.aspects.Aspect;
+
+/**
+ * This class is used to keep the material information for the various caps.
+ * It is also used to generate the wand recipes ingame.
+ * @author Azanor
+ *
+ */
+public class WandCap {
+
+ private String tag;
+
+ /**
+ * Cost to craft this wand. Combined with the rod cost.
+ */
+ private int craftCost;
+
+ /**
+ * the amount by which all aspect costs are multiplied
+ */
+ float baseCostModifier;
+
+ /**
+ * specifies a list of primal aspects that use the special discount figure instead of the normal discount.
+ */
+ List<Aspect> specialCostModifierAspects;
+
+ /**
+ * the amount by which the specified aspect costs are multiplied
+ */
+ float specialCostModifier;
+
+ /**
+ * The texture that will be used for the ingame wand cap
+ */
+ ResourceLocation texture;
+
+ /**
+ * the actual item that makes up this cap and will be used to generate the wand recipes
+ */
+ ItemStack item;
+
+ public static LinkedHashMap<String,WandCap> caps = new LinkedHashMap<String,WandCap>();
+
+ public WandCap (String tag, float discount, ItemStack item, int craftCost) {
+ this.setTag(tag);
+ this.baseCostModifier = discount;
+ this.specialCostModifierAspects = null;
+ texture = new ResourceLocation("thaumcraft","textures/models/wand_cap_"+getTag()+".png");
+ this.item=item;
+ this.setCraftCost(craftCost);
+ caps.put(tag, this);
+ }
+
+ public WandCap (String tag, float discount, List<Aspect> specialAspects, float discountSpecial, ItemStack item, int craftCost) {
+ this.setTag(tag);
+ this.baseCostModifier = discount;
+ this.specialCostModifierAspects = specialAspects;
+ this.specialCostModifier = discountSpecial;
+ texture = new ResourceLocation("thaumcraft","textures/models/wand_cap_"+getTag()+".png");
+ this.item=item;
+ this.setCraftCost(craftCost);
+ caps.put(tag, this);
+ }
+
+ public float getBaseCostModifier() {
+ return baseCostModifier;
+ }
+
+ public List<Aspect> getSpecialCostModifierAspects() {
+ return specialCostModifierAspects;
+ }
+
+ public float getSpecialCostModifier() {
+ return specialCostModifier;
+ }
+
+ public ResourceLocation getTexture() {
+ return texture;
+ }
+
+ public void setTexture(ResourceLocation texture) {
+ this.texture = texture;
+ }
+
+ public String getTag() {
+ return tag;
+ }
+
+ public void setTag(String tag) {
+ this.tag = tag;
+ }
+
+
+ public ItemStack getItem() {
+ return item;
+ }
+
+ public void setItem(ItemStack item) {
+ this.item = item;
+ }
+
+ public int getCraftCost() {
+ return craftCost;
+ }
+
+ public void setCraftCost(int craftCost) {
+ this.craftCost = craftCost;
+ }
+
+ /**
+ * The research a player needs to have finished to be able to craft a wand with this cap.
+ */
+ public String getResearch() {
+ return "CAP_"+getTag();
+ }
+
+ // Some examples:
+ // WandCap WAND_CAP_IRON = new WandCap("iron", 1.1f, Arrays.asList(Aspect.ORDER),1, new ItemStack(ConfigItems.itemWandCap,1,0),1);
+ // WandCap WAND_CAP_GOLD = new WandCap("gold", 1f, new ItemStack(ConfigItems.itemWandCap,1,1),3);
+
+}
diff --git a/src/api/java/thaumcraft/api/wands/WandRod.java b/src/api/java/thaumcraft/api/wands/WandRod.java
new file mode 100644
index 0000000..85954e4
--- /dev/null
+++ b/src/api/java/thaumcraft/api/wands/WandRod.java
@@ -0,0 +1,158 @@
+package thaumcraft.api.wands;
+
+import java.util.LinkedHashMap;
+
+import net.minecraft.item.ItemStack;
+import net.minecraft.util.ResourceLocation;
+
+/**
+ *
+ * @author Azanor
+ *
+ * This class is used to keep the material information for the various rods.
+ * It is also used to generate the wand recipes ingame.
+ *
+ */
+public class WandRod {
+
+
+ private String tag;
+
+ /**
+ * Cost to craft this wand. Combined with the rod cost.
+ */
+ private int craftCost;
+
+ /**
+ * The amount of vis that can be stored - this number is actually multiplied
+ * by 100 for use by the wands internals
+ */
+ int capacity;
+
+ /**
+ * The texture that will be used for the ingame wand rod
+ */
+ protected ResourceLocation texture;
+
+ /**
+ * the actual item that makes up this rod and will be used to generate the wand recipes
+ */
+ ItemStack item;
+
+ /**
+ * A class that will be called whenever the wand onUpdate tick is run
+ */
+ IWandRodOnUpdate onUpdate;
+
+ /**
+ * Does the rod glow in the dark?
+ */
+ boolean glow;
+
+ public static LinkedHashMap<String,WandRod> rods = new LinkedHashMap<String,WandRod>();
+
+ public WandRod (String tag, int capacity, ItemStack item, int craftCost, ResourceLocation texture) {
+ this.setTag(tag);
+ this.capacity = capacity;
+ this.texture = texture;
+ this.item=item;
+ this.setCraftCost(craftCost);
+ rods.put(tag, this);
+ }
+
+ public WandRod (String tag, int capacity, ItemStack item, int craftCost, IWandRodOnUpdate onUpdate, ResourceLocation texture) {
+ this.setTag(tag);
+ this.capacity = capacity;
+ this.texture = texture;
+ this.item=item;
+ this.setCraftCost(craftCost);
+ rods.put(tag, this);
+ this.onUpdate = onUpdate;
+ }
+
+ public WandRod (String tag, int capacity, ItemStack item, int craftCost) {
+ this.setTag(tag);
+ this.capacity = capacity;
+ this.texture = new ResourceLocation("thaumcraft","textures/models/wand_rod_"+getTag()+".png");
+ this.item=item;
+ this.setCraftCost(craftCost);
+ rods.put(tag, this);
+ }
+
+ public WandRod (String tag, int capacity, ItemStack item, int craftCost, IWandRodOnUpdate onUpdate) {
+ this.setTag(tag);
+ this.capacity = capacity;
+ this.texture = new ResourceLocation("thaumcraft","textures/models/wand_rod_"+getTag()+".png");
+ this.item=item;
+ this.setCraftCost(craftCost);
+ rods.put(tag, this);
+ this.onUpdate = onUpdate;
+ }
+
+ public String getTag() {
+ return tag;
+ }
+
+ public void setTag(String tag) {
+ this.tag = tag;
+ }
+
+ public int getCapacity() {
+ return capacity;
+ }
+
+ public void setCapacity(int capacity) {
+ this.capacity = capacity;
+ }
+
+ public ResourceLocation getTexture() {
+ return texture;
+ }
+
+ public void setTexture(ResourceLocation texture) {
+ this.texture = texture;
+ }
+
+ public ItemStack getItem() {
+ return item;
+ }
+
+ public void setItem(ItemStack item) {
+ this.item = item;
+ }
+
+ public int getCraftCost() {
+ return craftCost;
+ }
+
+ public void setCraftCost(int craftCost) {
+ this.craftCost = craftCost;
+ }
+
+ public IWandRodOnUpdate getOnUpdate() {
+ return onUpdate;
+ }
+
+ public void setOnUpdate(IWandRodOnUpdate onUpdate) {
+ this.onUpdate = onUpdate;
+ }
+
+ public boolean isGlowing() {
+ return glow;
+ }
+
+ public void setGlowing(boolean hasGlow) {
+ this.glow = hasGlow;
+ }
+
+ /**
+ * The research a player needs to have finished to be able to craft a wand with this rod.
+ */
+ public String getResearch() {
+ return "ROD_"+getTag();
+ }
+
+ // Some examples:
+ // WandRod WAND_ROD_WOOD = new WandRod("wood",25,new ItemStack(Item.stick),1);
+ // WandRod WAND_ROD_BLAZE = new WandRod("blaze",100,new ItemStack(Item.blazeRod),7,new WandRodBlazeOnUpdate());
+}
diff --git a/src/api/java/thaumcraft/api/wands/WandTriggerRegistry.java b/src/api/java/thaumcraft/api/wands/WandTriggerRegistry.java
new file mode 100644
index 0000000..7224e12
--- /dev/null
+++ b/src/api/java/thaumcraft/api/wands/WandTriggerRegistry.java
@@ -0,0 +1,126 @@
+package thaumcraft.api.wands;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+
+import net.minecraft.block.Block;
+import net.minecraft.entity.player.EntityPlayer;
+import net.minecraft.item.ItemStack;
+import net.minecraft.world.World;
+
+/**
+ * This class serves a similar function to IWandable in that it allows wands to interact
+ * with object in the world. In this case it is most useful for adding interaction with non-mod
+ * blocks where you can't control what happens in their code.
+ * Example where it is used is in crafting the thaumonomicon from a bookshelf and the
+ * crucible from a cauldron
+ *
+ * @author azanor
+ *
+ */
+public class WandTriggerRegistry {
+
+ private static HashMap<String,HashMap<List,List>> triggers = new HashMap<String,HashMap<List,List>>();
+ private static final String DEFAULT = "default";
+
+ /**
+ * Registers an action to perform when a casting wand right clicks on a specific block.
+ * A manager class needs to be created that implements IWandTriggerManager.
+ * @param manager
+ * @param event a logical number that you can use to differentiate different events or actions
+ * @param block
+ * @param meta send -1 as a wildcard value for all possible meta values
+ * @param modid a unique identifier. It is best to register your own triggers using your mod id to avoid conflicts with mods that register triggers for the same block
+ */
+ public static void registerWandBlockTrigger(IWandTriggerManager manager, int event, Block block, int meta, String modid) {
+ if (!triggers.containsKey(modid)) {
+ triggers.put(modid, new HashMap<List,List>());
+ }
+ HashMap<List,List> temp = triggers.get(modid);
+ temp.put(Arrays.asList(block,meta),Arrays.asList(manager,event));
+ triggers.put(modid, temp);
+ }
+
+ /**
+ * for legacy support
+ */
+ public static void registerWandBlockTrigger(IWandTriggerManager manager, int event, Block block, int meta) {
+ registerWandBlockTrigger(manager, event, block, meta, DEFAULT);
+ }
+
+ /**
+ * Checks all trigger registries if one exists for the given block and meta
+ * @param block
+ * @param meta
+ * @return
+ */
+ public static boolean hasTrigger(Block block, int meta) {
+ for (String modid:triggers.keySet()) {
+ HashMap<List,List> temp = triggers.get(modid);
+ if (temp.containsKey(Arrays.asList(block,meta)) ||
+ temp.containsKey(Arrays.asList(block,-1))) return true;
+ }
+ return false;
+ }
+
+ /**
+ * modid sensitive version
+ */
+ public static boolean hasTrigger(Block block, int meta, String modid) {
+ if (!triggers.containsKey(modid)) return false;
+ HashMap<List,List> temp = triggers.get(modid);
+ if (temp.containsKey(Arrays.asList(block,meta)) ||
+ temp.containsKey(Arrays.asList(block,-1))) return true;
+ return false;
+ }
+
+
+ /**
+ * This is called by the onItemUseFirst function in wands.
+ * Parameters and return value functions like you would expect for that function.
+ * @param world
+ * @param wand
+ * @param player
+ * @param x
+ * @param y
+ * @param z
+ * @param side
+ * @param block
+ * @param meta
+ * @return
+ */
+ public static boolean performTrigger(World world, ItemStack wand, EntityPlayer player,
+ int x, int y, int z, int side, Block block, int meta) {
+
+ for (String modid:triggers.keySet()) {
+ HashMap<List,List> temp = triggers.get(modid);
+ List l = temp.get(Arrays.asList(block,meta));
+ if (l==null) l = temp.get(Arrays.asList(block,-1));
+ if (l==null) continue;
+
+ IWandTriggerManager manager = (IWandTriggerManager) l.get(0);
+ int event = (Integer) l.get(1);
+ boolean result = manager.performTrigger(world, wand, player, x, y, z, side, event);
+ if (result) return true;
+ }
+ return false;
+ }
+
+ /**
+ * modid sensitive version
+ */
+ public static boolean performTrigger(World world, ItemStack wand, EntityPlayer player,
+ int x, int y, int z, int side, Block block, int meta, String modid) {
+ if (!triggers.containsKey(modid)) return false;
+ HashMap<List,List> temp = triggers.get(modid);
+ List l = temp.get(Arrays.asList(block,meta));
+ if (l==null) l = temp.get(Arrays.asList(block,-1));
+ if (l==null) return false;
+
+ IWandTriggerManager manager = (IWandTriggerManager) l.get(0);
+ int event = (Integer) l.get(1);
+ return manager.performTrigger(world, wand, player, x, y, z, side, event);
+ }
+
+}