From ee462721d4345bdb015a037778bf60dd62bdd6fe Mon Sep 17 00:00:00 2001 From: Marcel Sondaar Date: Sun, 12 Oct 2025 18:02:49 +0200 Subject: [PATCH 1/8] chore: create basic definitions for new multis --- .../botanichorizons/addons/BHBlocks.java | 15 +++ .../botanichorizons/addons/Multiblocks.java | 14 +++ .../addons/block/BlockAdvancedAltar.java | 105 +++++++++++++++++ .../addons/block/BlockAdvancedApothecary.java | 107 ++++++++++++++++++ .../addons/block/BlockAdvancedTerraPlate.java | 106 +++++++++++++++++ .../RecipeAutomationTileEntity.java | 20 ++++ .../addons/tileentity/TileAdvancedAltar.java | 42 +++++++ .../tileentity/TileAdvancedApothecary.java | 42 +++++++ .../tileentity/TileAdvancedTerraPlate.java | 54 +++++++++ 9 files changed, 505 insertions(+) create mode 100644 src/main/java/net/fuzzycraft/botanichorizons/addons/block/BlockAdvancedAltar.java create mode 100644 src/main/java/net/fuzzycraft/botanichorizons/addons/block/BlockAdvancedApothecary.java create mode 100644 src/main/java/net/fuzzycraft/botanichorizons/addons/block/BlockAdvancedTerraPlate.java create mode 100644 src/main/java/net/fuzzycraft/botanichorizons/addons/tileentity/RecipeAutomationTileEntity.java create mode 100644 src/main/java/net/fuzzycraft/botanichorizons/addons/tileentity/TileAdvancedAltar.java create mode 100644 src/main/java/net/fuzzycraft/botanichorizons/addons/tileentity/TileAdvancedApothecary.java create mode 100644 src/main/java/net/fuzzycraft/botanichorizons/addons/tileentity/TileAdvancedTerraPlate.java 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..878942a 100644 --- a/src/main/java/net/fuzzycraft/botanichorizons/addons/Multiblocks.java +++ b/src/main/java/net/fuzzycraft/botanichorizons/addons/Multiblocks.java @@ -25,6 +25,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 +99,19 @@ public static void init() { " ", " lllll ", " llgll ", " lglgl ", " llgll ", " lllll ", " " } ); + + // For new multiblocks + + MultiblockBuilder tempBuilder = new MultiblockBuilder(); + builder.setRootCharacter("x"); + builder.addCheck("w", livingWood); + builder.addCheck("r", livingRock); + placeholder = tempBuilder.buildForMap( + new String[] { + "rwr", "w w", "rxr" + } + ); + } public static void postInit() { 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/RecipeAutomationTileEntity.java b/src/main/java/net/fuzzycraft/botanichorizons/addons/tileentity/RecipeAutomationTileEntity.java new file mode 100644 index 0000000..32dbdd0 --- /dev/null +++ b/src/main/java/net/fuzzycraft/botanichorizons/addons/tileentity/RecipeAutomationTileEntity.java @@ -0,0 +1,20 @@ +package net.fuzzycraft.botanichorizons.addons.tileentity; + +import net.fuzzycraft.botanichorizons.util.multiblock.MultiblockHelper; +import net.minecraft.world.World; + +public abstract class RecipeAutomationTileEntity extends AutomationTileEntity { + public RecipeAutomationTileEntity(MultiblockHelper structure) { + super(structure); + } + + @Override + protected void updateEntityCrafting() { + + } + + // Brock breaking + public void dropItems(World world, int x, int y, int z) { + + } +} 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..626c8c6 --- /dev/null +++ b/src/main/java/net/fuzzycraft/botanichorizons/addons/tileentity/TileAdvancedAltar.java @@ -0,0 +1,42 @@ +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.minecraft.client.Minecraft; +import net.minecraft.client.gui.ScaledResolution; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.ItemStack; + +public class TileAdvancedAltar extends RecipeAutomationTileEntity { + + public static int MAX_PARALLELS = 16; + + public TileAdvancedAltar() { + super(Multiblocks.placeholder); + } + + @Override + public int getManaMaximum() { + return 0; + } + + @Override + public ItemStack getWrenchDrop(EntityPlayer entityPlayer) { + return new ItemStack(BHBlocks.autoAltar); + } + + public boolean onWanded(EntityPlayer wandUser) { + return false; + } + + // Mana HUD + + @SideOnly(Side.CLIENT) + public void renderHUD(Minecraft mc, ScaledResolution res) { + //ChargeState state = ChargeState.genState(isOnline, storedMana, ACTIVATE_MANA); + //String tooltip = state.getLocalisedHudString(BHBlocks.autoAltar); + //HUDHandler.drawSimpleManaHUD(state.color, storedMana, MANA_CAPACITY, tooltip, res); + } +} 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..ac2ac52 --- /dev/null +++ b/src/main/java/net/fuzzycraft/botanichorizons/addons/tileentity/TileAdvancedApothecary.java @@ -0,0 +1,42 @@ +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.minecraft.client.Minecraft; +import net.minecraft.client.gui.ScaledResolution; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.ItemStack; + +public class TileAdvancedApothecary extends RecipeAutomationTileEntity { + + public static int MAX_PARALLELS = 8; + + public TileAdvancedApothecary() { + super(Multiblocks.placeholder); + } + + @Override + public int getManaMaximum() { + return 0; + } + + @Override + public ItemStack getWrenchDrop(EntityPlayer entityPlayer) { + return new ItemStack(BHBlocks.autoApothecary); + } + + public boolean onWanded(EntityPlayer wandUser) { + return false; + } + + // Mana HUD + + @SideOnly(Side.CLIENT) + public void renderHUD(Minecraft mc, 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..ad42022 --- /dev/null +++ b/src/main/java/net/fuzzycraft/botanichorizons/addons/tileentity/TileAdvancedTerraPlate.java @@ -0,0 +1,54 @@ +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.minecraft.client.Minecraft; +import net.minecraft.client.gui.ScaledResolution; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.ItemStack; +import net.minecraft.world.World; +import vazkii.botania.client.core.handler.HUDHandler; + +public class TileAdvancedTerraPlate extends AutomationTileEntity { + public static int MAX_PARALLELS = 64; + + public TileAdvancedTerraPlate() { + super(Multiblocks.placeholder); + } + + @Override + public int getManaMaximum() { + return 0; + } + + @Override + protected void updateEntityCrafting() { + + } + + @Override + public ItemStack getWrenchDrop(EntityPlayer entityPlayer) { + return new ItemStack(BHBlocks.autoPlate); + } + + // Brock breaking + public void dropItems(World world, int x, int y, int z) { + + } + + public boolean onWanded(EntityPlayer wandUser) { + return false; + } + + // Mana HUD + + @SideOnly(Side.CLIENT) + public void renderHUD(Minecraft mc, ScaledResolution res) { + //ChargeState state = ChargeState.genState(isOnline, storedMana, ACTIVATE_MANA); + //String tooltip = state.getLocalisedHudString(BHBlocks.autoPlate); + //HUDHandler.drawSimpleManaHUD(state.color, storedMana, MANA_CAPACITY, tooltip, res); + } +} From f43e32100881af3d6e82fc802dfb285c72a3c161 Mon Sep 17 00:00:00 2001 From: Marcel Sondaar Date: Sat, 1 Nov 2025 09:10:09 +0100 Subject: [PATCH 2/8] wip: Automated terrasteel --- .../botanichorizons/addons/Multiblocks.java | 13 +- .../tileentity/AutomationTileEntity.java | 9 +- .../SimpleAutomationTileEntity.java | 12 +- .../tileentity/TileAdvancedTerraPlate.java | 287 +++++++++++++++++- .../botanichorizons/util/ChargeState.java | 1 - .../botanichorizons/util/InventoryHelper.java | 15 + 6 files changed, 314 insertions(+), 23 deletions(-) diff --git a/src/main/java/net/fuzzycraft/botanichorizons/addons/Multiblocks.java b/src/main/java/net/fuzzycraft/botanichorizons/addons/Multiblocks.java index 878942a..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; @@ -103,9 +106,9 @@ public static void init() { // For new multiblocks MultiblockBuilder tempBuilder = new MultiblockBuilder(); - builder.setRootCharacter("x"); - builder.addCheck("w", livingWood); - builder.addCheck("r", livingRock); + tempBuilder.setRootCharacter("x"); + tempBuilder.addCheck("w", livingWood); + tempBuilder.addCheck("r", livingRock); placeholder = tempBuilder.buildForMap( new String[] { "rwr", "w w", "rxr" @@ -120,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/tileentity/AutomationTileEntity.java b/src/main/java/net/fuzzycraft/botanichorizons/addons/tileentity/AutomationTileEntity.java index ef70366..a8e904c 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 @@ -115,6 +115,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/SimpleAutomationTileEntity.java b/src/main/java/net/fuzzycraft/botanichorizons/addons/tileentity/SimpleAutomationTileEntity.java index 66c32ad..fe25bb0 100644 --- a/src/main/java/net/fuzzycraft/botanichorizons/addons/tileentity/SimpleAutomationTileEntity.java +++ b/src/main/java/net/fuzzycraft/botanichorizons/addons/tileentity/SimpleAutomationTileEntity.java @@ -226,16 +226,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/TileAdvancedTerraPlate.java b/src/main/java/net/fuzzycraft/botanichorizons/addons/tileentity/TileAdvancedTerraPlate.java index ad42022..e3e3638 100644 --- a/src/main/java/net/fuzzycraft/botanichorizons/addons/tileentity/TileAdvancedTerraPlate.java +++ b/src/main/java/net/fuzzycraft/botanichorizons/addons/tileentity/TileAdvancedTerraPlate.java @@ -5,28 +5,168 @@ 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; -public class TileAdvancedTerraPlate extends AutomationTileEntity { +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() { - return 0; + // 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 @@ -36,19 +176,152 @@ public ItemStack getWrenchDrop(EntityPlayer entityPlayer) { // 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) { - return false; + 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) { - //ChargeState state = ChargeState.genState(isOnline, storedMana, ACTIVATE_MANA); - //String tooltip = state.getLocalisedHudString(BHBlocks.autoPlate); - //HUDHandler.drawSimpleManaHUD(state.color, storedMana, MANA_CAPACITY, tooltip, 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..b94937f 100644 --- a/src/main/java/net/fuzzycraft/botanichorizons/util/InventoryHelper.java +++ b/src/main/java/net/fuzzycraft/botanichorizons/util/InventoryHelper.java @@ -1,5 +1,6 @@ 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; @@ -138,4 +139,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); + } + } +} } From dc1eaca7b1a5a704adc40554072577f77e6d9c81 Mon Sep 17 00:00:00 2001 From: Marcel Sondaar Date: Tue, 3 Mar 2026 21:28:27 +0100 Subject: [PATCH 3/8] chore: WIP recipe handling for multiblocks --- .../RecipeAutomationTileEntity.java | 316 +++++++++++++++++- .../SimpleAutomationTileEntity.java | 47 +-- .../addons/tileentity/TileAdvancedAltar.java | 50 ++- .../tileentity/TileAdvancedApothecary.java | 54 ++- .../botanichorizons/util/InventoryHelper.java | 52 +++ 5 files changed, 466 insertions(+), 53 deletions(-) diff --git a/src/main/java/net/fuzzycraft/botanichorizons/addons/tileentity/RecipeAutomationTileEntity.java b/src/main/java/net/fuzzycraft/botanichorizons/addons/tileentity/RecipeAutomationTileEntity.java index 32dbdd0..adb8a18 100644 --- a/src/main/java/net/fuzzycraft/botanichorizons/addons/tileentity/RecipeAutomationTileEntity.java +++ b/src/main/java/net/fuzzycraft/botanichorizons/addons/tileentity/RecipeAutomationTileEntity.java @@ -1,20 +1,330 @@ package net.fuzzycraft.botanichorizons.addons.tileentity; +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.world.World; +import scala.Int; +import vazkii.botania.api.recipe.RecipePetals; -public abstract class RecipeAutomationTileEntity extends AutomationTileEntity { - public RecipeAutomationTileEntity(MultiblockHelper structure) { +import javax.annotation.Nonnull; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +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 final int inputSize; + public final int outputSize; + + public final InventoryBasic inventoryHandler; + private final HashSet possibleRecipes = new HashSet<>(); + private T setRecipe = null; // Are we limited to 1 recipe + + public RecipeAutomationTileEntity(@Nonnull MultiblockHelper structure) { super(structure); + inputSize = maxRecipeWidth(); + outputSize = 2; // increase later to handle rune returns + inventoryHandler = new InventoryBasic("name", false, inputSize + outputSize); + + possibleRecipes.addAll(getAllRecipes()); } @Override protected void updateEntityCrafting() { + if (setRecipe == null) { + // TODO: in progress cleanups + } else { + + } + } + + 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; + } + } + + // 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) { + 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 + } + + 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++) { + for (int inputSlot = 0; inputSlot < inputSize; inputSlot++) { + ItemStack inputStack = inventoryHandler.getStackInSlot(inputSlot); + List mappings = new ArrayList<>(); + if (inputStack != null && inputStack.stackSize != 0) { + if (InventoryHelper.isIngredient(inputStack, ingredientList.get(recipeSlot))) { + mappings.add(inputSlot); + } + } + applyMap[recipeSlot] = mappings; + } + } + 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; + } + } + 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) { + 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; + } + + // 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 fe25bb0..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); } diff --git a/src/main/java/net/fuzzycraft/botanichorizons/addons/tileentity/TileAdvancedAltar.java b/src/main/java/net/fuzzycraft/botanichorizons/addons/tileentity/TileAdvancedAltar.java index 626c8c6..e9d8410 100644 --- a/src/main/java/net/fuzzycraft/botanichorizons/addons/tileentity/TileAdvancedAltar.java +++ b/src/main/java/net/fuzzycraft/botanichorizons/addons/tileentity/TileAdvancedAltar.java @@ -8,10 +8,20 @@ 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; -public class TileAdvancedAltar extends RecipeAutomationTileEntity { +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; - public static int MAX_PARALLELS = 16; +public class TileAdvancedAltar extends RecipeAutomationTileEntity { + + public static final int MAX_PARALLELS = 16; + public static int cachedMaxRecipeWidth = 0; public TileAdvancedAltar() { super(Multiblocks.placeholder); @@ -39,4 +49,40 @@ public void renderHUD(Minecraft mc, ScaledResolution res) { //String tooltip = state.getLocalisedHudString(BHBlocks.autoAltar); //HUDHandler.drawSimpleManaHUD(state.color, storedMana, MANA_CAPACITY, 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) { + return recipe.getInputs(); + } + + @Override + public List getOutputs(@NotNull RecipeRuneAltar recipe) { + List outputs = new ArrayList<>(1); + outputs.add(recipe.getOutput()); + // TODO: return runes + return outputs; + } + } diff --git a/src/main/java/net/fuzzycraft/botanichorizons/addons/tileentity/TileAdvancedApothecary.java b/src/main/java/net/fuzzycraft/botanichorizons/addons/tileentity/TileAdvancedApothecary.java index ac2ac52..d8c1896 100644 --- a/src/main/java/net/fuzzycraft/botanichorizons/addons/tileentity/TileAdvancedApothecary.java +++ b/src/main/java/net/fuzzycraft/botanichorizons/addons/tileentity/TileAdvancedApothecary.java @@ -4,14 +4,24 @@ 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; -public class TileAdvancedApothecary extends RecipeAutomationTileEntity { +import javax.annotation.Nonnull; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; - public static int MAX_PARALLELS = 8; +public class TileAdvancedApothecary extends RecipeAutomationTileEntity { + + public static final int MAX_PARALLELS = 8; + public static int cachedMaxRecipeWidth = 0; public TileAdvancedApothecary() { super(Multiblocks.placeholder); @@ -31,10 +41,48 @@ 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; + } + + // Mana HUD @SideOnly(Side.CLIENT) - public void renderHUD(Minecraft mc, ScaledResolution res) { + 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/util/InventoryHelper.java b/src/main/java/net/fuzzycraft/botanichorizons/util/InventoryHelper.java index b94937f..7aab682 100644 --- a/src/main/java/net/fuzzycraft/botanichorizons/util/InventoryHelper.java +++ b/src/main/java/net/fuzzycraft/botanichorizons/util/InventoryHelper.java @@ -5,6 +5,7 @@ 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; @@ -72,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 From da9a161714d16c7c7c061d6afc7553795b0b8ee5 Mon Sep 17 00:00:00 2001 From: Marcel Sondaar Date: Tue, 14 Apr 2026 20:27:52 +0200 Subject: [PATCH 4/8] fix: correct broken checks --- .../tileentity/AutomationTileEntity.java | 1 + .../RecipeAutomationTileEntity.java | 105 ++++++++++++++++-- .../addons/tileentity/TileAdvancedAltar.java | 41 +++++-- .../tileentity/TileAdvancedApothecary.java | 16 ++- 4 files changed, 144 insertions(+), 19 deletions(-) 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 a8e904c..9252078 100644 --- a/src/main/java/net/fuzzycraft/botanichorizons/addons/tileentity/AutomationTileEntity.java +++ b/src/main/java/net/fuzzycraft/botanichorizons/addons/tileentity/AutomationTileEntity.java @@ -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(); } diff --git a/src/main/java/net/fuzzycraft/botanichorizons/addons/tileentity/RecipeAutomationTileEntity.java b/src/main/java/net/fuzzycraft/botanichorizons/addons/tileentity/RecipeAutomationTileEntity.java index adb8a18..5002819 100644 --- a/src/main/java/net/fuzzycraft/botanichorizons/addons/tileentity/RecipeAutomationTileEntity.java +++ b/src/main/java/net/fuzzycraft/botanichorizons/addons/tileentity/RecipeAutomationTileEntity.java @@ -1,5 +1,6 @@ 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; @@ -7,9 +8,8 @@ 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 scala.Int; -import vazkii.botania.api.recipe.RecipePetals; import javax.annotation.Nonnull; import java.util.ArrayList; @@ -17,7 +17,6 @@ import java.util.HashSet; import java.util.Iterator; import java.util.List; -import java.util.Set; public abstract class RecipeAutomationTileEntity extends AutomationTileEntity implements IInventory, IInvBasic { @@ -25,29 +24,71 @@ public abstract class RecipeAutomationTileEntity extends AutomationTileEntity 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<>(); - private T setRecipe = null; // Are we limited to 1 recipe + protected T setRecipe = null; // Are we limited to 1 recipe + protected boolean clientRecipeSet = false; + protected int lastCheckedMana = 0; - public RecipeAutomationTileEntity(@Nonnull MultiblockHelper structure) { + 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(); - outputSize = 2; // increase later to handle rune returns + 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) { - // TODO: in progress cleanups + 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); + } + } } } @@ -105,6 +146,7 @@ protected int getCopiesCraftable() { 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 @@ -113,6 +155,7 @@ protected int getCopiesCraftable() { 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) { @@ -140,6 +183,7 @@ protected int getCopiesCraftable() { searchMaximum = attempt - 1; } attempt = (searchMaximum + searchMinimum + 1) / 2; // round up + //FMLLog.info("iteration result (%b, %d, %d)", success, searchMinimum, searchMaximum); } return searchMinimum; @@ -184,17 +228,20 @@ 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); - List mappings = new ArrayList<>(); 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); } } - applyMap[recipeSlot] = mappings; } + //FMLLog.info("cumulative mapping: %s, %s", mappings.toString(), Arrays.deepToString(applyMap)); + applyMap[recipeSlot] = mappings; } + //FMLLog.info("recipe mapping: %s", Arrays.deepToString(applyMap)); return applyMap; } @@ -207,8 +254,10 @@ private int[] computeItemsAvailable() { 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; } @@ -238,6 +287,7 @@ private void resetPossibleRecipes() { } private void truncateRecipes(@Nonnull ItemStack changedStack) { + int currentCount = possibleRecipes.size(); Iterator iterator = possibleRecipes.iterator(); while (iterator.hasNext()) { T recipe = iterator.next(); @@ -246,6 +296,43 @@ private void truncateRecipes(@Nonnull ItemStack changedStack) { } } 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 diff --git a/src/main/java/net/fuzzycraft/botanichorizons/addons/tileentity/TileAdvancedAltar.java b/src/main/java/net/fuzzycraft/botanichorizons/addons/tileentity/TileAdvancedAltar.java index e9d8410..4575a61 100644 --- a/src/main/java/net/fuzzycraft/botanichorizons/addons/tileentity/TileAdvancedAltar.java +++ b/src/main/java/net/fuzzycraft/botanichorizons/addons/tileentity/TileAdvancedAltar.java @@ -4,14 +4,19 @@ 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; @@ -21,15 +26,17 @@ 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); + super(Multiblocks.placeholder, 5); } @Override public int getManaMaximum() { - return 0; + return setRecipe == null ? IDLE_MANA : IDLE_MANA + lastCheckedMana; } @Override @@ -43,11 +50,19 @@ public boolean onWanded(EntityPlayer wandUser) { // Mana HUD + // Mana HUD + @SideOnly(Side.CLIENT) public void renderHUD(Minecraft mc, ScaledResolution res) { - //ChargeState state = ChargeState.genState(isOnline, storedMana, ACTIVATE_MANA); - //String tooltip = state.getLocalisedHudString(BHBlocks.autoAltar); - //HUDHandler.drawSimpleManaHUD(state.color, storedMana, MANA_CAPACITY, tooltip, 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 @@ -74,15 +89,27 @@ public int maxRecipeWidth() { @Override public List getInputs(@NotNull RecipeRuneAltar recipe) { - return recipe.getInputs(); + 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()); - // TODO: return runes + 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 index d8c1896..24851be 100644 --- a/src/main/java/net/fuzzycraft/botanichorizons/addons/tileentity/TileAdvancedApothecary.java +++ b/src/main/java/net/fuzzycraft/botanichorizons/addons/tileentity/TileAdvancedApothecary.java @@ -12,6 +12,7 @@ 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; @@ -21,15 +22,16 @@ 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); + super(Multiblocks.placeholder, 2); } @Override public int getManaMaximum() { - return 0; + return MAX_PARALLELS * RECIPE_MANA; } @Override @@ -41,7 +43,6 @@ public boolean onWanded(EntityPlayer wandUser) { return false; } - // Recipe wrangling @Override @@ -78,6 +79,15 @@ public List getOutputs(@NotNull RecipePetals recipe) { 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 From 42d7aa2ede5c46e604edd6461e7a0c1bad42bb79 Mon Sep 17 00:00:00 2001 From: Marcel Sondaar Date: Tue, 14 Apr 2026 21:22:03 +0200 Subject: [PATCH 5/8] chore: Sort and de-stub interfaces, implement wand starting --- .../RecipeAutomationTileEntity.java | 2 - .../addons/tileentity/TileAdvancedAltar.java | 79 +++++++++++++------ .../tileentity/TileAdvancedApothecary.java | 56 ++++++++++--- 3 files changed, 97 insertions(+), 40 deletions(-) diff --git a/src/main/java/net/fuzzycraft/botanichorizons/addons/tileentity/RecipeAutomationTileEntity.java b/src/main/java/net/fuzzycraft/botanichorizons/addons/tileentity/RecipeAutomationTileEntity.java index 5002819..7c65d54 100644 --- a/src/main/java/net/fuzzycraft/botanichorizons/addons/tileentity/RecipeAutomationTileEntity.java +++ b/src/main/java/net/fuzzycraft/botanichorizons/addons/tileentity/RecipeAutomationTileEntity.java @@ -46,8 +46,6 @@ public RecipeAutomationTileEntity(@Nonnull final MultiblockHelper structure, fin inventoryHandler = new InventoryBasic("name", false, inputSize + outputSize); possibleRecipes.addAll(getAllRecipes()); - - isOnline = true; // TODO: apply block activation specifics } @Override diff --git a/src/main/java/net/fuzzycraft/botanichorizons/addons/tileentity/TileAdvancedAltar.java b/src/main/java/net/fuzzycraft/botanichorizons/addons/tileentity/TileAdvancedAltar.java index 4575a61..bc41eae 100644 --- a/src/main/java/net/fuzzycraft/botanichorizons/addons/tileentity/TileAdvancedAltar.java +++ b/src/main/java/net/fuzzycraft/botanichorizons/addons/tileentity/TileAdvancedAltar.java @@ -6,6 +6,8 @@ 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.multiblock.MultiblockHelper; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.ScaledResolution; import net.minecraft.entity.player.EntityPlayer; @@ -23,6 +25,9 @@ import java.util.Collections; import java.util.List; +import static net.fuzzycraft.botanichorizons.util.Constants.MC_BLOCK_SEND_TO_CLIENT; +import static net.fuzzycraft.botanichorizons.util.Constants.MC_BLOCK_UPDATE; + public class TileAdvancedAltar extends RecipeAutomationTileEntity { public static final int MAX_PARALLELS = 16; @@ -39,32 +44,6 @@ 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; @@ -112,4 +91,52 @@ public int getManaRequired(@NotNull RecipeRuneAltar recipe, int copies) { public int getAvailableParallels(@NotNull RecipeRuneAltar recipe) { return MAX_PARALLELS; } + + // 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); + } + } + + // IWrenchable + + @Override + public ItemStack getWrenchDrop(EntityPlayer entityPlayer) { + return new ItemStack(BHBlocks.autoAltar); + } + + + // IWandable delegate + + public boolean onWanded(EntityPlayer wandUser) { + this.facing = Facing2D.fromIndex((worldObj.getBlockMetadata(xCoord, yCoord, zCoord) >> 1) & 3); + + 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; + isOnline = true; + worldObj.setBlockMetadataWithNotify(xCoord, yCoord, zCoord, 1 + facing.index * 2, MC_BLOCK_UPDATE + MC_BLOCK_SEND_TO_CLIENT); + markDirty(); + return true; + } + return false; + } } diff --git a/src/main/java/net/fuzzycraft/botanichorizons/addons/tileentity/TileAdvancedApothecary.java b/src/main/java/net/fuzzycraft/botanichorizons/addons/tileentity/TileAdvancedApothecary.java index 24851be..e35c3c0 100644 --- a/src/main/java/net/fuzzycraft/botanichorizons/addons/tileentity/TileAdvancedApothecary.java +++ b/src/main/java/net/fuzzycraft/botanichorizons/addons/tileentity/TileAdvancedApothecary.java @@ -4,7 +4,10 @@ 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.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; @@ -13,16 +16,21 @@ 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 javax.annotation.Nonnull; import java.util.ArrayList; import java.util.Collection; import java.util.List; +import static net.fuzzycraft.botanichorizons.util.Constants.MC_BLOCK_SEND_TO_CLIENT; +import static net.fuzzycraft.botanichorizons.util.Constants.MC_BLOCK_UPDATE; + public class TileAdvancedApothecary extends RecipeAutomationTileEntity { public static final int MAX_PARALLELS = 8; public static final int RECIPE_MANA = 1000; + public static final int ACTIVATE_MANA = 5000; public static int cachedMaxRecipeWidth = 0; public TileAdvancedApothecary() { @@ -34,15 +42,6 @@ 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 @@ -93,8 +92,41 @@ public int getAvailableParallels(@NotNull RecipePetals recipe) { @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); + ChargeState state = ChargeState.genState(isOnline, storedMana, ACTIVATE_MANA); + String tooltip = state.getLocalisedHudString(BHBlocks.autoApothecary); + HUDHandler.drawSimpleManaHUD(state.color, storedMana, getManaMaximum(), tooltip, res); + } + + // IWrenchable + + @Override + public ItemStack getWrenchDrop(EntityPlayer entityPlayer) { + return new ItemStack(BHBlocks.autoApothecary); + } + + + // IWandable delegate + + public boolean onWanded(EntityPlayer wandUser) { + this.facing = Facing2D.fromIndex((worldObj.getBlockMetadata(xCoord, yCoord, zCoord) >> 1) & 3); + + 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; + isOnline = true; + worldObj.setBlockMetadataWithNotify(xCoord, yCoord, zCoord, 1 + facing.index * 2, MC_BLOCK_UPDATE + MC_BLOCK_SEND_TO_CLIENT); + markDirty(); + return true; + } + return false; } } From d056f01cc88fd126eaea9485361bfb9acd46366c Mon Sep 17 00:00:00 2001 From: Marcel Sondaar Date: Tue, 2 Jun 2026 20:21:53 +0200 Subject: [PATCH 6/8] chore: Implement multiblock designs and apply fixes to the Holo Projector support --- .../botanichorizons/addons/Multiblocks.java | 89 ++++++++++++++++++- .../addons/tileentity/TileAdvancedAltar.java | 2 +- .../tileentity/TileAdvancedApothecary.java | 5 +- .../tileentity/TileAdvancedTerraPlate.java | 2 +- .../botanichorizons/util/Constants.java | 1 + .../util/multiblock/BasicBlockCheck.java | 3 + .../util/multiblock/MetaBlockCheck.java | 4 + .../util/multiblock/MultiblockHelper.java | 7 +- .../util/multiblock/WrongBlockException.java | 8 +- .../botanichorizons/util/nei/NEIConfig.java | 2 + .../structurelib/HoloProjectorSupport.java | 8 +- 11 files changed, 115 insertions(+), 16 deletions(-) diff --git a/src/main/java/net/fuzzycraft/botanichorizons/addons/Multiblocks.java b/src/main/java/net/fuzzycraft/botanichorizons/addons/Multiblocks.java index 8e5a2cd..cf14685 100644 --- a/src/main/java/net/fuzzycraft/botanichorizons/addons/Multiblocks.java +++ b/src/main/java/net/fuzzycraft/botanichorizons/addons/Multiblocks.java @@ -25,18 +25,31 @@ public final class Multiblocks { public static MultiblockHelper alfPortal; + public static MultiblockHelper altar; + public static MultiblockHelper apothecary; public static MultiblockHelper poolConjuration; public static MultiblockHelper poolAlchemy; public static MultiblockHelper poolInfusion; + public static MultiblockHelper terraPlate; + public static MultiblockHelper placeholder; public static void init() { MultiblockCheck air = new BasicBlockCheck(Blocks.air); MultiblockCheck livingWood = new MetaBlockCheck(ModBlocks.livingwood, Constants.LIVINGWOOD_META_BLOCK); MultiblockCheck livingRock = new MetaBlockCheck(ModBlocks.livingrock, Constants.LIVINGSTONE_META_BLOCK); + MultiblockCheck livingRockSlab = new MetaBlockCheck(ModFluffBlocks.livingrockSlab, 0); MultiblockCheck glimmerWood = new MetaBlockCheck(ModBlocks.livingwood, Constants.LIVINGWOOD_META_GLIMMERING); + MultiblockCheck manaPylon = new MetaBlockCheck(ModBlocks.pylon, Constants.PYLON_META_MANA); MultiblockCheck naturaPylon = new MetaBlockCheck(ModBlocks.pylon, Constants.PYLON_META_NATURA); MultiblockCheck manaPool = new BasicBlockCheck(ModBlocks.pool); + MultiblockCheck lapisBlock = new BasicBlockCheck(Blocks.lapis_block); + MultiblockCheck manaBlock = new MetaBlockCheck(ModBlocks.storage, Constants.STORAGE_META_MANASTEELBLOCK); + MultiblockCheck terraBlock = new MetaBlockCheck(ModBlocks.storage, Constants.STORAGE_META_ELEMENTIUMBLOCK); + MultiblockCheck dirt = new MetaBlockCheck(Blocks.dirt, 0); + MultiblockCheck melon = new BasicBlockCheck(Blocks.melon_block); + MultiblockCheck pumpkin = new BasicBlockCheck(Blocks.lit_pumpkin); + MultiblockCheck water = new BasicBlockCheck(Blocks.water); // Mana pool @@ -103,6 +116,76 @@ public static void init() { } ); + // Terrestrial Agglomeration plate + + MultiblockBuilder terraBuilder = new MultiblockBuilder(); + terraBuilder.setRootCharacter("x"); + terraBuilder.addCheck(".", air); + terraBuilder.addCheck("b", lapisBlock); + terraBuilder.addCheck("w", livingRock); + terraBuilder.addCheck("g", terraBlock); + terraBuilder.addCheck("p", manaPylon); + + terraPlate = terraBuilder.buildForMap( + new String[]{ + " ", " ", " ", " ... ", " ... ", " ... ", " ", " ", " " + }, + new String[]{ + " ", " p p ", " ", " bbb ", " bgb ", " bbb ", " ", " p p ", " " + }, + new String[]{ + "bbbwwwbbb", "bbbwwwbbb", "bbbwwwbbb", "wwwbbbwww", "wwwbbbwww", "wwwbbbwww", "bbbwwwbbb", "bbbwwwbbb", "bbbwxwbbb" + } + ); + + // Petal apothecary + + MultiblockBuilder apothecaryBuilder = new MultiblockBuilder(); + apothecaryBuilder.setRootCharacter("x"); + apothecaryBuilder.addCheck(".", air); + apothecaryBuilder.addCheck("w", livingWood); + apothecaryBuilder.addCheck("r", livingRock); + apothecaryBuilder.addCheck("W", water); + apothecaryBuilder.addCheck("d", dirt); + apothecaryBuilder.addCheck("P", pumpkin); + apothecaryBuilder.addCheck("M", melon); + + apothecary = apothecaryBuilder.buildForMap( + new String[]{ + "wrrrwrrrw", " ", " ", " ", " " + }, + new String[]{ + "wrrrwrrrw", " ", " P M ... ", " ... ", " " + }, + new String[]{ + "wrrrwrrrw", "rdddrWWWr", "rdddrWWWr", "rdddrWWWr", "rrrrxrrrr" + }, + new String[]{ + "wrrrwrrrw", " rrr rrr ", " rrr rrr ", " rrr rrr ", " " + } + ); + + // Altar + + MultiblockBuilder altarBuilder = new MultiblockBuilder(); + altarBuilder.setRootCharacter("x"); + altarBuilder.addCheck("r", livingRock); + altarBuilder.addCheck("s", livingRockSlab); + altarBuilder.addCheck("m", manaBlock); + + altar = altarBuilder.buildForMap( + new String[]{ + " ", " ", " ", " x ", " ", " ", " " + }, + new String[]{ + " ", " sssss ", " smrms ", " sr rs ", " smrms ", " sssss ", " " + }, + new String[]{ + "mrrrrrm", "r r", "r r", "r r", "r r", "r r", "mrrrrrm" + } + ); + + // For new multiblocks MultiblockBuilder tempBuilder = new MultiblockBuilder(); @@ -124,9 +207,9 @@ public static void postInit() { 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); + HoloProjectorSupport.registerOrientedWithStructureLib(apothecary, BHBlocks.autoApothecary, TileAdvancedApothecary.class); + HoloProjectorSupport.registerOrientedWithStructureLib(altar, BHBlocks.autoAltar, TileAdvancedAltar.class); + HoloProjectorSupport.registerOrientedWithStructureLib(terraPlate, BHBlocks.autoPlate, TileAdvancedTerraPlate.class); } } } diff --git a/src/main/java/net/fuzzycraft/botanichorizons/addons/tileentity/TileAdvancedAltar.java b/src/main/java/net/fuzzycraft/botanichorizons/addons/tileentity/TileAdvancedAltar.java index bc41eae..5b756b9 100644 --- a/src/main/java/net/fuzzycraft/botanichorizons/addons/tileentity/TileAdvancedAltar.java +++ b/src/main/java/net/fuzzycraft/botanichorizons/addons/tileentity/TileAdvancedAltar.java @@ -36,7 +36,7 @@ public class TileAdvancedAltar extends RecipeAutomationTileEntity void registerOrientedWithStructureLib(MultiblockHelper definition, Block controllerBlock, Class controllerTileClass) { final Map keys = new HashMap<>(); - int xMin = definition.blocks[0].dx, xMax = definition.blocks[0].dx; - int yMin = definition.blocks[0].dy, yMax = definition.blocks[0].dy; - int zMin = definition.blocks[0].dz, zMax = definition.blocks[0].dz; + int xMin = 0, xMax = 0; + int yMin = 0, yMax = 0; + int zMin = 0, zMax = 0; char counter = 'a'; @@ -69,7 +69,7 @@ public static void registerOrientedWithStru // pass 2: fill out the structure definition for (MultiblockStructure piece : definition.blocks) { final String key = keys.get(piece.check).toString(); - final int xPos = piece.dx - xMin; + final int xPos = xMax - piece.dx; final int yPos = piece.dy - yMin; final int zPos = piece.dz - zMin; final String old = structureString[zPos][yPos]; From 9907a9f37e64b876c8949de22ee30759f5b0aef9 Mon Sep 17 00:00:00 2001 From: Marcel Sondaar Date: Tue, 2 Jun 2026 20:22:25 +0200 Subject: [PATCH 7/8] chore: User-facing multiblock names and HUD states --- .../assets/botanichorizons/lang/en_GB.lang | 16 ++++++++++++++++ .../assets/botanichorizons/lang/en_US.lang | 15 +++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/src/main/resources/assets/botanichorizons/lang/en_GB.lang b/src/main/resources/assets/botanichorizons/lang/en_GB.lang index db615fd..380adc4 100644 --- a/src/main/resources/assets/botanichorizons/lang/en_GB.lang +++ b/src/main/resources/assets/botanichorizons/lang/en_GB.lang @@ -95,6 +95,22 @@ tile.botanichorizons.automatedConjurationPool.name=Fountain of Conjuration tile.botanichorizons.automatedConjurationPool.hud.empty=Fountain of Conjuration (needs mana) tile.botanichorizons.automatedConjurationPool.hud.idle=Fountain of Conjuration (unformed) tile.botanichorizons.automatedConjurationPool.hud.active=Fountain of Conjuration (online) +tile.botanichorizons.automatedApothecary.name=Industrial Grafting Station +tile.botanichorizons.automatedApothecary.hud.empty=Industrial Grafting Station (needs mana) +tile.botanichorizons.automatedApothecary.hud.idle=Industrial Grafting Station (unformed) +tile.botanichorizons.automatedApothecary.hud.active=Industrial Grafting Station (online) +tile.botanichorizons.automatedAltar.name=Synthetic Rune Infuser +tile.botanichorizons.automatedAltar.hud.empty=Synthetic Rune Infuser (needs mana) +tile.botanichorizons.automatedAltar.hud.idle=Synthetic Rune Infuser (unformed) +tile.botanichorizons.automatedAltar.hud.active=Synthetic Rune Infuser (online) +tile.botanichorizons.automatedAltar.hud.collecting=Synthetic Rune Infuser (gathering mana) +tile.botanichorizons.automatedTerraPlate.name=Terrestrial Agglomeration Plateau +tile.botanichorizons.automatedTerraPlate.hud.empty=Terrestrial Agglomeration Plateau (needs mana) +tile.botanichorizons.automatedTerraPlate.hud.idle=Terrestrial Agglomeration Plateau (unformed) +tile.botanichorizons.automatedTerraPlate.hud.active=Terrestrial Agglomeration Plateau (online) +tile.botanichorizons.automatedTerraPlate.hud.collecting=Terrestrial Agglomeration Plateau (gathering mana) +tile.botanichorizons.automatedTerraPlate.hud.crashing=Terrestrial Agglomeration Plateau (destabilised) + item.botanichorizons.manasteelWrench.name=Manasteel Wrench item.botanichorizons.manasteelSuperWrench.name=Demolition Wrench diff --git a/src/main/resources/assets/botanichorizons/lang/en_US.lang b/src/main/resources/assets/botanichorizons/lang/en_US.lang index e5fc634..4a822a2 100644 --- a/src/main/resources/assets/botanichorizons/lang/en_US.lang +++ b/src/main/resources/assets/botanichorizons/lang/en_US.lang @@ -95,6 +95,21 @@ tile.botanichorizons.automatedConjurationPool.name=Fountain of Conjuration tile.botanichorizons.automatedConjurationPool.hud.empty=Fountain of Conjuration (needs mana) tile.botanichorizons.automatedConjurationPool.hud.idle=Fountain of Conjuration (unformed) tile.botanichorizons.automatedConjurationPool.hud.active=Fountain of Conjuration (online) +tile.botanichorizons.automatedApothecary.name=Industrial Grafting Station +tile.botanichorizons.automatedApothecary.hud.empty=Industrial Grafting Station (needs mana) +tile.botanichorizons.automatedApothecary.hud.idle=Industrial Grafting Station (unformed) +tile.botanichorizons.automatedApothecary.hud.active=Industrial Grafting Station (online) +tile.botanichorizons.automatedAltar.name=Synthetic Rune Infuser +tile.botanichorizons.automatedAltar.hud.empty=Synthetic Rune Infuser (needs mana) +tile.botanichorizons.automatedAltar.hud.idle=Synthetic Rune Infuser (unformed) +tile.botanichorizons.automatedAltar.hud.active=Synthetic Rune Infuser (online) +tile.botanichorizons.automatedAltar.hud.collecting=Synthetic Rune Infuser (gathering mana) +tile.botanichorizons.automatedTerraPlate.name=Terrestrial Agglomeration Plateau +tile.botanichorizons.automatedTerraPlate.hud.empty=Terrestrial Agglomeration Plateau (needs mana) +tile.botanichorizons.automatedTerraPlate.hud.idle=Terrestrial Agglomeration Plateau (unformed) +tile.botanichorizons.automatedTerraPlate.hud.active=Terrestrial Agglomeration Plateau (online) +tile.botanichorizons.automatedTerraPlate.hud.collecting=Terrestrial Agglomeration Plateau (gathering mana) +tile.botanichorizons.automatedTerraPlate.hud.crashing=Terrestrial Agglomeration Plateau (destabilized) item.botanichorizons.manasteelWrench.name=Manasteel Wrench item.botanichorizons.manasteelSuperWrench.name=Demolition Wrench From fadc967b7621aeea5e7cacba04cce4b43c2417e4 Mon Sep 17 00:00:00 2001 From: Marcel Sondaar Date: Tue, 2 Jun 2026 21:32:03 +0200 Subject: [PATCH 8/8] chore: Lexicon integration first pass --- .../botanichorizons/lexicon/BHLexicon.java | 35 +++++++++++++++++++ .../assets/botanichorizons/lang/en_GB.lang | 15 ++++++++ .../assets/botanichorizons/lang/en_US.lang | 15 ++++++++ 3 files changed, 65 insertions(+) diff --git a/src/main/java/net/fuzzycraft/botanichorizons/lexicon/BHLexicon.java b/src/main/java/net/fuzzycraft/botanichorizons/lexicon/BHLexicon.java index e7edb10..f8d4469 100644 --- a/src/main/java/net/fuzzycraft/botanichorizons/lexicon/BHLexicon.java +++ b/src/main/java/net/fuzzycraft/botanichorizons/lexicon/BHLexicon.java @@ -24,8 +24,11 @@ private BHLexicon() {} public static LexiconCategory multiblockCategory; public static LexiconEntry multiblockIntro; + public static LexiconEntry automatedAltar; + public static LexiconEntry automatedApothecary; public static LexiconEntry automatedManaPool; public static LexiconEntry automatedPortal; + public static LexiconEntry automatedTerraPlate; public static LexiconEntry basicWrenches; public static LexiconEntry multiblockWrenches; @@ -42,6 +45,17 @@ public static void init() { titlePage.setIcon(new ItemStack(Items.writable_book)); BHLexicon.multiblockIntro = titlePage; + LexiconEntry apothecaryEntry = new BHLexiconEntry("automatedApothecary", multiblockCategory); + apothecaryEntry.addPage(new PageText("botanichorizons.lexicon.text.automatedApothecary.1")); + // Multiblock preview disabled due to getBiomeGenForCoords returning null for getWaterColorMultiplier + /*apothecaryEntry.addPage(new PageMultiblock( + "botanichorizons.lexicon.preview.automatedApothecary", + Multiblocks.apothecary.lexiconMultiblock( + 0, 1, -2, + BHBlocks.autoApothecary, 0 + )));*/ + BHLexicon.automatedApothecary = apothecaryEntry; + LexiconEntry poolEntry = new BHLexiconEntry("automatedManaPool", multiblockCategory); poolEntry.addPage(new PageText("botanichorizons.lexicon.text.automatedManaPool.1")); poolEntry.addPage(new PageText("botanichorizons.lexicon.text.automatedManaPool.2")); @@ -66,6 +80,27 @@ public static void init() { poolEntry.setIcon(new ItemStack(ModBlocks.pool, 1, Constants.POOL_META_REGULAR_FABULOUS)); BHLexicon.automatedManaPool = poolEntry; + LexiconEntry altarEntry = new BHLexiconEntry("automatedAltar", multiblockCategory); + altarEntry.addPage(new PageText("botanichorizons.lexicon.text.automatedAltar.1")); + altarEntry.addPage(new PageMultiblock( + "botanichorizons.lexicon.preview.automatedAltar", + Multiblocks.altar.lexiconMultiblock( + 0, 2, 0, + BHBlocks.autoAltar, 0 + ))); + BHLexicon.automatedAltar = altarEntry; + + LexiconEntry plateEntry = new BHLexiconEntry("automatedTerraPlate", multiblockCategory); + plateEntry.addPage(new PageText("botanichorizons.lexicon.text.automatedTerraPlate.1")); + plateEntry.addPage(new PageMultiblock( + "botanichorizons.lexicon.preview.automatedTerraPlate", + Multiblocks.terraPlate.lexiconMultiblock( + 0, 0, -4, + BHBlocks.autoPlate, 0 + ))); + plateEntry.setKnowledgeType(BotaniaAPI.elvenKnowledge); + BHLexicon.automatedTerraPlate = plateEntry; + LexiconEntry gatewayEntry = new BHLexiconEntry("automatedAlfPortal", multiblockCategory); gatewayEntry.setKnowledgeType(BotaniaAPI.elvenKnowledge); gatewayEntry.addPage(new PageText("botanichorizons.lexicon.text.automatedAlfPortal.1")); diff --git a/src/main/resources/assets/botanichorizons/lang/en_GB.lang b/src/main/resources/assets/botanichorizons/lang/en_GB.lang index 380adc4..f6278f8 100644 --- a/src/main/resources/assets/botanichorizons/lang/en_GB.lang +++ b/src/main/resources/assets/botanichorizons/lang/en_GB.lang @@ -130,6 +130,16 @@ botanichorizons.lexicon.preview.automatedAlfPortal=Building the device botanichorizons.lexicon.text.automatedAlfPortal.1=The &1Synthetic Elven Gateway&0 uses advanced engineering techniques and wondrous elven materials to reinforce the link to &4Alfheim&0 even more than is possible with your first &1Elven Gateway&0. While the link is still too weak to allow living beings through, it allows sending several items at once. This works both ways, as &4Elven Garde&0 can also send back up to a stack of items every time! botanichorizons.lexicon.text.automatedAlfPortal.2=There is however a price to pay for this additional speed. Strengthening the link in this way costs both more &4Mana&0 to keep it open, and exchanging items also incurs an extra &4Mana&0 cost. &4Mana&0 and items are provided directly to the controller. +botanichorizons.lexicon.entry.automatedAltar=Synthetic Rune Infuser +botanichorizons.lexicon.tagline.automatedAltar=Pyramids of Power +botanichorizons.lexicon.preview.automatedAltar=Building the device +botanichorizons.lexicon.text.automatedAltar.1=Feeding the Runic Altar all the parts at the correct place at the correct time is a chore, by scaling up the device into the &1Synthetic Rune Infuser&0 and turning it in a pyramid, the focal point ends up inside of the machine. This allows the internal mechanics to supply the items as needed, and perform multiple crafts at the same time. + +botanichorizons.lexicon.entry.automatedApothecary=Industrial Grafting Station +botanichorizons.lexicon.tagline.automatedApothecary=A bouquet of flowers +botanichorizons.lexicon.preview.automatedApothecary=Building the device +botanichorizons.lexicon.text.automatedApothecary.1=The &1Industrial Grafting Station&0 solves the issue of having a small workspace and having to source your materials from elsewhere all the time. This upgrade allows you to automatically craft flowers, without their water or seed cost. Instead, a bit of mana is used to regenerate the biomass included in the station. + botanichorizons.lexicon.entry.automatedManaPool=Mana Fountains botanichorizons.lexicon.tagline.automatedManaPool=Immerse items in a steady stream of mana botanichorizons.lexicon.text.automatedManaPool.1=Automating a &1Mana Pool&0 involves dropping and collecting items. Some &1Robot Arms&0 seem to do the trick! You can have up to four &1Fountains&0 around a big central pool, and they can craft up to a stack of items at a time. Use a &1Wand of the Forest&0 on the &1Fountain's&0 controller block to form the structure after supplying it with a bit of &4Mana&0. Make sure the large pool in the middle is free from obstructions! @@ -144,6 +154,11 @@ botanichorizons.lexicon.tagline.automatedAlchemyPool=Large Chemical Reactor botanichorizons.lexicon.crafting.automatedAlchemyPool=Making the fountain botanichorizons.lexicon.preview.automatedAlchemyPool=Building the device +botanichorizons.lexicon.entry.automatedTerraPlate=Bulk Terrasteel +botanichorizons.lexicon.tagline.automatedTerraPlate=Bigger is better +botanichorizons.lexicon.preview.automatedTerraPlate=Building the device +botanichorizons.lexicon.text.automatedTerraPlate.1=The process of making Terrasteel requires large amounts of mana. The &1Terrestrial Agglomeration Plateau&0 large size increases the area in which energy can collect, allowing you to make multiple pieces of Terrasteel in one go at a small discount. The energies involved are extreme and any misplaced item unfortunately makes the machine go haywire. + botanichorizons.lexicon.entry.basicWrenches=Wrenches botanichorizons.lexicon.tagline.basicWrenches=Rattles like a snake botanichorizons.lexicon.text.basicWrenches.1=You can make a &1Wrench&0 out of various botanic materials. If you have a mana source on you, they will not take damage but consume a bit of &4Mana&0 instead. Unfortunately they are only good at breaking blocks. Check your &1thaumonomicon&0 for research and recipes. diff --git a/src/main/resources/assets/botanichorizons/lang/en_US.lang b/src/main/resources/assets/botanichorizons/lang/en_US.lang index 4a822a2..f2d3d79 100644 --- a/src/main/resources/assets/botanichorizons/lang/en_US.lang +++ b/src/main/resources/assets/botanichorizons/lang/en_US.lang @@ -129,6 +129,16 @@ botanichorizons.lexicon.preview.automatedAlfPortal=Building the device botanichorizons.lexicon.text.automatedAlfPortal.1=The &1Synthetic Elven Gateway&0 uses advanced engineering techniques and wondrous elven materials to reinforce the link to &4Alfheim&0 even more than is possible with your first &1Elven Gateway&0. While the link is still too weak to allow living beings through, it allows sending several items at once. This works both ways, as &4Elven Garde&0 can also send back up to a stack of items every time! botanichorizons.lexicon.text.automatedAlfPortal.2=There is however a price to pay for this additional speed. Strengthening the link in this way costs both more &4Mana&0 to keep it open, and exchanging items also incurs an extra &4Mana&0 cost. &4Mana&0 and items are provided directly to the controller. +botanichorizons.lexicon.entry.automatedAltar=Synthetic Rune Infuser +botanichorizons.lexicon.tagline.automatedAltar=Pyramids of Power +botanichorizons.lexicon.preview.automatedAltar=Building the device +botanichorizons.lexicon.text.automatedAltar.1=Feeding the Runic Altar all the parts at the correct place at the correct time is a chore, by scaling up the device into the &1Synthetic Rune Infuser&0 and turning it in a pyramid, the focal point ends up inside of the machine. This allows the internal mechanics to supply the items as needed, and perform multiple crafts at the same time. + +botanichorizons.lexicon.entry.automatedApothecary=Industrial Grafting Station +botanichorizons.lexicon.tagline.automatedApothecary=A bouquet of flowers +botanichorizons.lexicon.preview.automatedApothecary=Building the device +botanichorizons.lexicon.text.automatedApothecary.1=The &1Industrial Grafting Station&0 solves the issue of having a small workspace and having to source your materials from elsewhere all the time. This upgrade allows you to automatically craft flowers, without their water or seed cost. Instead, a bit of mana is used to regenerate the biomass included in the station. + botanichorizons.lexicon.entry.automatedManaPool=Mana Fountains botanichorizons.lexicon.tagline.automatedManaPool=Immerse items in a steady stream of mana botanichorizons.lexicon.text.automatedManaPool.1=Automating a &1Mana Pool&0 involves dropping and collecting items. Some &1Robot Arms&0 seem to do the trick! You can have up to four &1Fountains&0 around a big central pool, and they can craft up to a stack of items at a time. Use a &1Wand of the Forest&0 on the &1Fountain's&0 controller block to form the structure after supplying it with a bit of &4Mana&0. Make sure the large pool in the middle is free from obstructions! @@ -143,6 +153,11 @@ botanichorizons.lexicon.tagline.automatedAlchemyPool=Large Chemical Reactor botanichorizons.lexicon.crafting.automatedAlchemyPool=Making the fountain botanichorizons.lexicon.preview.automatedAlchemyPool=Building the device +botanichorizons.lexicon.entry.automatedTerraPlate=Bulk Terrasteel +botanichorizons.lexicon.tagline.automatedTerraPlate=Bigger is better +botanichorizons.lexicon.preview.automatedTerraPlate=Building the device +botanichorizons.lexicon.text.automatedTerraPlate.1=The process of making Terrasteel requires large amounts of mana. The &1Terrestrial Agglomeration Plateau&0 large size increases the area in which energy can collect, allowing you to make multiple pieces of Terrasteel in one go at a small discount. The energies involved are extreme and any misplaced item unfortunately makes the machine go haywire. + botanichorizons.lexicon.entry.basicWrenches=Wrenches botanichorizons.lexicon.tagline.basicWrenches=Rattles like a snake botanichorizons.lexicon.text.basicWrenches.1=You can make a &1Wrench&0 out of various botanic materials. If you have a mana source on you, they will not take damage but consume a bit of &4Mana&0 instead. Unfortunately they are only good at breaking blocks. Check your &1Thaumonomicon&0 for research and recipes.