package slimeknights.tconstruct.smeltery.tileentity;
import net.minecraft.block.state.IBlockState;
import net.minecraft.client.gui.inventory.GuiContainer;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.item.EntityItem;
import net.minecraft.entity.player.InventoryPlayer;
import net.minecraft.inventory.Container;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.DamageSource;
import net.minecraft.util.ITickable;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import net.minecraft.world.WorldServer;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
import org.apache.logging.log4j.Logger;
import java.util.List;
import javax.annotation.Nonnull;
import slimeknights.mantle.common.IInventoryGui;
import slimeknights.tconstruct.common.TinkerNetwork;
import slimeknights.tconstruct.library.TinkerRegistry;
import slimeknights.tconstruct.library.Util;
import slimeknights.tconstruct.library.materials.Material;
import slimeknights.tconstruct.library.smeltery.AlloyRecipe;
import slimeknights.tconstruct.library.smeltery.ISmelteryTankHandler;
import slimeknights.tconstruct.library.smeltery.MeltingRecipe;
import slimeknights.tconstruct.library.smeltery.SmelteryTank;
import slimeknights.tconstruct.library.utils.TagUtil;
import slimeknights.tconstruct.shared.TinkerFluids;
import slimeknights.tconstruct.smeltery.client.GuiSmeltery;
import slimeknights.tconstruct.smeltery.events.TinkerSmelteryEvent;
import slimeknights.tconstruct.smeltery.inventory.ContainerSmeltery;
import slimeknights.tconstruct.smeltery.multiblock.MultiblockDetection;
import slimeknights.tconstruct.smeltery.multiblock.MultiblockSmeltery;
import slimeknights.tconstruct.smeltery.network.SmelteryFluidUpdatePacket;
import slimeknights.tconstruct.smeltery.network.SmelteryInventoryUpdatePacket;
public class TileSmeltery extends TileHeatingStructureFuelTank<MultiblockSmeltery> implements ITickable, IInventoryGui,
ISmelteryTankHandler {
public static final DamageSource smelteryDamage = new DamageSource("smeltery").setFireDamage();
static final Logger log = Util.getLogger("Smeltery");
// NBT tags
public static final String TAG_INSIDEPOS = "insidePos";
protected static final int CAPACITY_PER_BLOCK = Material.VALUE_Ingot * 8;
protected static final int ALLOYING_PER_TICK = 10; // how much liquid can be created per tick to make alloys
// Info about the state of the smeltery. Liquids etc.
protected SmelteryTank liquids;
protected int tick;
private BlockPos insideCheck; // last checked position for validity inside the smeltery
private int fullCheckCounter = 0;
public TileSmeltery() {
super("gui.smeltery.name", 0, 1);
setMultiblock(new MultiblockSmeltery(this));
liquids = new SmelteryTank(this);
}
@Override
public void update() {
if(isClientWorld()) {
return;
}
// are we fully formed?
if(!isActive()) {
// check for smeltery once per second
if(tick == 0) {
checkMultiblockStructure();
}
}
else {
// smeltery structure is there.. do stuff with the current fuel
// this also updates the needsFuel flag, which causes us to consume fuel at the end.
// This way fuel is only consumed if it's actually needed
if(tick == 0) {
interactWithEntitiesInside();
}
if(tick % 4 == 0) {
heatItems();
alloyAlloys();
}
if(needsFuel) {
consumeFuel();
}
// we gradually check if the inside of the smeltery is blocked (for performance reasons)
if(tick == 0) {
// called every second, we check every 15s or so
if(++fullCheckCounter >= 15) {
fullCheckCounter = 0;
checkMultiblockStructure();
}
else {
// outside or unset?
updateInsideCheck();
if(!getWorld().isAirBlock(insideCheck)) {
// we broke. inside blocked. :(
setInvalid();
insideCheck = null;
IBlockState state = getWorld().getBlockState(this.pos);
getWorld().notifyBlockUpdate(getPos(), state, state, 3);
}
else {
// advance to next block
progressInsideCheck();
}
}
}
}
tick = (tick + 1) % 20;
}
private void updateInsideCheck() {
if(insideCheck == null
|| insideCheck.getX() < minPos.getX()
|| insideCheck.getY() < minPos.getY()
|| insideCheck.getZ() < minPos.getZ()
|| insideCheck.getX() > maxPos.getX()
|| insideCheck.getY() > maxPos.getY()
|| insideCheck.getZ() > maxPos.getZ()) {
insideCheck = minPos;
}
}
private void progressInsideCheck() {
insideCheck = insideCheck.add(1, 0, 0);
if(insideCheck.getX() > maxPos.getX()) {
insideCheck = new BlockPos(minPos.getX(), insideCheck.getY(), insideCheck.getZ() + 1);
if(insideCheck.getZ() > maxPos.getZ()) {
insideCheck = new BlockPos(minPos.getX(), insideCheck.getY() + 1, minPos.getZ());
}
}
}
/* Smeltery processing logic. Consuming fuel, heating stuff, creating alloys etc. */
@Override
protected void updateHeatRequired(int index) {
ItemStack stack = getStackInSlot(index);
if(stack != null) {
MeltingRecipe melting = TinkerRegistry.getMelting(stack);
if(melting != null) {
setHeatRequiredForSlot(index, Math.max(5, melting.getUsableTemperature()));
// instantly consume fuel if required
if(!hasFuel()) {
consumeFuel();
}
return;
}
}
setHeatRequiredForSlot(index, 0);
}
// melt stuff
@Override
protected boolean onItemFinishedHeating(ItemStack stack, int slot) {
MeltingRecipe recipe = TinkerRegistry.getMelting(stack);
if(recipe == null) {
return false;
}
TinkerSmelteryEvent.OnMelting event = TinkerSmelteryEvent.OnMelting.fireEvent(this, stack, recipe.output.copy());
int filled = liquids.fill(event.result, false);
if(filled == event.result.amount) {
liquids.fill(event.result, true);
// only clear out items n stuff if it was successful
setInventorySlotContents(slot, null);
return true;
}
else {
// can't fill into the smeltery, set error state
itemTemperatures[slot] = itemTempRequired[slot] * 2 + 1;
}
return false;
}
// This is how you get blisters
protected void interactWithEntitiesInside() {
// find all entities inside the smeltery
AxisAlignedBB bb = info.getBoundingBox().contract(1).offset(0, 0.5, 0).expand(0, 0.5, 0);
List<Entity> entities = getWorld().getEntitiesWithinAABB(Entity.class, bb);
for(Entity entity : entities) {
// item?
if(entity instanceof EntityItem) {
if(TinkerRegistry.getMelting(((EntityItem) entity).getEntityItem()) != null) {
ItemStack stack = ((EntityItem) entity).getEntityItem();
// pick it up if we can melt it
for(int i = 0; i < this.getSizeInventory(); i++) {
if(!isStackInSlot(i)) {
// remove 1 from the stack and add it to the smeltery
ItemStack invStack = stack.copy();
stack.stackSize--;
invStack.stackSize = 1;
this.setInventorySlotContents(i, invStack);
}
if(stack.stackSize <= 0) {
// picked up whole stack
entity.setDead();
break;
}
}
}
}
// we only melt living entities if we have something in the smeltery
else if(liquids.getFluidAmount() > 0) {
// custom melting?
FluidStack fluid = TinkerRegistry.getMeltingForEntity(entity);
// no custom melting but a living entity that's alive?
if(fluid == null && entity instanceof EntityLivingBase) {
if(entity.isEntityAlive() && !entity.isDead) {
fluid = new FluidStack(TinkerFluids.blood, 10);
}
}
if(fluid != null) {
// hurt it
if(entity.attackEntityFrom(smelteryDamage, 2f)) {
// spill the blood
liquids.fill(fluid.copy(), true);
}
}
}
}
}
// check for alloys and create them
protected void alloyAlloys() {
for(AlloyRecipe recipe : TinkerRegistry.getAlloys()) {
// find out how often we can apply the recipe
int matched = recipe.matches(liquids.getFluids());
if(matched > ALLOYING_PER_TICK) {
matched = ALLOYING_PER_TICK;
}
while(matched > 0) {
// remove all liquids from the tank
for(FluidStack liquid : recipe.getFluids()) {
FluidStack toDrain = liquid.copy();
FluidStack drained = liquids.drain(toDrain, true);
// error logging
if(!drained.isFluidEqual(toDrain) || drained.amount != toDrain.amount) {
log.error("Smeltery alloy creation drained incorrect amount: was %s:%d, should be %s:%d", drained
.getUnlocalizedName(), drained.amount, toDrain.getUnlocalizedName(), toDrain.amount);
}
}
// and insert the alloy
FluidStack toFill = recipe.getResult().copy();
int filled = liquids.fill(toFill, true);
if(filled != recipe.getResult().amount) {
log.error("Smeltery alloy creation filled incorrect amount: was %d, should be %d (%s)", filled,
recipe.getResult().amount * matched, recipe.getResult().getUnlocalizedName());
}
matched -= filled;
}
}
}
/* Smeltery Multiblock Detection/Formation */
@Override
protected void updateStructureInfo(MultiblockDetection.MultiblockStructure structure) {
super.updateStructureInfo(structure);
this.liquids.setCapacity(getSizeInventory() * CAPACITY_PER_BLOCK);
}
@Override
protected boolean hasCeiling() {
return false;
}
@Override
protected int getUpdatedInventorySize(int width, int height, int depth) {
return width * height * depth;
}
/* Fluid handling */
@Override
public SmelteryTank getTank() {
return liquids;
}
/* GUI */
@Override
public Container createContainer(InventoryPlayer inventoryplayer, World world, BlockPos pos) {
return new ContainerSmeltery(inventoryplayer, this);
}
@Override
@SideOnly(Side.CLIENT)
public GuiContainer createGui(InventoryPlayer inventoryplayer, World world, BlockPos pos) {
return new GuiSmeltery((ContainerSmeltery) createContainer(inventoryplayer, world, pos), this);
}
@Nonnull
@Override
public AxisAlignedBB getRenderBoundingBox() {
if(minPos == null || maxPos == null) {
return super.getRenderBoundingBox();
}
return new AxisAlignedBB(minPos.getX(), minPos.getY(), minPos.getZ(), maxPos.getX() + 1, maxPos.getY() + 1, maxPos.getZ() + 1);
}
/* Network & Saving */
@Override
public void setInventorySlotContents(int slot, ItemStack itemstack) {
// send to client if needed
if(this.getWorld() != null && this.getWorld() instanceof WorldServer && !this.getWorld().isRemote && !ItemStack.areItemStacksEqual(itemstack, getStackInSlot(slot))) {
TinkerNetwork.sendToClients((WorldServer) this.getWorld(), this.pos, new SmelteryInventoryUpdatePacket(itemstack, slot, pos));
}
super.setInventorySlotContents(slot, itemstack);
}
@Override
@SideOnly(Side.CLIENT)
public void updateFluidsFromPacket(List<FluidStack> fluids) {
this.liquids.setFluids(fluids);
}
@Override
public void onTankChanged(List<FluidStack> fluids, FluidStack changed) {
// notify clients of liquid changes.
// the null check is to prevent potential crashes during loading
if(isServerWorld()) {
TinkerNetwork.sendToAll(new SmelteryFluidUpdatePacket(pos, fluids));
}
}
@Nonnull
@Override
public NBTTagCompound writeToNBT(NBTTagCompound compound) {
compound = super.writeToNBT(compound);
liquids.writeToNBT(compound);
compound.setTag(TAG_INSIDEPOS, TagUtil.writePos(insideCheck));
return compound;
}
@Override
public void readFromNBT(NBTTagCompound compound) {
super.readFromNBT(compound);
liquids.readFromNBT(compound);
insideCheck = TagUtil.readPos(compound.getCompoundTag(TAG_INSIDEPOS));
}
}