package slimeknights.tconstruct.smeltery.tileentity;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.init.SoundEvents;
import net.minecraft.inventory.ISidedInventory;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.network.play.server.SPacketParticles;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.EnumParticleTypes;
import net.minecraft.util.ITickable;
import net.minecraft.util.SoundCategory;
import net.minecraft.world.WorldServer;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.fluids.Fluid;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.capability.CapabilityFluidHandler;
import net.minecraftforge.fluids.capability.IFluidHandler;
import net.minecraftforge.fml.common.FMLCommonHandler;
import net.minecraftforge.items.ItemHandlerHelper;
import net.minecraftforge.items.wrapper.SidedInvWrapper;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import slimeknights.tconstruct.common.TinkerNetwork;
import slimeknights.tconstruct.library.fluid.FluidHandlerCasting;
import slimeknights.tconstruct.library.fluid.FluidTankAnimated;
import slimeknights.tconstruct.library.smeltery.ICastingRecipe;
import slimeknights.tconstruct.library.tileentity.IProgress;
import slimeknights.tconstruct.shared.tileentity.TileTable;
import slimeknights.tconstruct.smeltery.events.TinkerCastingEvent;
import slimeknights.tconstruct.smeltery.network.FluidUpdatePacket;
public abstract class TileCasting extends TileTable implements ITickable, ISidedInventory, IProgress {
// the internal fluidtank of the casting block
public FluidTankAnimated tank;
public IFluidHandler fluidHandler;
protected int timer; // timer for recipe cooldown
protected ICastingRecipe recipe; // current recipe
public TileCasting() {
super("casting", 2, 1); // 2 slots. 0 == input, 1 == output
// initialize with empty tank
tank = new FluidTankAnimated(0, this);
fluidHandler = new FluidHandlerCasting(this, tank);
// use a SidedInventory Wrapper to respect the canInsert/Extract calls
this.itemHandler = new SidedInvWrapper(this, EnumFacing.DOWN);
}
// capability
@Override
public boolean hasCapability(@Nonnull Capability<?> capability, @Nullable EnumFacing facing) {
if(capability == CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY) {
return true;
}
return super.hasCapability(capability, facing);
}
@SuppressWarnings("unchecked")
@Nonnull
@Override
public <T> T getCapability(@Nonnull Capability<T> capability, @Nullable EnumFacing facing) {
if(capability == CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY) {
return (T) fluidHandler;
}
return super.getCapability(capability, facing);
}
/* Inventory Management */
public void interact(EntityPlayer player) {
// can't interact if liquid inside
if(tank.getFluidAmount() > 0) {
return;
}
// completely empty -> insert current item into input
if(!isStackInSlot(0) && !isStackInSlot(1)) {
ItemStack stack = player.inventory.decrStackSize(player.inventory.currentItem, stackSizeLimit);
setInventorySlotContents(0, stack);
}
// take item out
else {
// take out of stack 1 if something is in there, 0 otherwise
int slot = isStackInSlot(1) ? 1 : 0;
// Additional Info: Only 1 item can only be put into the casting block usually, however recipes
// can have Itemstacks with stacksize > 1 as output
// we therefore spill the whole contents on extraction
ItemStack stack = getStackInSlot(slot);
if(slot == 1) {
FMLCommonHandler.instance().firePlayerSmeltedEvent(player, stack);
}
ItemHandlerHelper.giveItemToPlayer(player, stack);
setInventorySlotContents(slot, null);
// send a block update for the comparator, needs to be done after the stack is removed
if(slot == 1) {
this.getWorld().notifyNeighborsOfStateChange(this.pos, this.getBlockType());
}
}
}
@Nonnull
@Override
public int[] getSlotsForFace(@Nonnull EnumFacing side) {
return new int[]{0, 1};
}
@Override
public boolean canInsertItem(int index, @Nonnull ItemStack itemStackIn, @Nonnull EnumFacing direction) {
return index == 0 && !isStackInSlot(1);
}
@Override
public boolean canExtractItem(int index, @Nonnull ItemStack stack, @Nonnull EnumFacing direction) {
return index == 1;
}
/* Logic */
@Override
public void update() {
// no recipeeeh
if(recipe == null) {
return;
}
// fully filled
if(tank.getFluidAmount() == tank.getCapacity()) {
timer++;
if(!getWorld().isRemote) {
if(timer >= recipe.getTime()) {
TinkerCastingEvent.OnCasted event = TinkerCastingEvent.OnCasted.fire(recipe, this);
// done, finish!
if(event.consumeCast) {
// todo: play breaking sound and animation
setInventorySlotContents(0, null);
for(EntityPlayer player : getWorld().playerEntities) {
if(player.getDistanceSq(pos) < 1024 && player instanceof EntityPlayerMP) {
TinkerNetwork.sendPacket(player, new SPacketParticles(EnumParticleTypes.FLAME, false,
pos.getX() + 0.5f,
pos.getY() + 1.1f,
pos.getZ() + 0.5f,
0.25f, 0.0125f, 0.25f,
0.005f, 5));
}
}
}
// put result into output
if(event.switchOutputs) {
setInventorySlotContents(1, getStackInSlot(0));
setInventorySlotContents(0, event.output);
}
else {
setInventorySlotContents(1, event.output);
}
getWorld().playSound(null, pos, SoundEvents.BLOCK_LAVA_EXTINGUISH, SoundCategory.AMBIENT, 0.07f, 4f);
// comparator update
getWorld().notifyNeighborsOfStateChange(this.pos, this.getBlockType());
// reset state
reset();
}
}
else if(getWorld().rand.nextFloat() > 0.9f) {
getWorld().spawnParticle(EnumParticleTypes.SMOKE_NORMAL, pos.getX() + getWorld().rand.nextDouble(), pos.getY() + 1.1, pos.getZ() + getWorld().rand.nextDouble(), 0.0D, 0.0D, 0.0D);
}
}
}
@Override
public float getProgress() {
if(recipe == null || tank.getFluidAmount() == 0) {
return 0f;
}
return Math.min(1f, (float) timer / (float) recipe.getTime());
}
public ItemStack getCurrentResult() {
if(recipe == null) {
return null;
}
Fluid fluid = null;
if(tank.getFluid() != null) {
fluid = tank.getFluid().getFluid();
}
return recipe.getResult(getStackInSlot(0), fluid);
}
/** Return the recipe for the current state, if one exists. Don't forget to fire the OnCasting event! */
protected abstract ICastingRecipe findRecipe(ItemStack cast, Fluid fluid);
protected ICastingRecipe findRecipe(Fluid fluid) {
ICastingRecipe recipe = findRecipe(getStackInSlot(0), fluid);
if(TinkerCastingEvent.OnCasting.fire(recipe, this)) {
return recipe;
}
// event was cancelled
return null;
}
/** Sets the state for a new casting recipe, returns the fluid amount needed for casting */
public int initNewCasting(Fluid fluid, boolean setNewRecipe) {
ICastingRecipe recipe = findRecipe(fluid);
if(recipe != null) {
if(setNewRecipe) {
this.recipe = recipe;
}
return recipe.getFluidAmount();
}
return 0;
}
/** Resets the current state completely */
public void reset() {
timer = 0;
recipe = null;
tank.setCapacity(0);
tank.setFluid(null);
tank.renderOffset = 0;
if(getWorld() != null && !getWorld().isRemote && getWorld() instanceof WorldServer) {
TinkerNetwork.sendToClients((WorldServer) getWorld(), pos, new FluidUpdatePacket(pos, null));
}
}
// called clientside to sync with the server and on load
public void updateFluidTo(FluidStack fluid) {
int oldAmount = tank.getFluidAmount();
tank.setFluid(fluid);
if(fluid == null) {
reset();
return;
}
else if(recipe == null) {
recipe = findRecipe(fluid.getFluid());
if(recipe != null) {
tank.setCapacity(recipe.getFluidAmount());
}
}
tank.renderOffset += tank.getFluidAmount() - oldAmount;
}
/**
* @return The current comparator strength based on if an output exists
*/
public int comparatorStrength() {
return isStackInSlot(1) ? 15 : 0;
}
/* Saving and Loading */
@Nonnull
@Override
public NBTTagCompound writeToNBT(NBTTagCompound tags) {
tags = super.writeToNBT(tags);
NBTTagCompound tankTag = new NBTTagCompound();
tank.writeToNBT(tankTag);
tags.setTag("tank", tankTag);
tags.setInteger("timer", timer);
return tags;
}
@Override
public void readFromNBT(NBTTagCompound tags) {
super.readFromNBT(tags);
tank.readFromNBT(tags.getCompoundTag("tank"));
updateFluidTo(tank.getFluid());
timer = tags.getInteger("timer");
}
}