From 870e5d13ebe55838bae163ae25fd41e6c73ebf9a Mon Sep 17 00:00:00 2001 From: KoyoNoguchi Date: Tue, 27 Oct 2026 00:59:45 +0900 Subject: [PATCH] Add EMC Builder: a BuildCraft Builder that pays for materials with EMC - TileEMCBuilder extends TileBuilder. During build() it exposes virtual material slots backed by EMC, then charges the EMC value (1:1) of what was used. Real items and tank fluids are still used first. - EMC is stored per player per world (EmcPool, WorldSavedData), so all EMC Builders a player owns in a world share one balance. - GUI based on BuildCraft's Builder GUI, with an EMC consume slot like the Transmutation Table's and the owner's balance shown. - Blueprint schematic strips the owner; the first player to open an unowned builder becomes its owner. - Lit top while building is not drawn. - Shapeless recipe: Builder + Transmutation Table. - Depend on local BuildCraft 7.1.27 and FMProjectE dev jars in libs/ (not committed). Co-Authored-By: Claude Opus 5.5 --- dependencies.gradle | 6 + .../net/kanawo/emcbuilder/ClientProxy.java | 14 + .../net/kanawo/emcbuilder/CommonProxy.java | 28 +- .../net/kanawo/emcbuilder/EMCBuilder.java | 10 +- .../emcbuilder/block/BlockEMCBuilder.java | 96 ++++++ .../emcbuilder/block/SchematicEMCBuilder.java | 22 ++ .../emcbuilder/gui/ContainerEMCBuilder.java | 107 +++++++ .../kanawo/emcbuilder/gui/GuiEMCBuilder.java | 252 +++++++++++++++ .../net/kanawo/emcbuilder/gui/GuiHandler.java | 34 ++ .../kanawo/emcbuilder/gui/SlotEMCConsume.java | 41 +++ .../net/kanawo/emcbuilder/tile/EmcPool.java | 93 ++++++ .../emcbuilder/tile/TileEMCBuilder.java | 294 ++++++++++++++++++ .../assets/emcbuilder/lang/en_US.lang | 2 + .../assets/emcbuilder/lang/ja_JP.lang | 2 + .../textures/blocks/emcbuilderBlock/front.png | Bin 0 -> 1115 bytes .../textures/blocks/emcbuilderBlock/side.png | Bin 0 -> 1166 bytes .../textures/blocks/emcbuilderBlock/top.png | Bin 0 -> 1500 bytes .../emcbuilder/textures/gui/emcbuilder.png | Bin 0 -> 2122 bytes 18 files changed, 999 insertions(+), 2 deletions(-) create mode 100644 src/main/java/net/kanawo/emcbuilder/block/BlockEMCBuilder.java create mode 100644 src/main/java/net/kanawo/emcbuilder/block/SchematicEMCBuilder.java create mode 100644 src/main/java/net/kanawo/emcbuilder/gui/ContainerEMCBuilder.java create mode 100644 src/main/java/net/kanawo/emcbuilder/gui/GuiEMCBuilder.java create mode 100644 src/main/java/net/kanawo/emcbuilder/gui/GuiHandler.java create mode 100644 src/main/java/net/kanawo/emcbuilder/gui/SlotEMCConsume.java create mode 100644 src/main/java/net/kanawo/emcbuilder/tile/EmcPool.java create mode 100644 src/main/java/net/kanawo/emcbuilder/tile/TileEMCBuilder.java create mode 100644 src/main/resources/assets/emcbuilder/lang/en_US.lang create mode 100644 src/main/resources/assets/emcbuilder/lang/ja_JP.lang create mode 100644 src/main/resources/assets/emcbuilder/textures/blocks/emcbuilderBlock/front.png create mode 100644 src/main/resources/assets/emcbuilder/textures/blocks/emcbuilderBlock/side.png create mode 100644 src/main/resources/assets/emcbuilder/textures/blocks/emcbuilderBlock/top.png create mode 100644 src/main/resources/assets/emcbuilder/textures/gui/emcbuilder.png diff --git a/dependencies.gradle b/dependencies.gradle index 780276b..cf2ff4b 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -35,4 +35,10 @@ */ dependencies { //runtimeOnlyNonPublishable("com.github.GTNewHorizons:NotEnoughItems:2.7.69-GTNH:dev") + + // Local deobfuscated (MCP-named) dev jars placed in libs/: + // - BuildCraft 7.1.27 (gradlew deobfJar -> buildcraft-7.1.27-dev.jar) + // - FMProjectE (gradlew jar -> FMProjectE-*-dev.jar) + devOnlyNonPublishable(files("libs/buildcraft-7.1.27-dev.jar")) + devOnlyNonPublishable(fileTree("libs") { include("FMProjectE-*-dev.jar") }) } diff --git a/src/main/java/net/kanawo/emcbuilder/ClientProxy.java b/src/main/java/net/kanawo/emcbuilder/ClientProxy.java index 962d50b..bb65f04 100644 --- a/src/main/java/net/kanawo/emcbuilder/ClientProxy.java +++ b/src/main/java/net/kanawo/emcbuilder/ClientProxy.java @@ -1,8 +1,22 @@ package net.kanawo.emcbuilder; +import net.kanawo.emcbuilder.tile.TileEMCBuilder; + +import buildcraft.core.render.RenderBuilder; +import cpw.mods.fml.client.registry.ClientRegistry; +import cpw.mods.fml.common.event.FMLInitializationEvent; + public class ClientProxy extends CommonProxy { // Override CommonProxy methods here, if you want a different behaviour on the client (e.g. registering renders). // Don't forget to call the super methods as well. + @Override + public void init(FMLInitializationEvent event) { + super.init(event); + + // The Builder's own renderer (RenderBuilderTile) also draws BuildCraft's lit top while building. Its base + // renderer draws only the area box, path lasers and flying items. + ClientRegistry.bindTileEntitySpecialRenderer(TileEMCBuilder.class, new RenderBuilder()); + } } diff --git a/src/main/java/net/kanawo/emcbuilder/CommonProxy.java b/src/main/java/net/kanawo/emcbuilder/CommonProxy.java index 42bbd42..2c68de3 100644 --- a/src/main/java/net/kanawo/emcbuilder/CommonProxy.java +++ b/src/main/java/net/kanawo/emcbuilder/CommonProxy.java @@ -1,12 +1,25 @@ package net.kanawo.emcbuilder; +import net.kanawo.emcbuilder.block.BlockEMCBuilder; +import net.kanawo.emcbuilder.block.SchematicEMCBuilder; +import net.kanawo.emcbuilder.gui.GuiHandler; +import net.kanawo.emcbuilder.tile.TileEMCBuilder; +import net.minecraft.item.ItemStack; + +import buildcraft.BuildCraftBuilders; +import buildcraft.api.blueprints.BuilderAPI; import cpw.mods.fml.common.event.FMLInitializationEvent; import cpw.mods.fml.common.event.FMLPostInitializationEvent; import cpw.mods.fml.common.event.FMLPreInitializationEvent; import cpw.mods.fml.common.event.FMLServerStartingEvent; +import cpw.mods.fml.common.network.NetworkRegistry; +import cpw.mods.fml.common.registry.GameRegistry; +import moze_intel.projecte.gameObjs.ObjHandler; public class CommonProxy { + public static BlockEMCBuilder emcBuilderBlock; + // preInit "Run before anything else. Read your config, create blocks, items, etc, and register them with the // GameRegistry." (Remove if not needed) public void preInit(FMLPreInitializationEvent event) { @@ -14,10 +27,23 @@ public class CommonProxy { EMCBuilder.LOG.info(Config.greeting); EMCBuilder.LOG.info("I am EMCBuilder at version " + Tags.VERSION); + + emcBuilderBlock = new BlockEMCBuilder(); + GameRegistry.registerBlock(emcBuilderBlock, "emcBuilder"); + GameRegistry.registerTileEntity(TileEMCBuilder.class, EMCBuilder.MODID + ":EMCBuilder"); } // load "Do your mod setup. Build whatever data structures you care about. Register recipes." (Remove if not needed) - public void init(FMLInitializationEvent event) {} + public void init(FMLInitializationEvent event) { + NetworkRegistry.INSTANCE.registerGuiHandler(EMCBuilder.instance, new GuiHandler()); + + BuilderAPI.schematicRegistry.registerSchematicBlock(emcBuilderBlock, SchematicEMCBuilder.class); + + GameRegistry.addShapelessRecipe( + new ItemStack(emcBuilderBlock), + BuildCraftBuilders.builderBlock, + ObjHandler.transmuteStone); + } // postInit "Handle interaction with other mods, complete your setup based on this." (Remove if not needed) public void postInit(FMLPostInitializationEvent event) {} diff --git a/src/main/java/net/kanawo/emcbuilder/EMCBuilder.java b/src/main/java/net/kanawo/emcbuilder/EMCBuilder.java index 9f77b4d..ddca9e5 100644 --- a/src/main/java/net/kanawo/emcbuilder/EMCBuilder.java +++ b/src/main/java/net/kanawo/emcbuilder/EMCBuilder.java @@ -10,12 +10,20 @@ import cpw.mods.fml.common.event.FMLPostInitializationEvent; import cpw.mods.fml.common.event.FMLPreInitializationEvent; import cpw.mods.fml.common.event.FMLServerStartingEvent; -@Mod(modid = EMCBuilder.MODID, version = "1.0.0", name = "EMCBuilder", acceptedMinecraftVersions = "[1.7.10]") +@Mod( + modid = EMCBuilder.MODID, + version = "1.0.0", + name = "EMCBuilder", + acceptedMinecraftVersions = "[1.7.10]", + dependencies = "required-after:BuildCraft|Builders;required-after:ProjectE") public class EMCBuilder { public static final String MODID = "emcbuilder"; public static final Logger LOG = LogManager.getLogger(MODID); + @Mod.Instance(MODID) + public static EMCBuilder instance; + @SidedProxy(clientSide = "net.kanawo.emcbuilder.ClientProxy", serverSide = "net.kanawo.emcbuilder.CommonProxy") public static CommonProxy proxy; diff --git a/src/main/java/net/kanawo/emcbuilder/block/BlockEMCBuilder.java b/src/main/java/net/kanawo/emcbuilder/block/BlockEMCBuilder.java new file mode 100644 index 0000000..39b1074 --- /dev/null +++ b/src/main/java/net/kanawo/emcbuilder/block/BlockEMCBuilder.java @@ -0,0 +1,96 @@ +package net.kanawo.emcbuilder.block; + +import net.kanawo.emcbuilder.EMCBuilder; +import net.kanawo.emcbuilder.gui.GuiHandler; +import net.kanawo.emcbuilder.tile.TileEMCBuilder; +import net.minecraft.client.renderer.texture.IIconRegister; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.Item; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.world.World; +import net.minecraftforge.common.util.ForgeDirection; + +import buildcraft.api.events.BlockInteractionEvent; +import buildcraft.builders.BlockBuilder; +import buildcraft.builders.ItemConstructionMarker; +import buildcraft.core.lib.fluids.TankUtils; +import buildcraft.core.lib.utils.ResourceUtils; +import cpw.mods.fml.common.FMLCommonHandler; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; + +public class BlockEMCBuilder extends BlockBuilder { + + private static final String TEXTURE_PREFIX = EMCBuilder.MODID + ":emcbuilderBlock"; + + public BlockEMCBuilder() { + super(); + setBlockName("emcBuilder"); + } + + @Override + public TileEntity createNewTileEntity(World world, int metadata) { + return new TileEMCBuilder(); + } + + /** + * Same as BlockBuilder, but opens the EMC Builder GUI. BlockBuilder's version cannot be called because it opens + * BuildCraft's own Builder GUI. + */ + @Override + public boolean onBlockActivated(World world, int x, int y, int z, EntityPlayer player, int side, float hitX, + float hitY, float hitZ) { + BlockInteractionEvent event = new BlockInteractionEvent(player, this); + FMLCommonHandler.instance() + .bus() + .post(event); + if (event.isCanceled()) { + return true; + } + + if (player.isSneaking()) { + return false; + } + + TileEntity tile = world.getTileEntity(x, y, z); + TileEMCBuilder builder = tile instanceof TileEMCBuilder ? (TileEMCBuilder) tile : null; + + Item equipped = player.getCurrentEquippedItem() != null ? player.getCurrentEquippedItem() + .getItem() : null; + if (equipped instanceof ItemConstructionMarker) { + if (ItemConstructionMarker.linkStarted(player.getCurrentEquippedItem())) { + ItemConstructionMarker.link(player.getCurrentEquippedItem(), world, x, y, z); + } + + return true; + } + + boolean filledTank = builder != null + && TankUtils.handleRightClick(builder, ForgeDirection.UNKNOWN, player, true, false); + if (!filledTank && !world.isRemote) { + player.openGui(EMCBuilder.instance, GuiHandler.EMC_BUILDER, world, x, y, z); + } + + return true; + } + + /** Textures are looked up as textures/blocks/emcbuilderBlock/{top,bottom,front,side,...}.png. */ + @Override + @SideOnly(Side.CLIENT) + public String[] getIconBlockNames() { + return new String[] { TEXTURE_PREFIX }; + } + + @Override + @SideOnly(Side.CLIENT) + public void registerBlockIcons(IIconRegister register) { + super.registerBlockIcons(register); + + // Without bottom.png BlockBuildCraft would fall back to a missing "default" texture. Use the left face + // (index 4), which resolves to side.png, instead. + String bottom = TEXTURE_PREFIX + "/bottom"; + if (!ResourceUtils.resourceExists(ResourceUtils.iconToResourcePath(register, bottom))) { + icons[0][0] = icons[0][4]; + } + } +} diff --git a/src/main/java/net/kanawo/emcbuilder/block/SchematicEMCBuilder.java b/src/main/java/net/kanawo/emcbuilder/block/SchematicEMCBuilder.java new file mode 100644 index 0000000..01a1e47 --- /dev/null +++ b/src/main/java/net/kanawo/emcbuilder/block/SchematicEMCBuilder.java @@ -0,0 +1,22 @@ +package net.kanawo.emcbuilder.block; + +import net.kanawo.emcbuilder.tile.TileEMCBuilder; + +import buildcraft.builders.schematics.SchematicBuilderLike; + +/** + * Blueprint schematic that, like the Builder's, drops the build state, and also drops the owner, so a copy built by + * someone else does not draw on the original owner's EMC. + */ +public class SchematicEMCBuilder extends SchematicBuilderLike { + + @Override + public void onNBTLoaded() { + super.onNBTLoaded(); + + if (tileNBT != null) { + tileNBT.removeTag(TileEMCBuilder.OWNER_MOST); + tileNBT.removeTag(TileEMCBuilder.OWNER_LEAST); + } + } +} diff --git a/src/main/java/net/kanawo/emcbuilder/gui/ContainerEMCBuilder.java b/src/main/java/net/kanawo/emcbuilder/gui/ContainerEMCBuilder.java new file mode 100644 index 0000000..5d1ce30 --- /dev/null +++ b/src/main/java/net/kanawo/emcbuilder/gui/ContainerEMCBuilder.java @@ -0,0 +1,107 @@ +package net.kanawo.emcbuilder.gui; + +import net.kanawo.emcbuilder.tile.TileEMCBuilder; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.InventoryPlayer; +import net.minecraft.inventory.IInventory; +import net.minecraft.inventory.Slot; +import net.minecraft.item.ItemStack; + +import buildcraft.builders.gui.ContainerBuilder; +import buildcraft.core.lib.gui.widgets.ScrollbarWidget; + +/** + * The Builder container plus an EMC consume slot. Extending {@link ContainerBuilder} keeps BuildCraft's requirement + * tooltips and GUI watcher handling. + */ +public class ContainerEMCBuilder extends ContainerBuilder { + + /** Matches the slot frame at (132, 26) in emcbuilder.png. */ + public static final int CONSUME_SLOT_X = 133; + public static final int CONSUME_SLOT_Y = 27; + + private static final int PLAYER_SLOTS = 9 * 4; + + /** Index of the consume slot; the builder and player slots are all before it. */ + private final int consumeSlot; + + public ContainerEMCBuilder(IInventory playerInventory, TileEMCBuilder builder) { + super(playerInventory, builder); + + // A builder made from a blueprint has no owner yet; whoever opens it first takes it. + if (!builder.getWorldObj().isRemote && playerInventory instanceof InventoryPlayer) { + builder.claimOwner(((InventoryPlayer) playerInventory).player); + } + + consumeSlot = inventorySlots.size(); + addSlotToContainer(new SlotEMCConsume(builder, CONSUME_SLOT_X, CONSUME_SLOT_Y)); + } + + public ScrollbarWidget getScrollbar() { + return scrollbarWidget; + } + + /** + * Same behaviour as BuildCraftContainer, which assumes the player inventory is the last 36 slots; the consume slot + * comes after it here. Shift-clicking never moves items into the consume slot. + */ + @Override + public ItemStack transferStackInSlot(EntityPlayer player, int slotIndex) { + if (slotIndex == consumeSlot) { + return null; + } + + Slot slot = (Slot) inventorySlots.get(slotIndex); + + if (slot == null || !slot.getHasStack()) { + return null; + } + + int playerStart = consumeSlot - PLAYER_SLOTS; + int hotbarStart = consumeSlot - 9; + ItemStack stack = slot.getStack(); + ItemStack original = stack.copy(); + + if (slotIndex >= playerStart) { + if (!shiftIntoBuilder(stack, playerStart)) { + boolean fromHotbar = slotIndex >= hotbarStart; + + if (!shiftItemStack( + stack, + fromHotbar ? playerStart : hotbarStart, + fromHotbar ? hotbarStart : consumeSlot)) { + return null; + } + } + } else if (!shiftItemStack(stack, playerStart, consumeSlot)) { + return null; + } + + slot.onSlotChange(stack, original); + + if (stack.stackSize <= 0) { + slot.putStack(null); + } else { + slot.onSlotChanged(); + } + + if (stack.stackSize == original.stackSize) { + return null; + } + + slot.onPickupFromSlot(player, stack); + return original; + } + + private boolean shiftIntoBuilder(ItemStack stack, int builderSlots) { + for (int i = 0; i < builderSlots; i++) { + Slot slot = (Slot) inventorySlots.get(i); + + if (slot.isItemValid(stack) && shiftItemStack(stack, i, i + 1)) { + return true; + } + } + + return false; + } +} diff --git a/src/main/java/net/kanawo/emcbuilder/gui/GuiEMCBuilder.java b/src/main/java/net/kanawo/emcbuilder/gui/GuiEMCBuilder.java new file mode 100644 index 0000000..5b3868c --- /dev/null +++ b/src/main/java/net/kanawo/emcbuilder/gui/GuiEMCBuilder.java @@ -0,0 +1,252 @@ +package net.kanawo.emcbuilder.gui; + +import java.util.ArrayList; +import java.util.List; + +import net.kanawo.emcbuilder.EMCBuilder; +import net.kanawo.emcbuilder.tile.TileEMCBuilder; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiButton; +import net.minecraft.inventory.IInventory; +import net.minecraft.util.EnumChatFormatting; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.StatCollector; + +import org.lwjgl.opengl.GL11; + +import buildcraft.BuildCraftCore; +import buildcraft.builders.gui.SlotBuilderRequirement; +import buildcraft.core.blueprints.RequirementItemStack; +import buildcraft.core.lib.fluids.Tank; +import buildcraft.core.lib.gui.AdvancedSlot; +import buildcraft.core.lib.gui.GuiAdvancedInterface; +import buildcraft.core.lib.network.command.CommandWriter; +import buildcraft.core.lib.network.command.PacketCommand; +import buildcraft.core.lib.utils.StringUtils; +import io.netty.buffer.ByteBuf; +import moze_intel.projecte.utils.Constants; + +/** + * BuildCraft's GuiBuilder with the EMC consume slot and the stored EMC added. GuiBuilder cannot be extended because it + * creates its own ContainerBuilder. + */ +public class GuiEMCBuilder extends GuiAdvancedInterface { + + /** BuildCraft's builder.png with the EMC consume slot added. */ + private static final ResourceLocation REGULAR_TEXTURE = new ResourceLocation( + EMCBuilder.MODID, + "textures/gui/emcbuilder.png"); + private static final ResourceLocation BLUEPRINT_TEXTURE = new ResourceLocation( + "buildcraftbuilders:textures/gui/builder_blueprint.png"); + + private final TileEMCBuilder builder; + private GuiButton selectedButton; + + public GuiEMCBuilder(IInventory playerInventory, TileEMCBuilder builder) { + super(new ContainerEMCBuilder(playerInventory, builder), builder, BLUEPRINT_TEXTURE); + this.builder = builder; + xSize = 256; + ySize = 225; + + resetNullSlots(6 * 4); + + for (int i = 0; i < 6; ++i) { + for (int j = 0; j < 4; ++j) { + slots.set(i * 4 + j, new SlotBuilderRequirement(this, 179 + j * 18, 18 + i * 18)); + } + } + } + + private ContainerEMCBuilder getContainerBuilder() { + return (ContainerEMCBuilder) getContainer(); + } + + @Override + protected void drawGuiContainerForegroundLayer(int par1, int par2) { + super.drawGuiContainerForegroundLayer(par1, par2); + + drawCenteredString(StringUtils.localize("tile.emcBuilder.name"), 178 / 2, 16, 0x404040); + + drawCenteredString( + StatCollector.translateToLocalFormatted( + "gui.emcbuilder.stored", + Constants.EMC_FORMATTER.format(builder.getStoredEmc())), + 178 / 2, + 51, + 0x404040); + + if (builder.getStackInSlot(0) != null) { + fontRendererObj.drawString(StringUtils.localize("gui.building.resources"), 8, 60, 0x404040); + fontRendererObj.drawString(StringUtils.localize("gui.inventory"), 8, ySize - 97, 0x404040); + fontRendererObj.drawString(StringUtils.localize("gui.needed"), 178, 7, 0x404040); + fontRendererObj.drawString(StringUtils.localize("gui.building.fluids"), 178, 133, 0x404040); + } + + drawTooltips(par1, par2); + } + + private void drawTooltips(int par1, int par2) { + int top = guiTop + 145; + for (int i = 0; i < builder.fluidTanks.length; i++) { + int left = guiLeft + 179 + 18 * i; + if (par1 >= left && par2 >= top && par1 < (left + 16) && par2 < (left + 47)) { + List fluidTip = new ArrayList<>(); + Tank tank = builder.fluidTanks[i]; + if (tank.getFluid() != null && tank.getFluid().amount > 0) { + fluidTip.add( + tank.getFluid() + .getLocalizedName()); + if (!BuildCraftCore.hideFluidNumbers) { + fluidTip.add( + EnumChatFormatting.GRAY + "" + EnumChatFormatting.ITALIC + tank.getFluid().amount + " mB"); + } + } else { + fluidTip.add(StatCollector.translateToLocal("gui.fluidtank.empty")); + } + drawHoveringText(fluidTip, par1 - guiLeft, par2 - guiTop, fontRendererObj); + return; + } + } + + drawTooltipForSlotAt(par1, par2); + } + + @Override + @SuppressWarnings("unchecked") + protected void drawGuiContainerBackgroundLayer(float f, int x, int y) { + // Like GuiBuilder, super is not called because the GUI is resized dynamically. + GL11.glColor4f(1.0F, 1.0F, 1.0F, 1.0F); + boolean isBlueprint = builder.getStackInSlot(0) != null; + + mc.renderEngine.bindTexture(REGULAR_TEXTURE); + drawTexturedModalRect(guiLeft, guiTop, 0, 0, 176, ySize); + mc.renderEngine.bindTexture(BLUEPRINT_TEXTURE); + if (isBlueprint) { + drawTexturedModalRect(guiLeft + 169, guiTop, 169, 0, 256 - 169, ySize); + } + + List needs = builder.getNeededItems(); + + if (needs != null) { + if (needs.size() > slots.size()) { + getContainerBuilder().getScrollbar().hidden = false; + getContainerBuilder().getScrollbar() + .setLength((needs.size() - slots.size() + 3) / 4); + } else { + getContainerBuilder().getScrollbar().hidden = true; + } + + int offset = getContainerBuilder().getScrollbar() + .getPosition() * 4; + for (int s = 0; s < slots.size(); s++) { + int ts = offset + s; + if (ts >= needs.size()) { + ((SlotBuilderRequirement) slots.get(s)).stack = null; + } else { + ((SlotBuilderRequirement) slots.get(s)).stack = needs.get(ts); + } + } + + for (GuiButton b : (List) buttonList) { + b.visible = true; + } + } else { + getContainerBuilder().getScrollbar().hidden = true; + for (AdvancedSlot slot : slots) { + ((SlotBuilderRequirement) slot).stack = null; + } + for (GuiButton b : (List) buttonList) { + b.visible = false; + } + } + + drawWidgets(x, y); + + if (isBlueprint) { + drawBackgroundSlots(x, y); + + for (int i = 0; i < builder.fluidTanks.length; i++) { + Tank tank = builder.fluidTanks[i]; + if (tank.getFluid() != null && tank.getFluid().amount > 0) { + drawFluid(tank.getFluid(), guiLeft + 179 + 18 * i, guiTop + 145, 16, 47, tank.getCapacity()); + } + } + + mc.renderEngine.bindTexture(BLUEPRINT_TEXTURE); + + for (int i = 0; i < builder.fluidTanks.length; i++) { + Tank tank = builder.fluidTanks[i]; + if (tank.getFluid() != null && tank.getFluid().amount > 0) { + drawTexturedModalRect(guiLeft + 179 + 18 * i, guiTop + 145, 0, 54, 16, 47); + } + } + } + } + + @Override + @SuppressWarnings("unchecked") + public void initGui() { + super.initGui(); + for (int i = 0; i < 4; i++) { + buttonList.add(new EraseTankButton(i, guiLeft + 178 + 18 * i, guiTop + 197, 18, 18)); + } + } + + @Override + protected void mouseMovedOrUp(int mouseX, int mouseY, int eventType) { + super.mouseMovedOrUp(mouseX, mouseY, eventType); + if (this.selectedButton != null && eventType == 0) { + this.selectedButton.mouseReleased(mouseX, mouseY); + this.selectedButton = null; + } + } + + /** Empties one of the builder's fluid tanks; same as GuiBuilder's erase button. */ + private class EraseTankButton extends GuiButton { + + private boolean clicked; + + public EraseTankButton(int id, int x, int y, int width, int height) { + super(id, x, y, width, height, null); + } + + @Override + public boolean mousePressed(Minecraft mc, int x, int y) { + if (super.mousePressed(mc, x, y)) { + selectedButton = this; + clicked = true; + BuildCraftCore.instance.sendToServer(new PacketCommand(builder, "eraseFluidTank", new CommandWriter() { + + @Override + public void write(ByteBuf data) { + data.writeInt(id); + } + })); + return true; + } else { + return false; + } + } + + @Override + public void mouseReleased(int x, int y) { + super.mouseReleased(x, y); + clicked = false; + } + + @Override + public void drawButton(Minecraft mc, int x, int y) { + if (!visible) { + return; + } + // hovered + this.field_146123_n = x >= this.xPosition && y >= this.yPosition + && x < this.xPosition + this.width + && y < this.yPosition + this.height; + + mc.renderEngine.bindTexture(BLUEPRINT_TEXTURE); + drawTexturedModalRect(xPosition, yPosition, 0, (clicked ? 1 : this.field_146123_n ? 2 : 0) * 18, 18, 18); + mouseDragged(mc, x, y); + } + } +} diff --git a/src/main/java/net/kanawo/emcbuilder/gui/GuiHandler.java b/src/main/java/net/kanawo/emcbuilder/gui/GuiHandler.java new file mode 100644 index 0000000..aac20f3 --- /dev/null +++ b/src/main/java/net/kanawo/emcbuilder/gui/GuiHandler.java @@ -0,0 +1,34 @@ +package net.kanawo.emcbuilder.gui; + +import net.kanawo.emcbuilder.tile.TileEMCBuilder; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.world.World; + +import cpw.mods.fml.common.network.IGuiHandler; + +public class GuiHandler implements IGuiHandler { + + public static final int EMC_BUILDER = 0; + + @Override + public Object getServerGuiElement(int id, EntityPlayer player, World world, int x, int y, int z) { + TileEMCBuilder builder = getBuilder(id, world, x, y, z); + return builder == null ? null : new ContainerEMCBuilder(player.inventory, builder); + } + + @Override + public Object getClientGuiElement(int id, EntityPlayer player, World world, int x, int y, int z) { + TileEMCBuilder builder = getBuilder(id, world, x, y, z); + return builder == null ? null : new GuiEMCBuilder(player.inventory, builder); + } + + private static TileEMCBuilder getBuilder(int id, World world, int x, int y, int z) { + if (id != EMC_BUILDER || !world.blockExists(x, y, z)) { + return null; + } + + TileEntity tile = world.getTileEntity(x, y, z); + return tile instanceof TileEMCBuilder ? (TileEMCBuilder) tile : null; + } +} diff --git a/src/main/java/net/kanawo/emcbuilder/gui/SlotEMCConsume.java b/src/main/java/net/kanawo/emcbuilder/gui/SlotEMCConsume.java new file mode 100644 index 0000000..52906b1 --- /dev/null +++ b/src/main/java/net/kanawo/emcbuilder/gui/SlotEMCConsume.java @@ -0,0 +1,41 @@ +package net.kanawo.emcbuilder.gui; + +import net.kanawo.emcbuilder.tile.TileEMCBuilder; +import net.minecraft.inventory.InventoryBasic; +import net.minecraft.inventory.Slot; +import net.minecraft.item.ItemStack; + +import moze_intel.projecte.utils.EMCHelper; + +/** + * Converts any item with an EMC value into EMC stored in the builder, like the Transmutation Table's consume slot. + * The slot never holds anything itself. + */ +public class SlotEMCConsume extends Slot { + + private final TileEMCBuilder builder; + + public SlotEMCConsume(TileEMCBuilder builder, int x, int y) { + super(new InventoryBasic("EMC", false, 1), 0, x, y); + this.builder = builder; + } + + @Override + public boolean isItemValid(ItemStack stack) { + return stack != null && EMCHelper.doesItemHaveEmc(stack); + } + + @Override + public void putStack(ItemStack stack) { + if (stack == null) { + return; + } + + // The client discards the stack too, so it does not show an item the server never stored. + if (!builder.getWorldObj().isRemote) { + builder.consumeForEmc(stack); + } + + onSlotChanged(); + } +} diff --git a/src/main/java/net/kanawo/emcbuilder/tile/EmcPool.java b/src/main/java/net/kanawo/emcbuilder/tile/EmcPool.java new file mode 100644 index 0000000..7cbab8d --- /dev/null +++ b/src/main/java/net/kanawo/emcbuilder/tile/EmcPool.java @@ -0,0 +1,93 @@ +package net.kanawo.emcbuilder.tile; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.nbt.NBTTagList; +import net.minecraft.world.World; +import net.minecraft.world.WorldSavedData; +import net.minecraft.world.storage.MapStorage; +import net.minecraftforge.common.util.Constants; + +/** + * EMC stored for EMC Builders, one balance per player, saved with the world (data/emcbuilder_emc.dat). All + * dimensions of a world share the same storage, so every EMC Builder a player owns in the world draws from the same + * balance. Server side only. + */ +public class EmcPool extends WorldSavedData { + + private static final String NAME = "emcbuilder_emc"; + private static final double MAX_EMC = moze_intel.projecte.utils.Constants.TILE_MAX_EMC; + + private final Map balances = new HashMap<>(); + + public EmcPool(String name) { + super(name); + } + + public static EmcPool get(World world) { + MapStorage storage = world.mapStorage; + EmcPool pool = (EmcPool) storage.loadData(EmcPool.class, NAME); + + if (pool == null) { + pool = new EmcPool(NAME); + storage.setData(NAME, pool); + } + + return pool; + } + + public double get(UUID player) { + Double emc = balances.get(player); + return emc == null ? 0 : emc; + } + + /** Adds (or, with a negative amount, removes) EMC, keeping the balance between 0 and the maximum. */ + public void add(UUID player, double amount) { + double emc = Math.min(MAX_EMC, Math.max(0, get(player) + amount)); + + if (emc == 0) { + balances.remove(player); + } else { + balances.put(player, emc); + } + + markDirty(); + } + + @Override + public void readFromNBT(NBTTagCompound nbt) { + balances.clear(); + NBTTagList list = nbt.getTagList("Players", Constants.NBT.TAG_COMPOUND); + + for (int i = 0; i < list.tagCount(); i++) { + NBTTagCompound entry = list.getCompoundTagAt(i); + balances.put( + new UUID(entry.getLong("UUIDMost"), entry.getLong("UUIDLeast")), + Math.min(MAX_EMC, Math.max(0, entry.getDouble("EMC")))); + } + } + + @Override + public void writeToNBT(NBTTagCompound nbt) { + NBTTagList list = new NBTTagList(); + + for (Map.Entry balance : balances.entrySet()) { + NBTTagCompound entry = new NBTTagCompound(); + entry.setLong( + "UUIDMost", + balance.getKey() + .getMostSignificantBits()); + entry.setLong( + "UUIDLeast", + balance.getKey() + .getLeastSignificantBits()); + entry.setDouble("EMC", balance.getValue()); + list.appendTag(entry); + } + + nbt.setTag("Players", list); + } +} diff --git a/src/main/java/net/kanawo/emcbuilder/tile/TileEMCBuilder.java b/src/main/java/net/kanawo/emcbuilder/tile/TileEMCBuilder.java new file mode 100644 index 0000000..727c497 --- /dev/null +++ b/src/main/java/net/kanawo/emcbuilder/tile/TileEMCBuilder.java @@ -0,0 +1,294 @@ +package net.kanawo.emcbuilder.tile; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; + +import buildcraft.builders.TileBuilder; +import buildcraft.core.blueprints.BptBuilderBase; +import buildcraft.core.blueprints.BptBuilderBlueprint; +import buildcraft.core.blueprints.RequirementItemStack; +import io.netty.buffer.ByteBuf; +import moze_intel.projecte.utils.EMCHelper; + +/** + * A Builder that pays for building materials with EMC. + *

+ * The EMC is not stored in the tile but in its owner's balance in {@link EmcPool}, so all EMC Builders a player owns + * in a world share it. The owner is the player who placed the builder, or for one built from a blueprint, the first + * player who opens it. + *

+ * BuildCraft only builds blocks whose materials are present in the builder's inventory, and consumes them by + * shrinking the stacks it finds there. While {@link #build()} runs, this tile therefore appends virtual slots after + * the real inventory, one per needed item that has an EMC value, sized to what the stored EMC can afford. After the + * build step the amount taken from each virtual slot is charged at 1 EMC per EMC value. Real items in the material + * slots come first in the inventory, so they are used before EMC. + *

+ * Outside {@link #build()} the virtual slots are hidden, so GUIs, hoppers, pipes and robots only ever see the real + * inventory. + */ +public class TileEMCBuilder extends TileBuilder { + + /** NBT keys of the owner's UUID; SchematicEMCBuilder removes them from blueprints. */ + public static final String OWNER_MOST = "OwnerUUIDMost"; + public static final String OWNER_LEAST = "OwnerUUIDLeast"; + + private static final int EMC_SYNC_INTERVAL = 10; + + private UUID owner; + + /** Owner's balance as last received from the server. Client side only. */ + private double clientEmc; + /** Owner's balance as last sent to clients. Server side only. */ + private double sentEmc = -1; + private int ticksSinceEmcSync; + + private final List emcSlots = new ArrayList<>(); + private boolean emcSlotsExposed; + + /** A virtual material slot backed by stored EMC. */ + private static final class EmcSlot { + + final long emcPerItem; + final int initialSize; + ItemStack stack; + + EmcSlot(ItemStack stack, long emcPerItem) { + this.stack = stack; + this.emcPerItem = emcPerItem; + this.initialSize = stack.stackSize; + } + + int consumed() { + return stack == null ? initialSize : initialSize - stack.stackSize; + } + } + + /** The owner's EMC balance; on the client, as last synced. */ + public double getStoredEmc() { + if (worldObj.isRemote) { + return clientEmc; + } + + return owner == null ? 0 + : EmcPool.get(worldObj) + .get(owner); + } + + /** Makes the player the owner if the builder has none yet. Server side only. */ + public void claimOwner(EntityPlayer player) { + if (owner == null) { + owner = player.getUniqueID(); + markDirty(); + } + } + + @Override + public void onBlockPlacedBy(EntityLivingBase entity, ItemStack stack) { + super.onBlockPlacedBy(entity, stack); + + if (entity instanceof EntityPlayer) { + owner = entity.getUniqueID(); + } + } + + /** Converts the whole stack into EMC for the owner. Server side only. */ + public void consumeForEmc(ItemStack stack) { + long value = EMCHelper.getEmcValue(stack); + + if (owner == null || value <= 0 || stack.stackSize <= 0) { + return; + } + + EmcPool.get(worldObj) + .add(owner, (double) value * stack.stackSize); + stack.stackSize = 0; + sendNetworkUpdate(); + } + + @Override + public void updateEntity() { + super.updateEntity(); + + if (worldObj.isRemote) { + return; + } + + // The balance can also change through the owner's other EMC Builders, so poll it. + if (++ticksSinceEmcSync >= EMC_SYNC_INTERVAL) { + ticksSinceEmcSync = 0; + + if (getStoredEmc() != sentEmc) { + sendNetworkUpdate(); + } + } + } + + @Override + public void build() { + BptBuilderBase bpt = getBlueprint(); + double emc = getStoredEmc(); + + if (!(bpt instanceof BptBuilderBlueprint) || emc <= 0) { + super.build(); + return; + } + + // The needed item list is only filled once the builder has been initialized. + bpt.initialize(); + prepareEmcSlots(((BptBuilderBlueprint) bpt).getNeededItems(), emc); + + emcSlotsExposed = true; + + try { + super.build(); + } finally { + emcSlotsExposed = false; + chargeEmcSlots(); + } + } + + private void prepareEmcSlots(List needs, double emc) { + emcSlots.clear(); + + if (needs == null) { + return; + } + + for (RequirementItemStack need : needs) { + if (need.stack == null || need.stack.getItem() == null || need.size <= 0) { + continue; + } + + long value = EMCHelper.getEmcValue(need.stack); + + if (value <= 0) { + continue; + } + + int affordable = (int) Math.min(Integer.MAX_VALUE, Math.floor(emc / value)); + int size = Math.min(Math.min(need.stack.getMaxStackSize(), need.size), affordable); + + if (size <= 0) { + continue; + } + + ItemStack stack = need.stack.copy(); + stack.stackSize = size; + emcSlots.add(new EmcSlot(stack, value)); + } + } + + private void chargeEmcSlots() { + double cost = 0; + + for (EmcSlot slot : emcSlots) { + cost += (double) slot.consumed() * slot.emcPerItem; + } + + emcSlots.clear(); + + if (cost > 0) { + EmcPool.get(worldObj) + .add(owner, -cost); + } + } + + private int realSize() { + return super.getSizeInventory(); + } + + private EmcSlot emcSlot(int i) { + return emcSlotsExposed && i >= realSize() ? emcSlots.get(i - realSize()) : null; + } + + @Override + public int getSizeInventory() { + return emcSlotsExposed ? realSize() + emcSlots.size() : realSize(); + } + + @Override + public ItemStack getStackInSlot(int i) { + EmcSlot slot = emcSlot(i); + return slot != null ? slot.stack : super.getStackInSlot(i); + } + + @Override + public ItemStack decrStackSize(int i, int amount) { + EmcSlot slot = emcSlot(i); + + if (slot == null) { + return super.decrStackSize(i, amount); + } + + if (slot.stack == null) { + return null; + } + + ItemStack taken = slot.stack.splitStack(Math.min(amount, slot.stack.stackSize)); + + if (slot.stack.stackSize <= 0) { + slot.stack = null; + } + + return taken; + } + + @Override + public void setInventorySlotContents(int i, ItemStack stack) { + EmcSlot slot = emcSlot(i); + + if (slot == null) { + super.setInventorySlotContents(i, stack); + return; + } + + // Schematic.useItem only writes to a slot once its stack is used up (possibly leaving a container item + // behind). Either way the whole virtual stack has been consumed; container items are not kept. + slot.stack = null; + } + + @Override + public ItemStack getStackInSlotOnClosing(int i) { + return emcSlot(i) != null ? null : super.getStackInSlotOnClosing(i); + } + + @Override + public boolean isItemValidForSlot(int i, ItemStack stack) { + return emcSlot(i) == null && super.isItemValidForSlot(i, stack); + } + + @Override + public void readFromNBT(NBTTagCompound nbt) { + super.readFromNBT(nbt); + owner = nbt.hasKey(OWNER_MOST) ? new UUID(nbt.getLong(OWNER_MOST), nbt.getLong(OWNER_LEAST)) : null; + } + + @Override + public void writeToNBT(NBTTagCompound nbt) { + super.writeToNBT(nbt); + + if (owner != null) { + nbt.setLong(OWNER_MOST, owner.getMostSignificantBits()); + nbt.setLong(OWNER_LEAST, owner.getLeastSignificantBits()); + } + } + + @Override + public void writeData(ByteBuf stream) { + super.writeData(stream); + sentEmc = getStoredEmc(); + stream.writeDouble(sentEmc); + } + + @Override + public void readData(ByteBuf stream) { + super.readData(stream); + clientEmc = stream.readDouble(); + } +} diff --git a/src/main/resources/assets/emcbuilder/lang/en_US.lang b/src/main/resources/assets/emcbuilder/lang/en_US.lang new file mode 100644 index 0000000..78f74f0 --- /dev/null +++ b/src/main/resources/assets/emcbuilder/lang/en_US.lang @@ -0,0 +1,2 @@ +tile.emcBuilder.name=EMC Builder +gui.emcbuilder.stored=EMC: %s diff --git a/src/main/resources/assets/emcbuilder/lang/ja_JP.lang b/src/main/resources/assets/emcbuilder/lang/ja_JP.lang new file mode 100644 index 0000000..25a1acf --- /dev/null +++ b/src/main/resources/assets/emcbuilder/lang/ja_JP.lang @@ -0,0 +1,2 @@ +tile.emcBuilder.name=EMCビルダー +gui.emcbuilder.stored=EMC: %s diff --git a/src/main/resources/assets/emcbuilder/textures/blocks/emcbuilderBlock/front.png b/src/main/resources/assets/emcbuilder/textures/blocks/emcbuilderBlock/front.png new file mode 100644 index 0000000000000000000000000000000000000000..18f7ffb2d11a873f6689d845a0ffaea4f61c5946 GIT binary patch literal 1115 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdzmUKs7M+SzC{oH>NS%G}c0*}aI z1_rM8Ak0`>*5v^dWH0gbb!ETD$uAh^ZLTs9R}KofFPOA-|-a&z*EttxDlzy@0Z*&tzkB|DpbJjp}_zQ5Pgu0zM-B0$jKnKm48uYD$o@mEA0$z!15?!Hu@lkAUVtq zA_DX)kOdDJP%yzmH#H9!q~*Y{)0r}Zn}LBT+|$J|B*Ojdb$cJ-M2X`c&nt?w@@4dP zbs2d)`k&DuB5dkWa>O@G-CD|Q&K!w^!u0Zjl0sv?Fy^BIu7-ty25x~efgKWS4}U!s zyYa}u6SdP{zptI2eeZd7`QGh}r_3g{-D>>r8*u)>o5#%wUyt)|C{E|A`TdJm#`3pW z*xq^5k1{VlSiRb74hrWBg+f$0y#7%zR`gLk*uS%^fCC{o`_pX@!` zuS$hj67{A#GjCOUb(i_hdYQ>NyBc2w8~iQcm)Kv;dge^e=XSqEGLjrkOIB8iaFrie zE3LZW{|}x_gMe6v^8f00|IS|}yfnzLQ2xp@1r4sIMMXbruC{fp-1=H>R@eFT&5<{A zw#~YnykOcbzgJJAgeOmanD07e@z0KBKFfZkZMn$wPyF@0wpJ%ax&M~e*WY7K*VCWm z#eACk?}O9R3tD%|KJ?4TtCO2!@m&uX78&XxdhPMI@7?2*nmy~lw{z+nUzteGs4f#R z$rOKIQYOTvzerh|SJ>VD9wvDtlLqvM?xbd%F6$taD0e F0svwizg7SM literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/emcbuilder/textures/blocks/emcbuilderBlock/side.png b/src/main/resources/assets/emcbuilder/textures/blocks/emcbuilderBlock/side.png new file mode 100644 index 0000000000000000000000000000000000000000..e1f4747d4f898e8f3100046d39c4c77f19940f53 GIT binary patch literal 1166 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdzmUKs7M+SzC{oH>NS%G}c0*}aI z1_rM8Ak0`>*5v^dWH0gbb!ETD$uAh^ZLTs9R}KofFPOA-|-a&z*EttxDlzy@0Z*&tzkB|DpbJjp}_zQ5Pgu0zM-B0$jKnKm48uYD$o@mEA0$z!15?!Hu@lkAUVtq zA_DX)kOdDJP%yzmH#H9!q~*Y{)0r}Zn}LC;%hSa%B*Ojb^|$`P=@Q33^7Aj{=nTpd z36YsPv-hEK))kFFA2!^X`*B0#E!Ex@+ zjLxN#Qx+I~H=Z$nKD+Eo6AjVB^PZpkzxnT#@QTmB`4}!1bJvm`-5To~L?}QglE3T#O`BN&8VkBu`E-v}Tqj6$(@WvA5lZF9(SqTqMi(jz+ zQnCb3@18hcg7s#O{*7Bavg?+bu2HOgF+F0}#Lg9mzkOxBY}?%b zGhSjwT_Kmuje3SDi~Y8Xh5pZz{dD9UA|BJt#a$Y=jy)+DJd)=*PPpP zvbnFuMLQZlyC0*JrgK%!>hYT#3t>IiTBS-hub;OU1aSN}xGk-=@Z`!1m;K}C_bruP z>Fabg>&MN1{}MLYit?l^dbNI{4D;mrKP;E#Ch}$E<}%NmH?QFThr4f=?>qG4ae-Fa z=7TrFO4P1A>;Amm?%1W-+SQWLHaeU%u|RFhC(~&4-`H68k=x zP0LfUWK;Xk^S-*gJmTlV$^PYPuOsyDFnW8RUa)zyaLDS1JvZz>7);CaE8^KxA+Rsu zT+iP1=ie+a<6OIDjlgX7UAvoh-rXm?OzCp-^SDVG%+*KdN&nzacyM2_hUIGxs5tO+ L^>bP0l+XkKmIUnr literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/emcbuilder/textures/blocks/emcbuilderBlock/top.png b/src/main/resources/assets/emcbuilder/textures/blocks/emcbuilderBlock/top.png new file mode 100644 index 0000000000000000000000000000000000000000..889139e3845f78111ca573f22353d193389b5bfb GIT binary patch literal 1500 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 jUp0bkT>eS_