/**
* Copyright (c) Lambda Innovation, 2013-2016
* This file is part of the AcademyCraft mod.
* https://github.com/LambdaInnovation/AcademyCraft
* Licensed under GPLv3, see project root for more information.
*/
package cn.academy.crafting.block;
import cn.academy.core.block.TileReceiverBase;
import cn.academy.core.client.render.block.RenderDynamicBlock;
import cn.academy.core.client.sound.ACSounds;
import cn.academy.core.client.sound.PositionedSound;
import cn.academy.core.client.sound.TileEntitySound;
import cn.academy.crafting.ModuleCrafting;
import cn.academy.crafting.api.ImagFusorRecipes;
import cn.academy.crafting.api.ImagFusorRecipes.IFRecipe;
import cn.academy.crafting.item.ItemMatterUnit;
import cn.academy.energy.IFConstants;
import cn.academy.support.EnergyItemHelper;
import cn.lambdalib.annoreg.core.Registrant;
import cn.lambdalib.annoreg.mc.RegTileEntity;
import cn.lambdalib.s11n.network.TargetPoints;
import cn.lambdalib.s11n.network.NetworkMessage;
import cn.lambdalib.s11n.network.NetworkMessage.Listener;
import cn.lambdalib.util.mc.StackUtils;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import net.minecraft.inventory.ISidedInventory;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraftforge.common.util.ForgeDirection;
import net.minecraftforge.fluids.*;
/**
* @author WeAthFolD
*/
@Registrant
@RegTileEntity
@RegTileEntity.HasRender
public class TileImagFusor extends TileReceiverBase implements IFluidHandler, ISidedInventory {
static final double WORK_SPEED = 1.0 / 120;
static final double CONSUME_PER_TICK = 12;
static final int SYNC_INTV = 5;
@RegTileEntity.Render
@SideOnly(Side.CLIENT)
public static RenderDynamicBlock renderer;
static final int TANK_SIZE = 8000;
static final int PER_UNIT = 1000;
//Inventory id:
public static final int
SLOT_INPUT = 0,
SLOT_OUTPUT = 1,
SLOT_IMAG_INPUT = 2,
SLOT_ENERGY_INPUT = 3,
SLOT_IMAG_OUTPUT = 4;
private final int[] slotsTop = {SLOT_INPUT, SLOT_IMAG_INPUT};
private final int[] slotsBottom = {SLOT_OUTPUT, SLOT_IMAG_OUTPUT, SLOT_ENERGY_INPUT};
private final int[] slotsOther = {SLOT_ENERGY_INPUT};
protected FluidTank tank = new FluidTank(TANK_SIZE);
private IFRecipe currentRecipe;
private double workProgress;
private int checkCooldown = 10, syncCooldown = SYNC_INTV;
public TileImagFusor() {
super("imag_fusor", 5, 2000, IFConstants.LATENCY_MK1);
}
@Override
public int fill(ForgeDirection from, FluidStack resource, boolean doFill) {
if(resource.getFluid() != ModuleCrafting.fluidImagProj) {
return 0;
}
return tank.fill(resource, doFill);
}
public int getTankSize() {
return tank.getCapacity();
}
public int getLiquidAmount() {
return tank.getFluidAmount();
}
/**
* As has discussed, this should perform a fake drain-energy effect when crafting, and restore to the real energy when not.
* @return The energy for client-side display purpose.
*/
public double getEnergyForDisplay() {
return getEnergy();
}
@Override
public FluidStack drain(ForgeDirection from, FluidStack resource,
boolean doDrain) {
return tank.drain(resource.amount, doDrain);
}
@Override
public FluidStack drain(ForgeDirection from, int maxDrain, boolean doDrain) {
return tank.drain(maxDrain, doDrain);
}
@Override
public boolean canFill(ForgeDirection from, Fluid fluid) {
return fluid == ModuleCrafting.fluidImagProj;
}
@Override
public boolean canDrain(ForgeDirection from, Fluid fluid) {
return fluid == ModuleCrafting.fluidImagProj;
}
@Override
public void updateEntity() {
super.updateEntity();
if (!isWorking()) {
// Match the work in server
if(!worldObj.isRemote) {
if(--checkCooldown <= 0) {
checkCooldown = 10;
if(inventory[SLOT_INPUT] != null) {
IFRecipe recipe = ImagFusorRecipes.INSTANCE.getRecipe(inventory[SLOT_INPUT]);
if(recipe != null) {
startWorking(recipe);
}
}
}
}
}
if(isWorking()) {
updateWork();
}
// Update liquid
{
ItemStack imagOutStack = inventory[SLOT_IMAG_OUTPUT];
if(inventory[SLOT_IMAG_INPUT] != null &&
(imagOutStack == null || imagOutStack.stackSize < imagOutStack.getMaxStackSize()) &&
getLiquidAmount() + PER_UNIT <= TANK_SIZE) {
this.tank.fill(new FluidStack(ModuleCrafting.fluidImagProj, PER_UNIT), true);
inventory[SLOT_IMAG_INPUT].stackSize--;
if(inventory[SLOT_IMAG_INPUT].stackSize == 0)
inventory[SLOT_IMAG_INPUT] = null;
if (imagOutStack == null) {
inventory[SLOT_IMAG_OUTPUT] = ModuleCrafting.matterUnit.create(ItemMatterUnit.NONE);
} else {
++imagOutStack.stackSize;
}
}
}
// Update energy
if(inventory[SLOT_ENERGY_INPUT] != null) {
double gain = EnergyItemHelper
.pull(inventory[SLOT_ENERGY_INPUT], Math.min(getMaxEnergy() - getEnergy(), getBandwidth()), false);
this.injectEnergy(gain);
}
// Synchronization
if (!worldObj.isRemote) {
if(--syncCooldown <= 0) {
syncCooldown = SYNC_INTV;
NetworkMessage.sendToAllAround(TargetPoints.convert(this, 15),
this, "sync",
currentRecipe, workProgress, getLiquidAmount());
}
}
if (worldObj.isRemote) {
updateSounds();
}
}
//---Work API
/**
* @return The working progress, or 0.0 if isn't crafting
*/
public double getWorkProgress() {
return isWorking() ? workProgress : 0.0;
}
private void startWorking(IFRecipe recipe) {
currentRecipe = recipe;
workProgress = 0.0;
}
private void updateWork() {
// Check the input stack, and abort if item isnt there
// Also check whether the amount of Liquid is enough,
// and whether the output of currentRecipe can be outputed into outputslot
// Added by Shielian
if(inventory[0] == null || currentRecipe.consumeType.getItem() != inventory[0].getItem()
|| this.pullEnergy(CONSUME_PER_TICK) != CONSUME_PER_TICK || this.getLiquidAmount() < currentRecipe.consumeLiquid
|| (inventory[SLOT_OUTPUT] != null && inventory[SLOT_OUTPUT].getItem() != currentRecipe.output.getItem())
) {
abortWorking();
return;
}
if(!isActionBlocked()) {
workProgress += WORK_SPEED;
if(workProgress >= 1.0) {
endWorking();
}
}
}
private void endWorking() {
if(isWorking()) {
int drained = tank.drain(currentRecipe.consumeLiquid, true).amount;
if(!worldObj.isRemote) {
inventory[0].stackSize -= currentRecipe.consumeType.stackSize;
if(inventory[0].stackSize <= 0)
inventory[0] = null;
if(inventory[1] != null) {
inventory[1].stackSize += currentRecipe.output.stackSize;
} else {
inventory[1] = currentRecipe.output.copy();
}
}
}
workProgress = 0.0;
currentRecipe = null;
checkCooldown = 0; // Avoid work pausing
}
private void abortWorking() {
workProgress = 0.0;
currentRecipe = null ;
}
public boolean isWorking() {
return currentRecipe != null;
}
public boolean isActionBlocked() {
return !isWorking() ||
(inventory[0].stackSize < currentRecipe.consumeType.stackSize) ||
(inventory[1] != null && (!StackUtils.isStackDataEqual(inventory[1], currentRecipe.output) ||
inventory[1].stackSize + currentRecipe.output.stackSize > inventory[1].getMaxStackSize())) ||
currentRecipe.consumeLiquid > this.getLiquidAmount();
}
public IFRecipe getCurrentRecipe() {
return currentRecipe;
}
//---
@Override
public FluidTankInfo[] getTankInfo(ForgeDirection from) {
return new FluidTankInfo[] { tank.getInfo() };
}
@Override
public void readFromNBT(NBTTagCompound tag) {
super.readFromNBT(tag);
tank.readFromNBT(tag);
}
@Override
public void writeToNBT(NBTTagCompound tag) {
super.writeToNBT(tag);
tank.writeToNBT(tag);
}
@Listener(channel="sync", side=Side.CLIENT)
private void hSync(IFRecipe recipe, double progress, int fluidAmount) {
tank.setFluid(new FluidStack(ModuleCrafting.fluidImagProj, fluidAmount));
workProgress = progress;
currentRecipe = recipe;
}
// --- CLIENT EFFECTS
@SideOnly(Side.CLIENT)
private PositionedSound sound;
@SideOnly(Side.CLIENT)
private void updateSounds() {
boolean play = !isActionBlocked();
if(sound != null && !play) {
sound.stop();
sound = null;
} else if(sound == null && play) {
sound = new TileEntitySound(this, "machine.imag_fusor_work").setLoop().setVolume(0.6f);
ACSounds.playClient(sound);
}
}
@Override
public int[] getAccessibleSlotsFromSide(int side) {
switch(side) {
case 0:
return slotsBottom;
case 1:
return slotsTop;
default:
return slotsOther;
}
}
@Override
public boolean canInsertItem(int slot, ItemStack item, int side) {
return this.isItemValidForSlot(slot, item);
}
@Override
public boolean canExtractItem(int slot, ItemStack item, int side) {
return side == 0;
}
}