package crazypants.enderio.machine.farm;
import java.util.BitSet;
import javax.annotation.Nonnull;
import net.minecraft.block.Block;
import net.minecraft.enchantment.Enchantment;
import net.minecraft.enchantment.EnchantmentHelper;
import net.minecraft.entity.Entity;
import net.minecraft.entity.item.EntityItem;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.init.Blocks;
import net.minecraft.item.ItemShears;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.server.MinecraftServer;
import net.minecraft.world.EnumSkyBlock;
import net.minecraftforge.common.util.ForgeDirection;
import com.enderio.core.common.util.BlockCoord;
import cpw.mods.fml.common.network.NetworkRegistry.TargetPoint;
import crazypants.enderio.EnderIO;
import crazypants.enderio.ModObject;
import crazypants.enderio.config.Config;
import crazypants.enderio.machine.AbstractPoweredTaskEntity;
import crazypants.enderio.machine.ContinuousTask;
import crazypants.enderio.machine.IMachineRecipe.ResultStack;
import crazypants.enderio.machine.IPoweredTask;
import crazypants.enderio.machine.SlotDefinition;
import crazypants.enderio.machine.farm.farmers.FarmersCommune;
import crazypants.enderio.machine.farm.farmers.IHarvestResult;
import crazypants.enderio.machine.farm.farmers.RubberTreeFarmerIC2;
import crazypants.enderio.network.PacketHandler;
import crazypants.enderio.power.BasicCapacitor;
import crazypants.enderio.tool.ArrayMappingTool;
public class TileFarmStation extends AbstractPoweredTaskEntity {
private static final int TICKS_PER_WORK = 20;
public enum ToolType {
HOE {
@Override
boolean match(ItemStack item) {
for (ItemStack stack : Config.farmHoes) {
if (stack.getItem() == item.getItem()) {
return true;
}
}
return false;
}
},
AXE {
@Override
boolean match(ItemStack item) {
return item.getItem().getHarvestLevel(item, "axe") >= 0;
}
},
TREETAP {
@Override
boolean match(ItemStack item) {
return item.getItem().getClass() == RubberTreeFarmerIC2.treeTap;
}
},
SHEARS {
@Override
boolean match(ItemStack item) {
return item.getItem() instanceof ItemShears;
}
},
NONE {
@Override
boolean match(ItemStack item) {
return false;
}
};
public final boolean itemMatches(ItemStack item) {
if (item == null) {
return false;
}
return match(item) && !isBrokenTinkerTool(item);
}
private boolean isBrokenTinkerTool(ItemStack item)
{
return item.hasTagCompound() && item.getTagCompound().hasKey("InfiTool") && item.getTagCompound().getCompoundTag("InfiTool").getBoolean("Broken");
}
abstract boolean match(ItemStack item);
public static boolean isTool(ItemStack stack) {
for (ToolType type : values()) {
if (type.itemMatches(stack)) {
return true;
}
}
return false;
}
public static ToolType getToolType(ItemStack stack) {
for (ToolType type : values()) {
if (type.itemMatches(stack)) {
return type;
}
}
return NONE;
}
}
public static final String NOTIFICATION_NO_HOE = "noHoe";
public static final String NOTIFICATION_NO_AXE = "noAxe";
public static final String NOTIFICATION_NO_SEEDS = "noSeeds";
private BlockCoord lastScanned;
private EntityPlayerMP farmerJoe;
public static final int NUM_TOOL_SLOTS = 3;
private static final int minToolSlot = 0;
private static final int maxToolSlot = -1 + NUM_TOOL_SLOTS;
public static final int NUM_FERTILIZER_SLOTS = 2;
public static final int minFirtSlot = maxToolSlot + 1;
public static final int maxFirtSlot = maxToolSlot + NUM_FERTILIZER_SLOTS;
public static final int NUM_SUPPLY_SLOTS = 4;
public static final int minSupSlot = maxFirtSlot + 1;
public static final int maxSupSlot = maxFirtSlot + NUM_SUPPLY_SLOTS;
private final BitSet lockedSlots = new BitSet();
public String notification = "";
public boolean sendNotification = false;
private boolean wasActive;
public TileFarmStation() {
super(new SlotDefinition(9, 6, 1));
}
public int getFarmSize() {
return Config.farmDefaultSize + getUpgradeDist();
}
public void actionPerformed(boolean isAxe) {
if(isAxe) {
usePower(Config.farmAxeActionEnergyUseRF);
} else {
usePower(Config.farmActionEnergyUseRF);
}
clearNotification();
}
public boolean tillBlock(BlockCoord plantingLocation) {
BlockCoord dirtLoc = plantingLocation.getLocation(ForgeDirection.DOWN);
Block dirtBlock = getBlock(dirtLoc);
if((dirtBlock == Blocks.dirt || dirtBlock == Blocks.grass)) {
if(!hasHoe()) {
setNotification(NOTIFICATION_NO_HOE);
return false;
}
damageHoe(1, dirtLoc);
worldObj.setBlock(dirtLoc.x, dirtLoc.y, dirtLoc.z, Blocks.farmland);
worldObj.playSoundEffect(dirtLoc.x + 0.5F, dirtLoc.y + 0.5F, dirtLoc.z + 0.5F, Blocks.farmland.stepSound.getStepResourcePath(),
(Blocks.farmland.stepSound.getVolume() + 1.0F) / 2.0F, Blocks.farmland.stepSound.getPitch() * 0.8F);
actionPerformed(false);
return true;
} else if(dirtBlock == Blocks.farmland) {
return true;
}
return false;
}
public int getMaxLootingValue() {
int result = 0;
for (int i = minToolSlot; i <= maxToolSlot; i++) {
if(inventory[i] != null) {
int level = getLooting(inventory[i]);
if(level > result) {
result = level;
}
}
}
return result;
}
private int getUpgradeDist() {
int upg = slotDefinition.getMaxUpgradeSlot();
if(inventory[upg] == null) {
return 0;
} else {
return Config.farmBonusSize * inventory[upg].getItemDamage();
}
}
public boolean hasHoe() {
return hasTool(ToolType.HOE);
}
public boolean hasAxe() {
return hasTool(ToolType.AXE);
}
public boolean hasShears() {
return hasTool(ToolType.SHEARS);
}
public int getAxeLootingValue() {
ItemStack tool = getTool(ToolType.AXE);
if(tool == null) {
return 0;
}
return getLooting(tool);
}
public void damageAxe(Block blk, BlockCoord bc) {
damageTool(ToolType.AXE, blk, bc, 1);
}
public void damageHoe(int i, BlockCoord bc) {
damageTool(ToolType.HOE, null, bc, i);
}
public void damageShears(Block blk, BlockCoord bc) {
damageTool(ToolType.SHEARS, blk, bc, 1);
}
public boolean hasTool(ToolType type){
return getTool(type) != null;
}
private ItemStack getTool(ToolType type) {
for (int i = minToolSlot; i <= maxToolSlot; i++) {
if(type.itemMatches(inventory[i]) && inventory[i].stackSize>0) {
return inventory[i];
}
}
return null;
}
public void damageTool(ToolType type, Block blk, BlockCoord bc, int damage) {
ItemStack tool = getTool(type);
if(tool == null) {
return;
}
float rand = worldObj.rand.nextFloat();
if(rand >= Config.farmToolTakeDamageChance) {
return;
}
boolean canDamage = canDamage(tool);
if(type == ToolType.AXE) {
tool.getItem().onBlockDestroyed(tool, worldObj, blk, bc.x, bc.y, bc.z, farmerJoe);
} else if(type == ToolType.HOE) {
int origDamage = tool.getItemDamage();
tool.getItem().onItemUse(tool, farmerJoe, worldObj, bc.x, bc.y, bc.z, 1, 0.5f, 0.5f, 0.5f);
if(origDamage == tool.getItemDamage() && canDamage) {
tool.damageItem(1, farmerJoe);
}
} else if(canDamage) {
tool.damageItem(1, farmerJoe);
}
if(tool.stackSize == 0 || (canDamage && tool.getItemDamage() >= tool.getMaxDamage())) {
destroyTool(type);
}
}
private boolean canDamage(ItemStack stack) {
return stack != null && stack.isItemStackDamageable() && stack.getItem().isDamageable();
}
private void destroyTool(ToolType type) {
for (int i = minToolSlot; i <= maxToolSlot; i++) {
if(type.itemMatches(inventory[i]) && inventory[i].stackSize==0) {
inventory[i] = null;
markDirty();
return;
}
}
}
private int getLooting(ItemStack stack) {
return Math.max(
EnchantmentHelper.getEnchantmentLevel(Enchantment.looting.effectId, stack),
EnchantmentHelper.getEnchantmentLevel(Enchantment.fortune.effectId, stack));
}
public EntityPlayerMP getFakePlayer() {
return farmerJoe;
}
public Block getBlock(BlockCoord bc) {
return worldObj.getBlock(bc.x, bc.y, bc.z);
}
public Block getBlock(int x, int y, int z) {
return worldObj.getBlock(x, y, z);
}
public int getBlockMeta(BlockCoord bc) {
return worldObj.getBlockMetadata(bc.x, bc.y, bc.z);
}
public boolean isOpen(BlockCoord bc) {
Block block = worldObj.getBlock(bc.x, bc.y, bc.z);
return block.isAir(worldObj, bc.x, bc.y, bc.z) || block.isReplaceable(worldObj, bc.x, bc.y, bc.z);
}
public void setNotification(String unloc) {
String newNote = EnderIO.lang.localize("farm.note." + unloc);
if(!newNote.equals(notification)) {
notification = newNote;
sendNotification = true;
}
}
public void clearNotification() {
if(hasNotification()) {
notification = "";
sendNotification = true;
}
}
public boolean hasNotification() {
return !"".equals(notification);
}
private void sendNotification() {
PacketHandler.INSTANCE.sendToAll(new PacketUpdateNotification(this, notification));
}
@Override
protected boolean isMachineItemValidForSlot(int i, ItemStack stack) {
if(stack == null) {
return false;
}
if(i <= maxToolSlot) {
if (ToolType.isTool(stack)) {
for (int j = minToolSlot; j <= maxToolSlot; j++) {
if (ToolType.getToolType(stack).itemMatches(inventory[j])) {
return false;
}
}
return true;
}
return false;
} else if (i <= maxFirtSlot) {
return Fertilizer.isFertilizer(stack);
} else if (i <= maxSupSlot) {
return (inventory[i] != null || !isSlotLocked(i)) && FarmersCommune.instance.canPlant(stack);
} else {
return false;
}
}
@Override
public void doUpdate() {
super.doUpdate();
if(isActive() != wasActive) {
wasActive = isActive();
worldObj.updateLightByType(EnumSkyBlock.Block, xCoord, yCoord, zCoord);
}
}
@Override
protected boolean checkProgress(boolean redstoneChecksPassed) {
if(redstoneChecksPassed) {
usePower();
if(canTick(redstoneChecksPassed)) {
doTick();
}
}
return false;
}
protected boolean canTick(boolean redstoneChecksPassed) {
if(!shouldDoWorkThisTick(2)) {
return false;
}
if(getEnergyStored() < getPowerUsePerTick()) {
setNotification("noPower");
return false;
}
return true;
}
protected void doTick() {
if (sendNotification && shouldDoWorkThisTick(TICKS_PER_WORK)) {
sendNotification = false;
sendNotification();
}
if(!hasPower() && Config.farmActionEnergyUseRF > 0 && Config.farmAxeActionEnergyUseRF > 0) {
setNotification("noPower");
return;
}
if("noPower".equals(notification)) {
clearNotification();
}
BlockCoord bc = null;
int infiniteLoop = 20;
while (bc == null || bc.equals(getLocation()) || !worldObj.getChunkProvider().chunkExists(bc.x >> 4, bc.z >> 4)) {
if (infiniteLoop-- <= 0) {
return;
}
bc = getNextCoord();
}
Block block = worldObj.getBlock(bc.x, bc.y, bc.z);
if(block == null) {
return;
}
int meta = worldObj.getBlockMetadata(bc.x, bc.y, bc.z);
if(farmerJoe == null) {
farmerJoe = new FakeFarmPlayer(MinecraftServer.getServer().worldServerForDimension(worldObj.provider.dimensionId));
}
if(isOpen(bc)) {
FarmersCommune.instance.prepareBlock(this, bc, block, meta);
block = worldObj.getBlock(bc.x, bc.y, bc.z);
}
if(isOutputFull()) {
setNotification("outputFull");
return;
}
if(!hasPower() && Config.farmActionEnergyUseRF > 0 && Config.farmAxeActionEnergyUseRF > 0) {
setNotification("noPower");
return;
}
if(!isOpen(bc)) {
IHarvestResult harvest = FarmersCommune.instance.harvestBlock(this, bc, block, meta);
if(harvest != null && harvest.getDrops() != null) {
PacketFarmAction pkt = new PacketFarmAction(harvest.getHarvestedBlocks());
PacketHandler.INSTANCE.sendToAllAround(pkt, new TargetPoint(worldObj.provider.dimensionId, bc.x, bc.y, bc.z, 64));
for (EntityItem ei : harvest.getDrops()) {
if(ei != null) {
insertHarvestDrop(ei, bc);
if(!ei.isDead) {
worldObj.spawnEntityInWorld(ei);
}
}
}
return;
}
}
if(!hasPower() && (Config.farmBonemealActionEnergyUseRF > 0 || Config.farmBonemealTryEnergyUseRF > 0)) {
setNotification("noPower");
return;
}
if (hasBonemeal() && bonemealCooldown-- <= 0) {
Fertilizer fertilizer = Fertilizer.getInstance(inventory[minFirtSlot]);
if ((fertilizer.applyOnPlant() != isOpen(bc)) || (fertilizer.applyOnAir() == worldObj.isAirBlock(bc.x, bc.y, bc.z))) {
farmerJoe.inventory.mainInventory[0] = inventory[minFirtSlot];
farmerJoe.inventory.currentItem = 0;
if (fertilizer.apply(inventory[minFirtSlot], farmerJoe, worldObj, bc)) {
inventory[minFirtSlot] = farmerJoe.inventory.mainInventory[0];
PacketHandler.INSTANCE.sendToAllAround(new PacketFarmAction(bc), new TargetPoint(worldObj.provider.dimensionId, bc.x, bc.y, bc.z, 64));
if (inventory[minFirtSlot] != null && inventory[minFirtSlot].stackSize == 0) {
inventory[minFirtSlot] = null;
}
usePower(Config.farmBonemealActionEnergyUseRF);
bonemealCooldown = 20;
} else {
usePower(Config.farmBonemealTryEnergyUseRF);
bonemealCooldown = 5;
}
farmerJoe.inventory.mainInventory[0] = null;
}
}
}
private int bonemealCooldown = 5; // no need to persist this
private boolean hasBonemeal() {
if (inventory[minFirtSlot] != null) {
return true;
}
for (int i = minFirtSlot + 1; i <= maxFirtSlot; i++) {
if (inventory[i] != null) {
inventory[minFirtSlot] = inventory[i];
inventory[i] = null;
return true;
}
}
return false;
}
private boolean isOutputFull() {
for (int i = slotDefinition.minOutputSlot; i <= slotDefinition.maxOutputSlot; i++) {
ItemStack curStack = inventory[i];
if(curStack == null || curStack.stackSize < curStack.getMaxStackSize()) {
return false;
}
}
return true;
}
public boolean hasSeed(ItemStack seeds, BlockCoord bc) {
int slot = getSupplySlotForCoord(bc);
ItemStack inv = inventory[slot];
return inv != null && (inv.stackSize > 1 || !isSlotLocked(slot)) && inv.isItemEqual(seeds);
}
/*
* Returns a fuzzy boolean:
*
* <=0 - break no leaves for saplings
* 50 - break half the leaves for saplings
* 90 - break 90% of the leaves for saplings
*/
public int isLowOnSaplings(BlockCoord bc) {
int slot = getSupplySlotForCoord(bc);
ItemStack inv = inventory[slot];
return 90 * (Config.farmSaplingReserveAmount - (inv == null ? 0 : inv.stackSize)) / Config.farmSaplingReserveAmount;
}
public ItemStack takeSeedFromSupplies(ItemStack stack, BlockCoord forBlock) {
return takeSeedFromSupplies(stack, forBlock, true);
}
public ItemStack takeSeedFromSupplies(ItemStack stack, BlockCoord forBlock, boolean matchMetadata) {
if(stack == null || forBlock == null) {
return null;
}
int slot = getSupplySlotForCoord(forBlock);
ItemStack inv = inventory[slot];
if(inv != null) {
if(matchMetadata ? inv.isItemEqual(stack) : inv.getItem() == stack.getItem()) {
if (inv.stackSize <= 1 && isSlotLocked(slot)) {
return null;
}
ItemStack result = inv.copy();
result.stackSize = 1;
inv = inv.copy();
inv.stackSize--;
if(inv.stackSize == 0) {
inv = null;
}
setInventorySlotContents(slot, inv);
return result;
}
}
return null;
}
public ItemStack takeSeedFromSupplies(BlockCoord bc) {
return takeSeedFromSupplies(getSeedTypeInSuppliesFor(bc), bc);
}
public ItemStack getSeedTypeInSuppliesFor(BlockCoord bc) {
int slot = getSupplySlotForCoord(bc);
ItemStack inv = inventory[slot];
if(inv != null && (inv.stackSize > 1 || !isSlotLocked(slot))) {
return inv.copy();
}
return null;
}
protected int getSupplySlotForCoord(BlockCoord forBlock) {
if(forBlock.x <= xCoord && forBlock.z > zCoord) {
return minSupSlot;
} else if(forBlock.x > xCoord && forBlock.z > zCoord - 1) {
return minSupSlot + 1;
} else if(forBlock.x < xCoord && forBlock.z <= zCoord) {
return minSupSlot + 2;
}
return minSupSlot + 3;
}
private void insertHarvestDrop(Entity entity, BlockCoord bc) {
if(!worldObj.isRemote) {
if(entity instanceof EntityItem && !entity.isDead) {
EntityItem item = (EntityItem) entity;
ItemStack stack = item.getEntityItem().copy();
int numInserted = insertResult(stack, bc);
stack.stackSize -= numInserted;
item.setEntityItemStack(stack);
if(stack.stackSize == 0) {
item.setDead();
}
}
}
}
private int insertResult(ItemStack stack, BlockCoord bc) {
int slot = bc != null ? getSupplySlotForCoord(bc) : minSupSlot;
int[] slots = new int[NUM_SUPPLY_SLOTS];
int k = 0;
for (int j = slot; j <= maxSupSlot; j++) {
slots[k++] = j;
}
for (int j = minSupSlot; j < slot; j++) {
slots[k++] = j;
}
int origSize = stack.stackSize;
stack = stack.copy();
int inserted = 0;
for (int j = 0; j < slots.length && inserted < stack.stackSize; j++) {
int i = slots[j];
ItemStack curStack = inventory[i];
int inventoryStackLimit = getInventoryStackLimit(i);
if(isItemValidForSlot(i, stack) && (curStack == null || curStack.stackSize < inventoryStackLimit)) {
if(curStack == null) {
if (stack.stackSize < inventoryStackLimit) {
inventory[i] = stack.copy();
inserted = stack.stackSize;
} else {
inventory[i] = stack.copy();
inserted = inventoryStackLimit;
inventory[i].stackSize = inserted;
}
} else if(curStack.isItemEqual(stack)) {
inserted = Math.min(inventoryStackLimit - curStack.stackSize, stack.stackSize);
inventory[i].stackSize += inserted;
}
}
}
stack.stackSize -= inserted;
if(inserted >= origSize) {
return origSize;
}
ResultStack[] in = new ResultStack[] { new ResultStack(stack) };
mergeResults(in);
return origSize - (in[0].item == null ? 0 : in[0].item.stackSize);
}
private @Nonnull BlockCoord getNextCoord() {
int size = getFarmSize();
BlockCoord loc = getLocation();
if(lastScanned == null) {
return lastScanned = new BlockCoord(loc.x - size, loc.y, loc.z - size);
}
int nextX = lastScanned.x + 1;
int nextZ = lastScanned.z;
if(nextX > loc.x + size) {
nextX = loc.x - size;
nextZ += 1;
if(nextZ > loc.z + size) {
nextX = loc.x - size;
nextZ = loc.z - size;
}
}
return lastScanned = new BlockCoord(nextX, lastScanned.y, nextZ);
}
public void toggleLockedState(int slot) {
if (worldObj.isRemote) {
PacketHandler.INSTANCE.sendToServer(new PacketFarmLockedSlot(this, slot));
}
lockedSlots.flip(slot);
}
public boolean isSlotLocked(int slot) {
return lockedSlots.get(slot);
}
@Override
public String getInventoryName() {
return EnderIO.blockFarmStation.getLocalizedName();
}
@Override
public boolean hasCustomInventoryName() {
return false;
}
@Override
public String getMachineName() {
return ModObject.blockFarmStation.unlocalisedName;
}
@Override
public float getProgress() {
return 0.5f;
}
@Override
public void onCapacitorTypeChange() {
int ppt = calcPowerUsePerTick();
switch (getCapacitorType()) {
case BASIC_CAPACITOR:
setCapacitor(new BasicCapacitor(ppt * 40, 250000, ppt));
break;
case ACTIVATED_CAPACITOR:
setCapacitor(new BasicCapacitor(ppt * 40, 500000, ppt));
break;
case ENDER_CAPACITOR:
setCapacitor(new BasicCapacitor(ppt * 40, 1000000, ppt));
break;
}
currentTask = createTask();
}
private int calcPowerUsePerTick() {
return Math.round(Config.farmContinuousEnergyUseRF * (getFarmSize()/(float)Config.farmDefaultSize ));
}
@Override
public void readCustomNBT(NBTTagCompound nbtRoot) {
super.readCustomNBT(nbtRoot);
currentTask = createTask();
}
@Override
public void readCommon(NBTTagCompound nbtRoot) {
super.readCommon(nbtRoot);
lockedSlots.clear();
for (int i : nbtRoot.getIntArray("lockedSlots")) {
lockedSlots.set(i);
}
int slotLayoutVersion = nbtRoot.getInteger("slotLayoutVersion");
if (slotLayoutVersion == 0) {
inventory = (new ArrayMappingTool<ItemStack>("TTSSSSOOOOC", "TTTBBSSSSOOOOOOC")).map(inventory);
} else if (slotLayoutVersion == 1) {
inventory = (new ArrayMappingTool<ItemStack>("TTTSSSSOOOOC", "TTTBBSSSSOOOOOOC")).map(inventory);
} else if (slotLayoutVersion == 2) {
inventory = (new ArrayMappingTool<ItemStack>("TTTSSSSOOOOOOC", "TTTBBSSSSOOOOOOC")).map(inventory);
}
}
IPoweredTask createTask() {
return new ContinuousTask(getPowerUsePerTick());
}
@Override
public void writeCustomNBT(NBTTagCompound nbtRoot) {
super.writeCustomNBT(nbtRoot);
nbtRoot.setBoolean("isActive", isActive());
}
@Override
public void writeCommon(NBTTagCompound nbtRoot) {
super.writeCommon(nbtRoot);
if(!lockedSlots.isEmpty()) {
int[] locked = new int[lockedSlots.cardinality()];
for (int i=0,bit=-1; (bit=lockedSlots.nextSetBit(bit+1)) >= 0; i++) {
locked[i] = bit;
}
nbtRoot.setIntArray("lockedSlots", locked);
}
nbtRoot.setInteger("slotLayoutVersion", 3);
}
@Override
public int getInventoryStackLimit(int slot) {
if (slot >= minSupSlot && slot <= maxSupSlot) {
switch (getCapacitorType()) {
case BASIC_CAPACITOR:
return 16;
case ACTIVATED_CAPACITOR:
return 32;
case ENDER_CAPACITOR:
return 64;
}
}
return 64;
}
@Override
public int getInventoryStackLimit() {
// We return the (lowered) input slot limit here, so others who insert into us
// will behave nicely.
return getInventoryStackLimit(minSupSlot);
}
}