diff --git a/src/main/java/net/fuzzycraft/botanichorizons/addons/BHBlocks.java b/src/main/java/net/fuzzycraft/botanichorizons/addons/BHBlocks.java index dd8941f..66ab7b2 100644 --- a/src/main/java/net/fuzzycraft/botanichorizons/addons/BHBlocks.java +++ b/src/main/java/net/fuzzycraft/botanichorizons/addons/BHBlocks.java @@ -3,12 +3,18 @@ import cpw.mods.fml.common.registry.GameRegistry; import net.fuzzycraft.botanichorizons.addons.block.BlockAdvancedAlchemyPool; import net.fuzzycraft.botanichorizons.addons.block.BlockAdvancedAlfPortal; +import net.fuzzycraft.botanichorizons.addons.block.BlockAdvancedAltar; +import net.fuzzycraft.botanichorizons.addons.block.BlockAdvancedApothecary; import net.fuzzycraft.botanichorizons.addons.block.BlockAdvancedConjurationPool; import net.fuzzycraft.botanichorizons.addons.block.BlockAdvancedCraftingPool; +import net.fuzzycraft.botanichorizons.addons.block.BlockAdvancedTerraPlate; import net.fuzzycraft.botanichorizons.addons.tileentity.TileAdvancedAlchemyPool; import net.fuzzycraft.botanichorizons.addons.tileentity.TileAdvancedAlfPortal; +import net.fuzzycraft.botanichorizons.addons.tileentity.TileAdvancedAltar; +import net.fuzzycraft.botanichorizons.addons.tileentity.TileAdvancedApothecary; import net.fuzzycraft.botanichorizons.addons.tileentity.TileAdvancedConjurationPool; import net.fuzzycraft.botanichorizons.addons.tileentity.TileAdvancedCraftingPool; +import net.fuzzycraft.botanichorizons.addons.tileentity.TileAdvancedTerraPlate; import net.fuzzycraft.botanichorizons.mod.ForgeMod; import net.minecraft.tileentity.TileEntity; @@ -17,20 +23,29 @@ public final class BHBlocks { public static BlockAdvancedAlchemyPool autoPoolAlchemy; public static BlockAdvancedConjurationPool autoPoolConjuration; public static BlockAdvancedAlfPortal autoPortal; + public static BlockAdvancedAltar autoAltar; + public static BlockAdvancedTerraPlate autoPlate; + public static BlockAdvancedApothecary autoApothecary; public static void initBlocks() { + autoApothecary = new BlockAdvancedApothecary(); autoPoolInfusion = new BlockAdvancedCraftingPool(); autoPoolAlchemy = new BlockAdvancedAlchemyPool(); autoPoolConjuration = new BlockAdvancedConjurationPool(); + autoAltar = new BlockAdvancedAltar(); + autoPlate = new BlockAdvancedTerraPlate(); autoPortal = new BlockAdvancedAlfPortal(); registerTileEntities(); } public static void registerTileEntities() { + registerTile(TileAdvancedApothecary.class, BlockAdvancedApothecary.NAME); registerTile(TileAdvancedCraftingPool.class, BlockAdvancedCraftingPool.NAME); registerTile(TileAdvancedAlchemyPool.class, BlockAdvancedAlchemyPool.NAME); registerTile(TileAdvancedConjurationPool.class, BlockAdvancedConjurationPool.NAME); + registerTile(TileAdvancedAltar.class, BlockAdvancedAltar.NAME); + registerTile(TileAdvancedTerraPlate.class, BlockAdvancedTerraPlate.NAME); registerTile(TileAdvancedAlfPortal.class, BlockAdvancedAlfPortal.NAME); } diff --git a/src/main/java/net/fuzzycraft/botanichorizons/addons/Multiblocks.java b/src/main/java/net/fuzzycraft/botanichorizons/addons/Multiblocks.java index ceebf8a..8e5a2cd 100644 --- a/src/main/java/net/fuzzycraft/botanichorizons/addons/Multiblocks.java +++ b/src/main/java/net/fuzzycraft/botanichorizons/addons/Multiblocks.java @@ -3,8 +3,11 @@ import gregtech.api.enums.Mods; import net.fuzzycraft.botanichorizons.addons.tileentity.TileAdvancedAlchemyPool; import net.fuzzycraft.botanichorizons.addons.tileentity.TileAdvancedAlfPortal; +import net.fuzzycraft.botanichorizons.addons.tileentity.TileAdvancedAltar; +import net.fuzzycraft.botanichorizons.addons.tileentity.TileAdvancedApothecary; import net.fuzzycraft.botanichorizons.addons.tileentity.TileAdvancedConjurationPool; import net.fuzzycraft.botanichorizons.addons.tileentity.TileAdvancedCraftingPool; +import net.fuzzycraft.botanichorizons.addons.tileentity.TileAdvancedTerraPlate; import net.fuzzycraft.botanichorizons.util.Constants; import net.fuzzycraft.botanichorizons.util.multiblock.BasicBlockCheck; import net.fuzzycraft.botanichorizons.util.multiblock.MetaBlockCheck; @@ -25,6 +28,7 @@ public final class Multiblocks { public static MultiblockHelper poolConjuration; public static MultiblockHelper poolAlchemy; public static MultiblockHelper poolInfusion; + public static MultiblockHelper placeholder; public static void init() { MultiblockCheck air = new BasicBlockCheck(Blocks.air); @@ -98,6 +102,19 @@ public static void init() { " ", " lllll ", " llgll ", " lglgl ", " llgll ", " lllll ", " " } ); + + // For new multiblocks + + MultiblockBuilder tempBuilder = new MultiblockBuilder(); + tempBuilder.setRootCharacter("x"); + tempBuilder.addCheck("w", livingWood); + tempBuilder.addCheck("r", livingRock); + placeholder = tempBuilder.buildForMap( + new String[] { + "rwr", "w w", "rxr" + } + ); + } public static void postInit() { @@ -106,6 +123,10 @@ public static void postInit() { HoloProjectorSupport.registerOrientedWithStructureLib(poolAlchemy, BHBlocks.autoPoolAlchemy, TileAdvancedAlchemyPool.class); HoloProjectorSupport.registerOrientedWithStructureLib(poolConjuration, BHBlocks.autoPoolConjuration, TileAdvancedConjurationPool.class); HoloProjectorSupport.registerOrientedWithStructureLib(alfPortal, BHBlocks.autoPortal, TileAdvancedAlfPortal.class); + + HoloProjectorSupport.registerOrientedWithStructureLib(placeholder, BHBlocks.autoApothecary, TileAdvancedApothecary.class); + HoloProjectorSupport.registerOrientedWithStructureLib(placeholder, BHBlocks.autoAltar, TileAdvancedAltar.class); + HoloProjectorSupport.registerOrientedWithStructureLib(placeholder, BHBlocks.autoPlate, TileAdvancedTerraPlate.class); } } } diff --git a/src/main/java/net/fuzzycraft/botanichorizons/addons/block/BlockAdvancedAltar.java b/src/main/java/net/fuzzycraft/botanichorizons/addons/block/BlockAdvancedAltar.java new file mode 100644 index 0000000..0f9f24c --- /dev/null +++ b/src/main/java/net/fuzzycraft/botanichorizons/addons/block/BlockAdvancedAltar.java @@ -0,0 +1,105 @@ +package net.fuzzycraft.botanichorizons.addons.block; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; +import net.fuzzycraft.botanichorizons.addons.Multiblocks; +import net.fuzzycraft.botanichorizons.addons.tileentity.TileAdvancedAltar; +import net.fuzzycraft.botanichorizons.lexicon.BHLexicon; +import net.fuzzycraft.botanichorizons.util.Facing2D; +import net.fuzzycraft.botanichorizons.util.IBlockTooltip; +import net.minecraft.block.Block; +import net.minecraft.block.material.Material; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.ScaledResolution; +import net.minecraft.client.renderer.texture.IIconRegister; +import net.minecraft.client.resources.I18n; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.ItemStack; +import net.minecraft.util.IIcon; +import net.minecraft.world.IBlockAccess; +import net.minecraft.world.World; +import vazkii.botania.api.lexicon.ILexiconable; +import vazkii.botania.api.lexicon.LexiconEntry; +import vazkii.botania.api.wand.IWandHUD; +import vazkii.botania.api.wand.IWandable; + +import java.util.List; + +import static net.fuzzycraft.botanichorizons.util.Constants.BH_ICON_PREFIX; + +public class BlockAdvancedAltar extends BlockModContainer implements IWandHUD, IWandable, ILexiconable, IBlockTooltip { + public static final String NAME = "automatedAltar"; + + // META: bit 0 = online, bit 1..2 = 2D facing + + public BlockAdvancedAltar() { + super(Material.rock); + setHardness(10.0F); + setStepSound(soundTypeStone); + setBlockName(NAME); + } + + @Override + public TileAdvancedAltar createNewTileEntity(World world, int meta) { + return new TileAdvancedAltar(); + } + + IIcon iconPrimary; + IIcon iconSecondary; + + @SideOnly(Side.CLIENT) @Override + public void registerBlockIcons(IIconRegister register) { + this.iconPrimary = register.registerIcon(BH_ICON_PREFIX + "quartzYellowTop"); + this.iconSecondary = register.registerIcon(BH_ICON_PREFIX + "quartzYellowSide"); + } + + @SideOnly(Side.CLIENT) @Override + public IIcon getIcon(int side, int meta) { + return (side == 1 || Facing2D.fromIC2(side) == Facing2D.fromIndex(meta >> 1)) ? this.iconPrimary : this.iconSecondary; + } + + @SideOnly(Side.CLIENT) @Override + public void renderHUD(Minecraft mc, ScaledResolution res, World world, int x, int y, int z) { + ((TileAdvancedAltar) world.getTileEntity(x, y, z)).renderHUD(mc, res); + } + + @Override + public LexiconEntry getEntry(World world, int x, int y, int z, EntityPlayer player, ItemStack lexicon) { + return BHLexicon.automatedManaPool; + } + + @Override + public void onBlockPlacedBy(World worldIn, int x, int y, int z, EntityLivingBase placer, ItemStack itemIn) { + Facing2D facing = Facing2D.facingPlayer(placer); + worldIn.setBlockMetadataWithNotify(x, y, z, facing.index << 1, 3); + } + + @Override + public void breakBlock(World world, int x, int y, int z, Block blockBroken, int meta) { + TileAdvancedAltar tileEntity = (TileAdvancedAltar)world.getTileEntity(x, y, z); + if (tileEntity != null) { + tileEntity.dropItems(world, x, y, z); + } + super.breakBlock(world, x, y, z, blockBroken, meta); + } + + @Override + public boolean onUsedByWand(EntityPlayer player, ItemStack stack, World world, int x, int y, int z, int side) { + return ((TileAdvancedAltar)world.getTileEntity(x, y, z)).onWanded(player); + } + + @Override + public int getLightValue(IBlockAccess world, int x, int y, int z) { + return (world.getBlockMetadata(x, y, z) & 1) == 0 ? 0 : 10; + } + + @SideOnly(Side.CLIENT) + @Override + public void addTooltipInformation(ItemStack itemStack, List tooltipStrings) { + // every rune beyond the first gets a ??% mana discount + tooltipStrings.add(I18n.format("botanichorizons.tooltip.parallels", TileAdvancedAltar.MAX_PARALLELS)); + Multiblocks.poolAlchemy.addBuildInfoToTooltip(tooltipStrings); + tooltipStrings.add(I18n.format("botanichorizons.author.combuster")); + } +} diff --git a/src/main/java/net/fuzzycraft/botanichorizons/addons/block/BlockAdvancedApothecary.java b/src/main/java/net/fuzzycraft/botanichorizons/addons/block/BlockAdvancedApothecary.java new file mode 100644 index 0000000..093ba52 --- /dev/null +++ b/src/main/java/net/fuzzycraft/botanichorizons/addons/block/BlockAdvancedApothecary.java @@ -0,0 +1,107 @@ +package net.fuzzycraft.botanichorizons.addons.block; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; +import net.fuzzycraft.botanichorizons.addons.Multiblocks; +import net.fuzzycraft.botanichorizons.addons.tileentity.TileAdvancedAlchemyPool; +import net.fuzzycraft.botanichorizons.addons.tileentity.TileAdvancedApothecary; +import net.fuzzycraft.botanichorizons.addons.tileentity.TileAdvancedTerraPlate; +import net.fuzzycraft.botanichorizons.lexicon.BHLexicon; +import net.fuzzycraft.botanichorizons.util.Facing2D; +import net.fuzzycraft.botanichorizons.util.IBlockTooltip; +import net.minecraft.block.Block; +import net.minecraft.block.material.Material; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.ScaledResolution; +import net.minecraft.client.renderer.texture.IIconRegister; +import net.minecraft.client.resources.I18n; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.ItemStack; +import net.minecraft.util.IIcon; +import net.minecraft.world.IBlockAccess; +import net.minecraft.world.World; +import vazkii.botania.api.lexicon.ILexiconable; +import vazkii.botania.api.lexicon.LexiconEntry; +import vazkii.botania.api.wand.IWandHUD; +import vazkii.botania.api.wand.IWandable; + +import java.util.List; + +import static net.fuzzycraft.botanichorizons.util.Constants.BH_ICON_PREFIX; + +public class BlockAdvancedApothecary extends BlockModContainer implements IWandHUD, IWandable, ILexiconable, IBlockTooltip { + public static final String NAME = "automatedApothecary"; + + // META: bit 0 = online, bit 1..2 = 2D facing + + public BlockAdvancedApothecary() { + super(Material.rock); + setHardness(10.0F); + setStepSound(soundTypeStone); + setBlockName(NAME); + } + + @Override + public TileAdvancedApothecary createNewTileEntity(World world, int meta) { + return new TileAdvancedApothecary(); + } + + IIcon iconPrimary; + IIcon iconSecondary; + + @SideOnly(Side.CLIENT) @Override + public void registerBlockIcons(IIconRegister register) { + this.iconPrimary = register.registerIcon(BH_ICON_PREFIX + "quartzYellowTop"); + this.iconSecondary = register.registerIcon(BH_ICON_PREFIX + "quartzYellowSide"); + } + + @SideOnly(Side.CLIENT) @Override + public IIcon getIcon(int side, int meta) { + return (side == 1 || Facing2D.fromIC2(side) == Facing2D.fromIndex(meta >> 1)) ? this.iconPrimary : this.iconSecondary; + } + + @SideOnly(Side.CLIENT) @Override + public void renderHUD(Minecraft mc, ScaledResolution res, World world, int x, int y, int z) { + ((TileAdvancedApothecary) world.getTileEntity(x, y, z)).renderHUD(mc, res); + } + + @Override + public LexiconEntry getEntry(World world, int x, int y, int z, EntityPlayer player, ItemStack lexicon) { + return BHLexicon.automatedManaPool; + } + + @Override + public void onBlockPlacedBy(World worldIn, int x, int y, int z, EntityLivingBase placer, ItemStack itemIn) { + Facing2D facing = Facing2D.facingPlayer(placer); + worldIn.setBlockMetadataWithNotify(x, y, z, facing.index << 1, 3); + } + + @Override + public void breakBlock(World world, int x, int y, int z, Block blockBroken, int meta) { + TileAdvancedApothecary tileEntity = (TileAdvancedApothecary)world.getTileEntity(x, y, z); + if (tileEntity != null) { + tileEntity.dropItems(world, x, y, z); + } + super.breakBlock(world, x, y, z, blockBroken, meta); + } + + @Override + public boolean onUsedByWand(EntityPlayer player, ItemStack stack, World world, int x, int y, int z, int side) { + return ((TileAdvancedApothecary)world.getTileEntity(x, y, z)).onWanded(player); + } + + @Override + public int getLightValue(IBlockAccess world, int x, int y, int z) { + return (world.getBlockMetadata(x, y, z) & 1) == 0 ? 0 : 10; + } + + @SideOnly(Side.CLIENT) + @Override + public void addTooltipInformation(ItemStack itemStack, List tooltipStrings) { + // uses a bit of mana for instead of water or seeds + tooltipStrings.add(I18n.format("botanichorizons.tooltip.parallels", TileAdvancedApothecary.MAX_PARALLELS)); + Multiblocks.poolAlchemy.addBuildInfoToTooltip(tooltipStrings); + tooltipStrings.add(I18n.format("botanichorizons.author.combuster")); + } +} diff --git a/src/main/java/net/fuzzycraft/botanichorizons/addons/block/BlockAdvancedTerraPlate.java b/src/main/java/net/fuzzycraft/botanichorizons/addons/block/BlockAdvancedTerraPlate.java new file mode 100644 index 0000000..ea44c93 --- /dev/null +++ b/src/main/java/net/fuzzycraft/botanichorizons/addons/block/BlockAdvancedTerraPlate.java @@ -0,0 +1,106 @@ +package net.fuzzycraft.botanichorizons.addons.block; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; +import net.fuzzycraft.botanichorizons.addons.Multiblocks; +import net.fuzzycraft.botanichorizons.addons.tileentity.TileAdvancedTerraPlate; +import net.fuzzycraft.botanichorizons.lexicon.BHLexicon; +import net.fuzzycraft.botanichorizons.util.Facing2D; +import net.fuzzycraft.botanichorizons.util.IBlockTooltip; +import net.minecraft.block.Block; +import net.minecraft.block.material.Material; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.ScaledResolution; +import net.minecraft.client.renderer.texture.IIconRegister; +import net.minecraft.client.resources.I18n; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.ItemStack; +import net.minecraft.util.IIcon; +import net.minecraft.world.IBlockAccess; +import net.minecraft.world.World; +import vazkii.botania.api.lexicon.ILexiconable; +import vazkii.botania.api.lexicon.LexiconEntry; +import vazkii.botania.api.wand.IWandHUD; +import vazkii.botania.api.wand.IWandable; + +import java.util.List; + +import static net.fuzzycraft.botanichorizons.util.Constants.BH_ICON_PREFIX; + +public class BlockAdvancedTerraPlate extends BlockModContainer implements IWandHUD, IWandable, ILexiconable, IBlockTooltip { + public static final String NAME = "automatedTerraPlate"; + + // META: bit 0 = online, bit 1..2 = 2D facing + + public BlockAdvancedTerraPlate() { + super(Material.rock); + setHardness(10.0F); + setStepSound(soundTypeStone); + setBlockName(NAME); + } + + @Override + public TileAdvancedTerraPlate createNewTileEntity(World world, int meta) { + return new TileAdvancedTerraPlate(); + } + + IIcon iconPrimary; + IIcon iconSecondary; + + @SideOnly(Side.CLIENT) @Override + public void registerBlockIcons(IIconRegister register) { + this.iconPrimary = register.registerIcon(BH_ICON_PREFIX + "quartzYellowTop"); + this.iconSecondary = register.registerIcon(BH_ICON_PREFIX + "quartzYellowSide"); + } + + @SideOnly(Side.CLIENT) @Override + public IIcon getIcon(int side, int meta) { + return (side == 1 || Facing2D.fromIC2(side) == Facing2D.fromIndex(meta >> 1)) ? this.iconPrimary : this.iconSecondary; + } + + @SideOnly(Side.CLIENT) @Override + public void renderHUD(Minecraft mc, ScaledResolution res, World world, int x, int y, int z) { + ((TileAdvancedTerraPlate) world.getTileEntity(x, y, z)).renderHUD(mc, res); + } + + @Override + public LexiconEntry getEntry(World world, int x, int y, int z, EntityPlayer player, ItemStack lexicon) { + return BHLexicon.automatedManaPool; + } + + @Override + public void onBlockPlacedBy(World worldIn, int x, int y, int z, EntityLivingBase placer, ItemStack itemIn) { + Facing2D facing = Facing2D.facingPlayer(placer); + worldIn.setBlockMetadataWithNotify(x, y, z, facing.index << 1, 3); + } + + @Override + public void breakBlock(World world, int x, int y, int z, Block blockBroken, int meta) { + TileAdvancedTerraPlate tileEntity = (TileAdvancedTerraPlate)world.getTileEntity(x, y, z); + if (tileEntity != null) { + tileEntity.dropItems(world, x, y, z); + } + super.breakBlock(world, x, y, z, blockBroken, meta); + } + + @Override + public boolean onUsedByWand(EntityPlayer player, ItemStack stack, World world, int x, int y, int z, int side) { + return ((TileAdvancedTerraPlate)world.getTileEntity(x, y, z)).onWanded(player); + } + + @Override + public int getLightValue(IBlockAccess world, int x, int y, int z) { + return (world.getBlockMetadata(x, y, z) & 1) == 0 ? 0 : 10; + } + + @SideOnly(Side.CLIENT) + @Override + public void addTooltipInformation(ItemStack itemStack, List tooltipStrings) { + // Consumes mana as long as there are items in its inventory + // Crafts all terrasteel in one go + tooltipStrings.add(I18n.format("botanichorizons.tooltip.parallels", TileAdvancedTerraPlate.MAX_PARALLELS)); + Multiblocks.poolAlchemy.addBuildInfoToTooltip(tooltipStrings); + tooltipStrings.add(I18n.format("botanichorizons.author.combuster")); + } +} diff --git a/src/main/java/net/fuzzycraft/botanichorizons/addons/tileentity/AutomationTileEntity.java b/src/main/java/net/fuzzycraft/botanichorizons/addons/tileentity/AutomationTileEntity.java index ef70366..9252078 100644 --- a/src/main/java/net/fuzzycraft/botanichorizons/addons/tileentity/AutomationTileEntity.java +++ b/src/main/java/net/fuzzycraft/botanichorizons/addons/tileentity/AutomationTileEntity.java @@ -39,7 +39,7 @@ abstract public class AutomationTileEntity extends TileEntity implements IManaRe protected boolean clientSparkTransfer = false; protected int structureCycle = 0; - protected int cachedMana = Integer.MIN_VALUE; + private int cachedMana = Integer.MIN_VALUE; protected int statusCycle = 0; // Delegated state @@ -85,6 +85,7 @@ protected void updateEntitySparks() { } if (storedMana < getManaMaximum() + SPARK_BUFFER_MANA) { + FMLLog.warning("Requesting spark transfers: %d/%d mana", storedMana, getManaMaximum()); SparkHelper.requestSparkTransfers(worldObj, xCoord, yCoord, zCoord, getAttachedSpark()); markDirty(); } @@ -115,6 +116,13 @@ protected boolean shouldShareTE() { return delta <= -DEFAULT_MANA_ERROR_RANGE || delta >= DEFAULT_MANA_ERROR_RANGE; } + public void markTEForSharing(boolean immediate) { + cachedMana = Integer.MIN_VALUE; + if (immediate) { + statusCycle = STATUS_CYCLE; + } + } + // Persistence public static final String KEY_MANA = "mana"; diff --git a/src/main/java/net/fuzzycraft/botanichorizons/addons/tileentity/RecipeAutomationTileEntity.java b/src/main/java/net/fuzzycraft/botanichorizons/addons/tileentity/RecipeAutomationTileEntity.java new file mode 100644 index 0000000..5002819 --- /dev/null +++ b/src/main/java/net/fuzzycraft/botanichorizons/addons/tileentity/RecipeAutomationTileEntity.java @@ -0,0 +1,417 @@ +package net.fuzzycraft.botanichorizons.addons.tileentity; + +import cpw.mods.fml.common.FMLLog; +import net.fuzzycraft.botanichorizons.util.InventoryHelper; +import net.fuzzycraft.botanichorizons.util.multiblock.MultiblockHelper; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.inventory.IInvBasic; +import net.minecraft.inventory.IInventory; +import net.minecraft.inventory.InventoryBasic; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.world.World; + +import javax.annotation.Nonnull; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; + +public abstract class RecipeAutomationTileEntity extends AutomationTileEntity implements IInventory, IInvBasic { + + public abstract Collection getAllRecipes(); + public abstract int maxRecipeWidth(); + public abstract List getInputs(@Nonnull T recipe); + public abstract List getOutputs(@Nonnull T recipe); + public abstract int getManaRequired(@Nonnull T recipe, int copies); + public abstract int getAvailableParallels(@Nonnull T recipe); + + public final int inputSize; + public final int outputSize; + + public final InventoryBasic inventoryHandler; + private final HashSet possibleRecipes = new HashSet<>(); + protected T setRecipe = null; // Are we limited to 1 recipe + protected boolean clientRecipeSet = false; + protected int lastCheckedMana = 0; + + protected int checkTicks = 0; + protected final int TICKS_PER_RECIPE_CHECK = 20; + + public RecipeAutomationTileEntity(@Nonnull final MultiblockHelper structure, final int outputSize) { + super(structure); + inputSize = maxRecipeWidth(); + this.outputSize = outputSize; // increase later to handle rune returns + inventoryHandler = new InventoryBasic("name", false, inputSize + outputSize); + + possibleRecipes.addAll(getAllRecipes()); + + isOnline = true; // TODO: apply block activation specifics + } + + @Override + protected void updateEntityCrafting() { + if (setRecipe == null) { + checkTicks = 0; + lastCheckedMana = 0; + } else if (checkTicks > 0) { + checkTicks--; + } else { + checkTicks = TICKS_PER_RECIPE_CHECK - 1; + handleOutputs(); + cleanupInventory(); + + int craftable = getCopiesCraftable(); + FMLLog.warning("recipe length: %d, craftable items: %d", getInputs(setRecipe).size(), craftable); + if (craftable > 0) { + int maxParallel = getAvailableParallels(setRecipe); + if (craftable > maxParallel) { + craftable = maxParallel; + } + + int mana = getManaRequired(setRecipe, craftable); + FMLLog.warning("mana: %d/%d/%d, parallels %d/%d", storedMana, mana, lastCheckedMana, craftable, maxParallel); + if (storedMana >= mana) { + if (commitCrafts(craftable)) { + storedMana -= mana; + lastCheckedMana = 0; + + recalculateAvailableRecipes(); + handleOutputs(); + cleanupInventory(); + markTEForSharing(true); + markDirty(); + } + } else if (mana != lastCheckedMana) { + lastCheckedMana = mana; + sparkCycleRemaining = 0; + markTEForSharing(true); + } + } + } + } + + protected boolean commitCrafts(int numberToCraft) { + if (setRecipe == null || numberToCraft <= 0) { + return false; // probably a bug + } + + List outputs = getOutputs(setRecipe); + for (int space = 0; space < outputs.size(); space++) { + ItemStack destinationSlotStack = inventoryHandler.getStackInSlot(inputSize + outputSize - 1 - space); + if (destinationSlotStack != null && destinationSlotStack.stackSize > 0) { + return false; // not enough output space + } + } + boolean didCraft = deductCraft(numberToCraft); + if (!didCraft) { + return false; // ingredients were already removed + } + + // We're past the commit point now, inputs have been removed. + for (int space = 0; space < outputs.size(); space++) { + ItemStack resultStack = outputs.get(space).copy(); + resultStack.stackSize = resultStack.stackSize * numberToCraft; + inventoryHandler.setInventorySlotContents(inputSize + outputSize - 1 - space, resultStack); + } + return true; + } + + // Dump output downward + public void handleOutputs() { + InventoryHelper.pushInventoryToWorldDown(this, worldObj, inventoryHandler, inputSize, inputSize + outputSize); + } + + // pushes stacks left to try and make free space. + public void cleanupInventory() { + InventoryHelper.defragInventory(inventoryHandler, inputSize, inputSize + outputSize); + } + + // get the amount or recipes that can be run with the current ingredients. + // Limitation: if input 1 applies to ingredient A and B and input 2 applies to ingredient A, + // input 1 will not get assigned to ingredient B and the recipe won't work. + // TLDR: Don't do NP-hard recipes. + protected int getCopiesCraftable() { + int[] remainingMap = computeItemsAvailable(); + List[] applyMap = computeRecipeAssignment(); + + // compute upper bound - this may be too high if certain ingredients are used twice. + int searchMaximum = 64; + for (List application: applyMap) { + int copiesForThisIngredient = 0; + for (int reference: application) { + copiesForThisIngredient += remainingMap[reference]; + } + if (copiesForThisIngredient < searchMaximum) { + searchMaximum = copiesForThisIngredient; + } + FMLLog.info("computing copies: %d available, %d max", copiesForThisIngredient, searchMaximum); + } + + // Avoid doing +1 scans, these are notoriously slow even though we already pinned the recipe + int searchMinimum = 0; + // attempt the heuristic first. If all ingredients are inserted correctly in one go this is >99% cases the answer + int attempt = searchMaximum; + // if the recipe lacks specific items, the loop is not started + while (searchMinimum != searchMaximum) { + //FMLLog.info("iterating (%d, %d)", searchMinimum, searchMaximum); + int[] trialMap = remainingMap.clone(); + boolean success = true; + for (List application: applyMap) { + int toConsume = attempt; + for (Integer reference: application) { + if (trialMap[reference] >= toConsume) { + trialMap[reference] -= toConsume; + toConsume = 0; + break; + } else if (trialMap[reference] > 0) { + toConsume -= trialMap[reference]; + trialMap[reference] = 0; + } + } + if (toConsume != 0) { + success = false; + break; + } + } + + // Adjust upper/lower bounds + if (success) { + searchMinimum = attempt; + } else { + searchMaximum = attempt - 1; + } + attempt = (searchMaximum + searchMinimum + 1) / 2; // round up + //FMLLog.info("iteration result (%b, %d, %d)", success, searchMinimum, searchMaximum); + } + + return searchMinimum; + } + + protected boolean deductCraft(int copiesRequested) { + int[] remainingMap = computeItemsAvailable(); + List[] applyMap = computeRecipeAssignment(); + + for (List application: applyMap) { + int copiesForThisItem = copiesRequested; + for (Integer reference: application) { + if (remainingMap[reference] >= copiesForThisItem) { + remainingMap[reference] -= copiesForThisItem; + copiesForThisItem = 0; + } else if (remainingMap[reference] > 0) { + copiesForThisItem -= remainingMap[reference]; + remainingMap[reference] = 0; + } + } + if (copiesForThisItem > 0) { + return false; + } + } + + // commit changes + for (int inputSlot = 0; inputSlot < inputSize; inputSlot++) { + int newStackSize = remainingMap[inputSlot]; + if (newStackSize == 0) { + inventoryHandler.setInventorySlotContents(inputSlot, null); + } else { + int difference = inventoryHandler.getStackInSlot(inputSlot).stackSize - newStackSize; + if (difference > 0) { + inventoryHandler.decrStackSize(inputSlot, difference); + } + } + } + return true; + } + + protected List[] computeRecipeAssignment() { + List ingredientList = getInputs(setRecipe); + List[] applyMap = new List[ingredientList.size()]; + for (int recipeSlot = 0; recipeSlot < ingredientList.size(); recipeSlot++) { + List mappings = new ArrayList<>(); + for (int inputSlot = 0; inputSlot < inputSize; inputSlot++) { + ItemStack inputStack = inventoryHandler.getStackInSlot(inputSlot); + if (inputStack != null && inputStack.stackSize != 0) { + if (InventoryHelper.isIngredient(inputStack, ingredientList.get(recipeSlot))) { + mappings.add(inputSlot); + //FMLLog.info("Assigning slot %d to ingredient %d", inputSlot, recipeSlot); + } + } + } + //FMLLog.info("cumulative mapping: %s, %s", mappings.toString(), Arrays.deepToString(applyMap)); + applyMap[recipeSlot] = mappings; + } + //FMLLog.info("recipe mapping: %s", Arrays.deepToString(applyMap)); + return applyMap; + } + + private int[] computeItemsAvailable() { + int[] remainingMap = new int[inputSize]; + // Map available ingredients + for (int inputSlot = 0; inputSlot < inputSize; inputSlot++) { + ItemStack inputStack = inventoryHandler.getStackInSlot(inputSlot); + if (inputStack == null || inputStack.stackSize == 0) { + remainingMap[inputSlot] = 0; + } else { + remainingMap[inputSlot] = inputStack.stackSize; + //FMLLog.info("%d items available in slot %d", inputStack.stackSize, inputSlot); + } + } + //FMLLog.info("stockpile mapping: %s", Arrays.toString(remainingMap)); + return remainingMap; + } + + // Crafting management + + public boolean allowInput(ItemStack stack) { + for (T recipe: possibleRecipes) { + if (doesStackFitRecipe(stack, recipe)) { + return true; + } + } + return false; + } + + public boolean doesStackFitRecipe(@Nonnull ItemStack stack, @Nonnull T recipe) { + for (Object input: getInputs(recipe)) { + if (InventoryHelper.isIngredient(stack, input)) { + return true; + } + } + return false; + } + + + private void resetPossibleRecipes() { + possibleRecipes.addAll(getAllRecipes()); + } + + private void truncateRecipes(@Nonnull ItemStack changedStack) { + int currentCount = possibleRecipes.size(); + Iterator iterator = possibleRecipes.iterator(); + while (iterator.hasNext()) { + T recipe = iterator.next(); + if (!doesStackFitRecipe(changedStack, recipe)) { + iterator.remove(); + } + } + setRecipe = (possibleRecipes.size() == 1) ? possibleRecipes.iterator().next() : null; + FMLLog.warning("recipes %d -> %d after %s", currentCount, possibleRecipes.size(), changedStack.toString()); + if (setRecipe != null) { + FMLLog.warning("selected recipe: %s", setRecipe.toString()); + } + } + + private void recalculateAvailableRecipes() { + resetPossibleRecipes(); + for (int slot = 0; slot < inputSize; slot++) { + ItemStack stack = inventoryHandler.getStackInSlot(slot); + if (stack != null && stack.stackSize > 0) { + truncateRecipes(stack); + } + } + } + + // Persistence + + private static final String KEY_INVENTORY = "inv"; + private static final String KEY_RECIPE_SET = "rcp"; + private static final String KEY_RECIPE_MANA = "mana"; + + public void writeCustomNBT(NBTTagCompound compound) { + super.writeCustomNBT(compound); + compound.setTag(KEY_INVENTORY, InventoryHelper.saveInventoryToNBT(inventoryHandler)); + compound.setBoolean(KEY_RECIPE_SET, worldObj.isRemote ? clientRecipeSet : setRecipe != null); + compound.setInteger(KEY_RECIPE_MANA, lastCheckedMana); + } + + public void readCustomNBT(NBTTagCompound compound) { + super.readCustomNBT(compound); + InventoryHelper.readInventoryFromNBT(inventoryHandler, compound.getCompoundTag(KEY_INVENTORY)); + clientRecipeSet = compound.getBoolean(KEY_RECIPE_SET); + lastCheckedMana = compound.getInteger(KEY_RECIPE_MANA); + + resetPossibleRecipes(); + recalculateAvailableRecipes(); + } + + // IInventory + + @Override + public int getSizeInventory() { + return inventoryHandler.getSizeInventory(); + } + @Override + public ItemStack getStackInSlot(int slotIn) { + return inventoryHandler.getStackInSlot(slotIn); + } + @Override + public ItemStack decrStackSize(int index, int count) { + if (index < inputSize && count > 0) return null; + ItemStack returned = inventoryHandler.decrStackSize(index, count); + ItemStack remaining = inventoryHandler.getStackInSlot(index); + if (remaining == null || remaining.stackSize == 0) { + resetPossibleRecipes(); + } + return returned; + } + @Override + public ItemStack getStackInSlotOnClosing(int index) { + return inventoryHandler.getStackInSlotOnClosing(index); + } + @Override + public void setInventorySlotContents(int index, ItemStack stack) { + ItemStack previousStack = inventoryHandler.getStackInSlot(index); + inventoryHandler.setInventorySlotContents(index, stack); + if (stack == null || stack.stackSize == 0) { + resetPossibleRecipes(); + } else if (previousStack == null || previousStack.stackSize == 0) { + truncateRecipes(stack); + } else { + resetPossibleRecipes(); + truncateRecipes(stack); + } + } + @Override + public String getInventoryName() { + return inventoryHandler.getInventoryName(); + } + @Override + public boolean hasCustomInventoryName() { + return inventoryHandler.hasCustomInventoryName(); + } + @Override + public int getInventoryStackLimit() { + return inventoryHandler.getInventoryStackLimit(); + } + @Override + public boolean isUseableByPlayer(EntityPlayer player) { + return inventoryHandler.isUseableByPlayer(player); + } + @Override + public void openInventory() { + inventoryHandler.openInventory(); // no-op + } + @Override + public void closeInventory() { + inventoryHandler.closeInventory(); // no-op + } + @Override + public boolean isItemValidForSlot(int index, ItemStack stack) { + if (index >= inputSize || index < 0) return false; // do not allow inserts into output slots + if (stack == null || stack.getItem() == null) return false; + return allowInput(stack); + } + + // IInvBasic + @Override + public void onInventoryChanged(InventoryBasic p_76316_1_) { + markDirty(); + } + + // Brock breaking + public void dropItems(World world, int x, int y, int z) { + InventoryHelper.dropAllItems(world, x, y, z, inventoryHandler); + } + +} diff --git a/src/main/java/net/fuzzycraft/botanichorizons/addons/tileentity/SimpleAutomationTileEntity.java b/src/main/java/net/fuzzycraft/botanichorizons/addons/tileentity/SimpleAutomationTileEntity.java index 66c32ad..6ec7a52 100644 --- a/src/main/java/net/fuzzycraft/botanichorizons/addons/tileentity/SimpleAutomationTileEntity.java +++ b/src/main/java/net/fuzzycraft/botanichorizons/addons/tileentity/SimpleAutomationTileEntity.java @@ -98,55 +98,12 @@ public void handleCrafts() { // Dump output downward public void handleOutputs() { - if (yCoord < 1) return; - - for (int slot = INPUT_SIZE; slot < INPUT_SIZE + OUTPUT_SIZE; slot++) { - final ItemStack stack = inventoryHandler.getStackInSlot(slot); - if (stack == null || stack.getItem() == null || stack.stackSize == 0) continue; - - TileEntity outputEntity = worldObj.getTileEntity(xCoord, yCoord - 1, zCoord); - if (outputEntity instanceof IInventory) { - IInventory outputInventory = (IInventory) outputEntity; - ItemStack remainingItems = InventoryHelper.pushToInventory(outputInventory, stack); - inventoryHandler.setInventorySlotContents(slot, remainingItems); - } else if (worldObj.isAirBlock(xCoord, yCoord - 1, zCoord)) { - // TODO: drop items in world - } - } + InventoryHelper.pushInventoryToWorldDown(this, worldObj, inventoryHandler, INPUT_SIZE, INPUT_SIZE + OUTPUT_SIZE); } // pushes stacks left to try and make free space. public void cleanupInventory(int start, int end) { - for (int checkSlot = start + 1; checkSlot < end; checkSlot++) { - ItemStack sourceStack = inventoryHandler.getStackInSlot(checkSlot); - if (sourceStack != null) { - boolean done = false; - for (int refSlot = start; refSlot < checkSlot && !done; refSlot++) { - ItemStack destinationStack = inventoryHandler.getStackInSlot(refSlot); - if (destinationStack == null) { - inventoryHandler.setInventorySlotContents(refSlot, sourceStack); - inventoryHandler.setInventorySlotContents(checkSlot, null); - done = true; - } else { - final int itemsToMove = InventoryHelper.itemsToMove(destinationStack, sourceStack); - if (itemsToMove > 0) { - final ItemStack newDestinationStack = destinationStack.copy(); - final ItemStack newSourceStack = sourceStack.copy(); - newDestinationStack.stackSize += itemsToMove; - newSourceStack.stackSize -= itemsToMove; - inventoryHandler.setInventorySlotContents(refSlot, newDestinationStack); - if (newSourceStack.stackSize == 0) { - inventoryHandler.setInventorySlotContents(checkSlot, null); - done = true; - } else { - inventoryHandler.setInventorySlotContents(checkSlot, newSourceStack); - sourceStack = newSourceStack; - } - } - } - } - } - } + InventoryHelper.defragInventory(inventoryHandler, start, end); } @@ -226,16 +183,6 @@ public void onInventoryChanged(InventoryBasic p_76316_1_) { // Brock breaking public void dropItems(World world, int x, int y, int z) { - for (int slot = 0; slot < inventoryHandler.getSizeInventory(); slot++) { - ItemStack drop = inventoryHandler.getStackInSlot(slot); - - if (drop != null && drop.stackSize > 0) { - ItemStack copy = drop.copy(); - inventoryHandler.setInventorySlotContents(slot, null); - EntityItem entity = new EntityItem(world, (double)x + 0.5, (double)y + 0.5, (double)z + 0.5, copy); - InventoryHelper.setRandomDropDirection(entity, world); - world.spawnEntityInWorld(entity); - } - } + InventoryHelper.dropAllItems(world, x, y, z, inventoryHandler); } } diff --git a/src/main/java/net/fuzzycraft/botanichorizons/addons/tileentity/TileAdvancedAltar.java b/src/main/java/net/fuzzycraft/botanichorizons/addons/tileentity/TileAdvancedAltar.java new file mode 100644 index 0000000..4575a61 --- /dev/null +++ b/src/main/java/net/fuzzycraft/botanichorizons/addons/tileentity/TileAdvancedAltar.java @@ -0,0 +1,115 @@ +package net.fuzzycraft.botanichorizons.addons.tileentity; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; +import net.fuzzycraft.botanichorizons.addons.BHBlocks; +import net.fuzzycraft.botanichorizons.addons.Multiblocks; +import net.fuzzycraft.botanichorizons.util.ChargeState; +import net.fuzzycraft.botanichorizons.util.Constants; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.ScaledResolution; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.ItemStack; +import net.minecraft.util.StatCollector; +import org.jetbrains.annotations.NotNull; +import vazkii.botania.api.BotaniaAPI; +import vazkii.botania.api.recipe.RecipePetals; +import vazkii.botania.api.recipe.RecipeRuneAltar; +import vazkii.botania.client.core.handler.HUDHandler; +import vazkii.botania.common.block.ModBlocks; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +public class TileAdvancedAltar extends RecipeAutomationTileEntity { + + public static final int MAX_PARALLELS = 16; + public static final int IDLE_MANA = 10000; + public static final int ACTIVATE_MANA = 5000; + public static int cachedMaxRecipeWidth = 0; + + public TileAdvancedAltar() { + super(Multiblocks.placeholder, 5); + } + + @Override + public int getManaMaximum() { + return setRecipe == null ? IDLE_MANA : IDLE_MANA + lastCheckedMana; + } + + @Override + public ItemStack getWrenchDrop(EntityPlayer entityPlayer) { + return new ItemStack(BHBlocks.autoAltar); + } + + public boolean onWanded(EntityPlayer wandUser) { + return false; + } + + // Mana HUD + + // Mana HUD + + @SideOnly(Side.CLIENT) + public void renderHUD(Minecraft mc, ScaledResolution res) { + if (lastCheckedMana == 0 || setRecipe == null) { + ChargeState state = ChargeState.genState(isOnline, storedMana, ACTIVATE_MANA); + String tooltip = state.getLocalisedHudString(BHBlocks.autoAltar); + HUDHandler.drawSimpleManaHUD(state.color, storedMana, IDLE_MANA, tooltip, res); + } else { + // crafting state + String tooltip = StatCollector.translateToLocal(BHBlocks.autoAltar.getUnlocalizedName() + ".hud.collecting"); + HUDHandler.drawSimpleManaHUD(0xE0A044, storedMana, lastCheckedMana + IDLE_MANA, tooltip, res); + } + } + + @Override + public Collection getAllRecipes() { + return BotaniaAPI.runeAltarRecipes; + } + + @Override + public int maxRecipeWidth() { + int max = cachedMaxRecipeWidth; + if (max > 0) { + return max; + } else { + for (RecipeRuneAltar recipe: getAllRecipes()) { + int recipeMax = recipe.getInputs().size(); + if (recipeMax > max) { + max = recipeMax; + } + } + cachedMaxRecipeWidth = max; + return max; + } + } + + @Override + public List getInputs(@NotNull RecipeRuneAltar recipe) { + List baseInputs = new ArrayList<>(recipe.getInputs()); + baseInputs.add(new ItemStack(ModBlocks.livingrock, 1)); + + return baseInputs; + } + + @Override + public List getOutputs(@NotNull RecipeRuneAltar recipe) { + List outputs = new ArrayList<>(1); + outputs.add(recipe.getOutput()); + + return outputs; + } + + @Override + public int getManaRequired(@NotNull RecipeRuneAltar recipe, int copies) { + return recipe.getManaUsage() * copies; + } + + @Override + public int getAvailableParallels(@NotNull RecipeRuneAltar recipe) { + return MAX_PARALLELS; + } +} diff --git a/src/main/java/net/fuzzycraft/botanichorizons/addons/tileentity/TileAdvancedApothecary.java b/src/main/java/net/fuzzycraft/botanichorizons/addons/tileentity/TileAdvancedApothecary.java new file mode 100644 index 0000000..24851be --- /dev/null +++ b/src/main/java/net/fuzzycraft/botanichorizons/addons/tileentity/TileAdvancedApothecary.java @@ -0,0 +1,100 @@ +package net.fuzzycraft.botanichorizons.addons.tileentity; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; +import net.fuzzycraft.botanichorizons.addons.BHBlocks; +import net.fuzzycraft.botanichorizons.addons.Multiblocks; +import net.fuzzycraft.botanichorizons.util.InventoryHelper; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.ScaledResolution; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.ItemStack; +import org.jetbrains.annotations.NotNull; +import vazkii.botania.api.BotaniaAPI; +import vazkii.botania.api.recipe.RecipePetals; +import vazkii.botania.api.recipe.RecipeRuneAltar; + +import javax.annotation.Nonnull; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +public class TileAdvancedApothecary extends RecipeAutomationTileEntity { + + public static final int MAX_PARALLELS = 8; + public static final int RECIPE_MANA = 1000; + public static int cachedMaxRecipeWidth = 0; + + public TileAdvancedApothecary() { + super(Multiblocks.placeholder, 2); + } + + @Override + public int getManaMaximum() { + return MAX_PARALLELS * RECIPE_MANA; + } + + @Override + public ItemStack getWrenchDrop(EntityPlayer entityPlayer) { + return new ItemStack(BHBlocks.autoApothecary); + } + + public boolean onWanded(EntityPlayer wandUser) { + return false; + } + + // Recipe wrangling + + @Override + public Collection getAllRecipes() { + return BotaniaAPI.petalRecipes; + } + + @Override + public int maxRecipeWidth() { + int max = cachedMaxRecipeWidth; + if (max > 0) { + return max; + } else { + for (RecipePetals recipe: getAllRecipes()) { + int recipeMax = recipe.getInputs().size(); + if (recipeMax > max) { + max = recipeMax; + } + } + cachedMaxRecipeWidth = max; + return max; + } + } + + @Override + public List getInputs(@NotNull RecipePetals recipe) { + return recipe.getInputs(); + } + + @Override + public List getOutputs(@NotNull RecipePetals recipe) { + ArrayList result = new ArrayList<>(); + result.add(recipe.getOutput()); + return result; + } + + @Override + public int getManaRequired(@NotNull RecipePetals recipe, int copies) { + return copies * RECIPE_MANA; + } + + @Override + public int getAvailableParallels(@NotNull RecipePetals recipe) { + return MAX_PARALLELS; + } + + // Mana HUD + + @SideOnly(Side.CLIENT) + public void renderHUD(@Nonnull Minecraft mc, @Nonnull ScaledResolution res) { + //ChargeState state = ChargeState.genState(isOnline, storedMana, ACTIVATE_MANA); + //String tooltip = state.getLocalisedHudString(BHBlocks.autoApothecary); + //HUDHandler.drawSimpleManaHUD(state.color, storedMana, MANA_CAPACITY, tooltip, res); + } +} diff --git a/src/main/java/net/fuzzycraft/botanichorizons/addons/tileentity/TileAdvancedTerraPlate.java b/src/main/java/net/fuzzycraft/botanichorizons/addons/tileentity/TileAdvancedTerraPlate.java new file mode 100644 index 0000000..e3e3638 --- /dev/null +++ b/src/main/java/net/fuzzycraft/botanichorizons/addons/tileentity/TileAdvancedTerraPlate.java @@ -0,0 +1,327 @@ +package net.fuzzycraft.botanichorizons.addons.tileentity; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; +import net.fuzzycraft.botanichorizons.addons.BHBlocks; +import net.fuzzycraft.botanichorizons.addons.Multiblocks; +import net.fuzzycraft.botanichorizons.util.ChargeState; +import net.fuzzycraft.botanichorizons.util.Constants; +import net.fuzzycraft.botanichorizons.util.Facing2D; +import net.fuzzycraft.botanichorizons.util.InventoryHelper; +import net.fuzzycraft.botanichorizons.util.multiblock.MultiblockHelper; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.ScaledResolution; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.inventory.IInvBasic; +import net.minecraft.inventory.IInventory; +import net.minecraft.inventory.InventoryBasic; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.StatCollector; +import net.minecraft.world.World; +import vazkii.botania.client.core.handler.HUDHandler; +import vazkii.botania.common.block.tile.TileTerraPlate; +import vazkii.botania.common.item.ModItems; + +import static net.fuzzycraft.botanichorizons.util.Constants.MC_BLOCK_SEND_TO_CLIENT; +import static net.fuzzycraft.botanichorizons.util.Constants.MC_BLOCK_UPDATE; + +public class TileAdvancedTerraPlate extends AutomationTileEntity implements IInventory, IInvBasic { + + // Balance + public static int MAX_PARALLELS = 64; + public static int MANA_BUFFER = 10000; + public static int PUMPING_MANA_BUFFER = 1000000; + public static int ACTIVATE_MANA = 9000; + public static int VIRTUAL_SLOTS = 4; + public static int DRAIN_SLOT = 3; + public static int CHECK_INTERVAL = 50; + public static int COST_PERCENT = 90; + public static long CRAFT_MANA = TileTerraPlate.MAX_MANA / 100 * COST_PERCENT; + + // Definitions + public final InventoryBasic inventoryHandler; + public long craftingMana = 0; + public boolean isPumping = false; + protected long currentManaThreshold = 0; + protected int cycleRemaining = 0; + + public TileAdvancedTerraPlate() { + super(Multiblocks.placeholder); + inventoryHandler = new InventoryBasic("name", false, VIRTUAL_SLOTS); + } + + @Override + public int getManaMaximum() { + // prevents burst from completing and then stopping if the unit is active + return isPumping ? PUMPING_MANA_BUFFER : MANA_BUFFER; + } + + @Override + protected void updateEntityCrafting() { + if (!isOnline) { + craftingMana = 0; + return; + } else if (isPumping) { + long oldStash = craftingMana; + craftingMana += storedMana; + storedMana = 0; + if (oldStash < currentManaThreshold && craftingMana >= currentManaThreshold) checkCrafts(); + markDirty(); + } + + if (cycleRemaining > 0) { + cycleRemaining--; + } else { + cycleRemaining = CHECK_INTERVAL; + if (!partialStructureValidation()) { + craftingMana = 0; + isOnline = false; + markDirty(); + } else { + checkCrafts(); + } + } + } + + protected void checkCrafts() { + int ingredient1size = safeSizeInSlot(0); + int ingredient2size = safeSizeInSlot(1); + int ingredient3size = safeSizeInSlot(2); + int drainSize = safeSizeInSlot(DRAIN_SLOT); + + final int oldStoredMana = storedMana; + final long oldThreshold = currentManaThreshold; + final long oldCraftingMana = craftingMana; + final boolean oldPumping = isPumping; + + int runningCrafts = Math.min(ingredient1size, Math.min(ingredient2size, ingredient3size)); + currentManaThreshold = runningCrafts * CRAFT_MANA; + + // Check if we can complete a craft + System.out.println(String.format("Plate status: %d/%d/%d/%d %d->%d/%d", ingredient1size, ingredient2size, ingredient3size, drainSize, runningCrafts, craftingMana, currentManaThreshold)); + if (runningCrafts > 0 && craftingMana >= currentManaThreshold && drainSize == 0) { + + // Consume inputs + inventoryHandler.decrStackSize(0, runningCrafts); + inventoryHandler.decrStackSize(1, runningCrafts); + inventoryHandler.decrStackSize(2, runningCrafts); + ingredient1size -= runningCrafts; + ingredient2size -= runningCrafts; + ingredient3size -= runningCrafts; + + // Generate outputs + ItemStack crafts = new ItemStack(ModItems.manaResource, runningCrafts, Constants.MANARESOURCE_META_TERRASTEEL); + inventoryHandler.setInventorySlotContents(3, crafts); + drainSize = runningCrafts; + + // Return up to one tick of excess mana back + craftingMana -= currentManaThreshold; + if (storedMana + craftingMana < MANA_BUFFER) { + storedMana += (int) craftingMana; + craftingMana = 0; + } + currentManaThreshold = 0; + } + + if (drainSize > 0) { + ItemStack stack = inventoryHandler.getStackInSlot(DRAIN_SLOT); + if (stack.getItem() == ModItems.manaResource && stack.getItemDamage() == Constants.MANARESOURCE_META_TERRASTEEL) { + + TileEntity outputEntity = worldObj.getTileEntity(xCoord, yCoord - 1, zCoord); + if (outputEntity instanceof IInventory outputInventory) { + ItemStack remainingItems = InventoryHelper.pushToInventory(outputInventory, stack); + inventoryHandler.setInventorySlotContents(DRAIN_SLOT, remainingItems); + } + + drainSize = safeSizeInSlot(DRAIN_SLOT); + markDirty(); + } + } + + // check consuming state and waste mana + isPumping = drainSize > 0 || ingredient1size > 0 || ingredient2size > 0 || ingredient3size > 0; + if (craftingMana > currentManaThreshold) { + craftingMana = currentManaThreshold; + markDirty(); + } + + if(oldPumping != isPumping) { + sparkCycleRemaining = 0; + updateEntitySparks(); + } + + if ( + oldPumping != isPumping || + oldThreshold != currentManaThreshold || + oldCraftingMana != craftingMana || + oldStoredMana != storedMana + ) { + markTEForSharing(false); + } + } + + protected int safeSizeInSlot(int slot) { + ItemStack stack = inventoryHandler.getStackInSlot(slot); + if (stack == null) return 0; + return stack.stackSize; + } + + @Override + public ItemStack getWrenchDrop(EntityPlayer entityPlayer) { + return new ItemStack(BHBlocks.autoPlate); + } + + // Brock breaking + public void dropItems(World world, int x, int y, int z) { + InventoryHelper.dropAllItems(world, x, y, z, inventoryHandler); + } + + public boolean onWanded(EntityPlayer wandUser) { + this.facing = Facing2D.fromIndex((worldObj.getBlockMetadata(xCoord, yCoord, zCoord) >> 1) & 3); + + markTEForSharing(true); + if (!isOnline) { + Exception error = structure.checkEntireStructure(worldObj, xCoord, yCoord, zCoord, this.facing); + if (error != null) { + boolean handled = MultiblockHelper.handleFailedStructure(worldObj, wandUser, error); + return false; + } + + if (storedMana < ACTIVATE_MANA) { + return false; + } + + storedMana -= ACTIVATE_MANA; + craftingMana = 0; + isOnline = true; + worldObj.setBlockMetadataWithNotify(xCoord, yCoord, zCoord, 1 + facing.index * 2, MC_BLOCK_UPDATE + MC_BLOCK_SEND_TO_CLIENT); + markDirty(); + return true; + } else if (wandUser.isSneaking()) { + isOnline = false; + worldObj.setBlockMetadataWithNotify(xCoord, yCoord, zCoord, facing.index * 2, MC_BLOCK_UPDATE + MC_BLOCK_SEND_TO_CLIENT); + markDirty(); + return true; + } else { + return true; + } + } + + // Mana HUD + + @SideOnly(Side.CLIENT) + public void renderHUD(Minecraft mc, ScaledResolution res) { + System.out.println(String.format("Plate HUD: %d/%d/%d/%d %b->%d/%d", safeSizeInSlot(0), safeSizeInSlot(1), safeSizeInSlot(2), safeSizeInSlot(3), isPumping, craftingMana, currentManaThreshold)); + if (!isPumping) { + ChargeState state = ChargeState.genState(isOnline, storedMana, ACTIVATE_MANA); + String tooltip = state.getLocalisedHudString(BHBlocks.autoPlate); + HUDHandler.drawSimpleManaHUD(state.color, storedMana, MANA_BUFFER, tooltip, res); + } else if (currentManaThreshold == 0) { + // failing state + String tooltip = StatCollector.translateToLocal(BHBlocks.autoPlate.getUnlocalizedName() + ".hud.crashing"); + HUDHandler.drawSimpleManaHUD(0xE0A044, MANA_BUFFER, MANA_BUFFER, tooltip, res); + } else { + // crafting state + String tooltip = StatCollector.translateToLocal(BHBlocks.autoPlate.getUnlocalizedName() + ".hud.collecting"); + HUDHandler.drawSimpleManaHUD(0xE0A044, (int)craftingMana, (int)currentManaThreshold, tooltip, res); + } + } + + + // Persistence + private static final String KEY_INVENTORY = "inv"; + private static final String KEY_CRAFT_MANA = "mana_c"; + private static final String KEY_PUMPING = "pump"; + private static final String KEY_THRESHOLD = "mana_th"; + + public void writeCustomNBT(NBTTagCompound compound) { + super.writeCustomNBT(compound); + compound.setTag(KEY_INVENTORY, InventoryHelper.saveInventoryToNBT(inventoryHandler)); + compound.setLong(KEY_CRAFT_MANA, craftingMana); + compound.setLong(KEY_THRESHOLD, currentManaThreshold); + compound.setBoolean(KEY_PUMPING, isPumping); + System.out.println("Encode NBT - client=" + worldObj.isRemote); + } + + public void readCustomNBT(NBTTagCompound compound) { + super.readCustomNBT(compound); + InventoryHelper.readInventoryFromNBT(inventoryHandler, compound.getCompoundTag(KEY_INVENTORY)); + craftingMana = compound.getLong(KEY_CRAFT_MANA); + currentManaThreshold = compound.getLong(KEY_THRESHOLD); + isPumping = compound.getBoolean(KEY_PUMPING); + System.out.println("Decode NBT - client=" + worldObj.isRemote); + } + + + // IInventory + @Override + public int getSizeInventory() { + return inventoryHandler.getSizeInventory(); + } + @Override + public ItemStack getStackInSlot(int slotIn) { + return inventoryHandler.getStackInSlot(slotIn); + } + @Override + public ItemStack decrStackSize(int index, int count) { + return inventoryHandler.decrStackSize(index, count); + } + @Override + public ItemStack getStackInSlotOnClosing(int index) { + return inventoryHandler.getStackInSlotOnClosing(index); + } + @Override + public void setInventorySlotContents(int index, ItemStack stack) { + inventoryHandler.setInventorySlotContents(index, stack); + } + @Override + public String getInventoryName() { + return inventoryHandler.getInventoryName(); + } + @Override + public boolean hasCustomInventoryName() { + return inventoryHandler.hasCustomInventoryName(); + } + @Override + public int getInventoryStackLimit() { + return inventoryHandler.getInventoryStackLimit(); + } + @Override + public boolean isUseableByPlayer(EntityPlayer player) { + return inventoryHandler.isUseableByPlayer(player); + } + @Override + public void openInventory() { + inventoryHandler.openInventory(); // no-op + } + @Override + public void closeInventory() { + inventoryHandler.closeInventory(); // no-op + } + @Override + public boolean isItemValidForSlot(int index, ItemStack stack) { + if (stack == null) return false; + Item item = stack.getItem(); + if (item == null) return false; + int meta = stack.getItemDamage(); + + if (item == ModItems.manaResource && meta == Constants.MANARESOURCE_META_DIAMOND) { + return index == 0; + } else if (item == ModItems.manaResource && meta == Constants.MANARESOURCE_META_MANASTEEL) { + return index == 1; + } else if (item == ModItems.manaResource && meta == Constants.MANARESOURCE_META_PEARL) { + return index == 2; + } else { + return index == 3; // output slot + } + } + + // IInvBasic + @Override + public void onInventoryChanged(InventoryBasic p_76316_1_) { + markDirty(); + } +} diff --git a/src/main/java/net/fuzzycraft/botanichorizons/util/ChargeState.java b/src/main/java/net/fuzzycraft/botanichorizons/util/ChargeState.java index ac177c1..57b0c1c 100644 --- a/src/main/java/net/fuzzycraft/botanichorizons/util/ChargeState.java +++ b/src/main/java/net/fuzzycraft/botanichorizons/util/ChargeState.java @@ -51,5 +51,4 @@ public String getLocalisedHudString(ItemStack stack) { } } - } diff --git a/src/main/java/net/fuzzycraft/botanichorizons/util/InventoryHelper.java b/src/main/java/net/fuzzycraft/botanichorizons/util/InventoryHelper.java index c179183..7aab682 100644 --- a/src/main/java/net/fuzzycraft/botanichorizons/util/InventoryHelper.java +++ b/src/main/java/net/fuzzycraft/botanichorizons/util/InventoryHelper.java @@ -1,9 +1,11 @@ package net.fuzzycraft.botanichorizons.util; +import cofh.api.inventory.IInventoryHandler; import net.minecraft.entity.item.EntityItem; import net.minecraft.inventory.IInventory; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.tileentity.TileEntity; import net.minecraft.world.World; import net.minecraftforge.oredict.OreDictionary; @@ -71,6 +73,57 @@ public static int itemsToMove(@Nonnull ItemStack target, @Nonnull ItemStack sour return 0; } + public static void pushInventoryToWorldDown(TileEntity tileEntity, World worldObj, IInventory inventoryHandler, int startSlot, int endSlot) { + if (tileEntity.yCoord < 1) return; + + for (int slot = startSlot; slot < endSlot; slot++) { + final ItemStack stack = inventoryHandler.getStackInSlot(slot); + if (stack == null || stack.getItem() == null || stack.stackSize == 0) continue; + + TileEntity outputEntity = worldObj.getTileEntity(tileEntity.xCoord, tileEntity.yCoord - 1, tileEntity.zCoord); + if (outputEntity instanceof IInventory) { + IInventory outputInventory = (IInventory) outputEntity; + ItemStack remainingItems = InventoryHelper.pushToInventory(outputInventory, stack); + inventoryHandler.setInventorySlotContents(slot, remainingItems); + } else if (worldObj.isAirBlock(tileEntity.xCoord, tileEntity.yCoord - 1, tileEntity.zCoord)) { + // TODO: drop items in world + } + } + } + + public static void defragInventory(IInventory inventoryHandler, int startSlot, int endSlot) { + for (int checkSlot = startSlot + 1; checkSlot < endSlot; checkSlot++) { + ItemStack sourceStack = inventoryHandler.getStackInSlot(checkSlot); + if (sourceStack != null) { + boolean done = false; + for (int refSlot = startSlot; refSlot < checkSlot && !done; refSlot++) { + ItemStack destinationStack = inventoryHandler.getStackInSlot(refSlot); + if (destinationStack == null) { + inventoryHandler.setInventorySlotContents(refSlot, sourceStack); + inventoryHandler.setInventorySlotContents(checkSlot, null); + done = true; + } else { + final int itemsToMove = InventoryHelper.itemsToMove(destinationStack, sourceStack); + if (itemsToMove > 0) { + final ItemStack newDestinationStack = destinationStack.copy(); + final ItemStack newSourceStack = sourceStack.copy(); + newDestinationStack.stackSize += itemsToMove; + newSourceStack.stackSize -= itemsToMove; + inventoryHandler.setInventorySlotContents(refSlot, newDestinationStack); + if (newSourceStack.stackSize == 0) { + inventoryHandler.setInventorySlotContents(checkSlot, null); + done = true; + } else { + inventoryHandler.setInventorySlotContents(checkSlot, newSourceStack); + sourceStack = newSourceStack; + } + } + } + } + } + } + } + /** * Checks if items from source can be added to destination. Checks stack size for destination but not for source. * @param destination targeted ItemStack @@ -138,4 +191,18 @@ public static void setRandomDropDirection(EntityItem item, World world) { item.motionY = random.nextGaussian() * speed + 0.2F; item.motionZ = random.nextGaussian() * speed; } + + public static void dropAllItems(World world, int x, int y, int z, IInventory inventoryHandler) { + for (int slot = 0; slot < inventoryHandler.getSizeInventory(); slot++) { + ItemStack drop = inventoryHandler.getStackInSlot(slot); + + if (drop != null && drop.stackSize > 0) { + ItemStack copy = drop.copy(); + inventoryHandler.setInventorySlotContents(slot, null); + EntityItem entity = new EntityItem(world, (double)x + 0.5, (double)y + 0.5, (double)z + 0.5, copy); + InventoryHelper.setRandomDropDirection(entity, world); + world.spawnEntityInWorld(entity); + } + } +} }