package net.kanawo.bcxmodern.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(); } }