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 <noreply@anthropic.com>
This commit is contained in:
agomizi1cmandClaude Opus 5.5 committed 2026-10-27 00:59:45 +09:00
1 parent 536e567348
commit 870e5d13eb
18 files changed
+999 -2

No files matched your search

@@ -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());
}
}
@@ -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) {}
@@ -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;
@@ -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];
}
}
}
@@ -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);
}
}
}
@@ -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;
}
}
@@ -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<String> 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<RequirementItemStack> 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<GuiButton>) buttonList) {
b.visible = true;
}
} else {
getContainerBuilder().getScrollbar().hidden = true;
for (AdvancedSlot slot : slots) {
((SlotBuilderRequirement) slot).stack = null;
}
for (GuiButton b : (List<GuiButton>) 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);
}
}
}
@@ -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;
}
}
@@ -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();
}
}
@@ -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<UUID, Double> 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<UUID, Double> 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);
}
}
@@ -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.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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<EmcSlot> 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<RequirementItemStack> 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();
}
}
@@ -0,0 +1,2 @@
tile.emcBuilder.name=EMC Builder
gui.emcbuilder.stored=EMC: %s
@@ -0,0 +1,2 @@
tile.emcBuilder.name=EMCビルダー
gui.emcbuilder.stored=EMC: %s
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB