package pneumaticCraft.common.tileentity;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.EnumChatFormatting;
import net.minecraft.world.World;
import net.minecraftforge.common.util.ForgeDirection;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.MutablePair;
import org.apache.commons.lang3.tuple.Pair;
import pneumaticCraft.api.tileentity.IAirHandler;
import pneumaticCraft.api.tileentity.IManoMeasurable;
import pneumaticCraft.api.tileentity.IPneumaticMachine;
import pneumaticCraft.api.tileentity.ISidedPneumaticMachine;
import pneumaticCraft.common.block.tubes.IPneumaticPosProvider;
import pneumaticCraft.common.item.ItemMachineUpgrade;
import pneumaticCraft.common.network.GuiSynced;
import pneumaticCraft.common.network.NetworkHandler;
import pneumaticCraft.common.network.PacketPlaySound;
import pneumaticCraft.common.network.PacketSpawnParticle;
import pneumaticCraft.common.thirdparty.ModInteractionUtils;
import pneumaticCraft.common.thirdparty.computercraft.LuaConstant;
import pneumaticCraft.common.thirdparty.computercraft.LuaMethod;
import pneumaticCraft.common.util.PneumaticCraftUtils;
import pneumaticCraft.lib.Log;
import pneumaticCraft.lib.PneumaticValues;
import pneumaticCraft.lib.Sounds;
public class TileEntityPneumaticBase extends TileEntityBase implements IManoMeasurable, IAirHandler,
IPneumaticPosProvider{
public float maxPressure;
@GuiSynced
public int volume;
public final int DEFAULT_VOLUME;
public final float DANGER_PRESSURE;
public final float CRITICAL_PRESSURE;
@GuiSynced
public int currentAir;
public int soundCounter;
public TileEntity parentTile;
protected final Set<IAirHandler> specialConnectedHandlers = new HashSet<IAirHandler>();
public TileEntityPneumaticBase(float dangerPressure, float criticalPressure, int volume){
if(volume <= 0) throw new IllegalArgumentException("Volume can't be lower than or equal to 0!");
DANGER_PRESSURE = dangerPressure;
CRITICAL_PRESSURE = criticalPressure;
maxPressure = DANGER_PRESSURE + (CRITICAL_PRESSURE - DANGER_PRESSURE) * (float)Math.random();
this.volume = volume;
DEFAULT_VOLUME = volume;
currentAir = 0;
addLuaMethods();
}
@Override
public void updateEntity(){
// volume calculations
if(!worldObj.isRemote && getUpgradeSlots() != null) {
int upgradeVolume = getVolumeFromUpgrades(getUpgradeSlots());
setVolume(DEFAULT_VOLUME + upgradeVolume);
if(getUpgrades(ItemMachineUpgrade.UPGRADE_SECURITY, getUpgradeSlots()) > 0) {
if(getPressure(ForgeDirection.UNKNOWN) >= DANGER_PRESSURE - 0.1) {
airLeak(ForgeDirection.DOWN);
}
//Remove the remaining air if there is any still.
int excessAir = getCurrentAir(ForgeDirection.UNKNOWN) - (int)(getVolume() * (DANGER_PRESSURE - 0.1));
if(excessAir > 0) {
addAir(-excessAir, ForgeDirection.DOWN);
onAirDispersion(-excessAir, ForgeDirection.DOWN);
}
}
}
super.updateEntity();
// if(!worldObj.isRemote ) System.out.println("currentPressure: " +
// getPressure());
for(ForgeDirection pneumaticSide : ForgeDirection.values()) {
if(!worldObj.isRemote && getPressure(pneumaticSide) > maxPressure) {
worldObj.createExplosion(null, xCoord + 0.5D, yCoord + 0.5D, zCoord + 0.5D, 1.0F, true);
worldObj.setBlockToAir(xCoord, yCoord, zCoord);
}
}
if(!worldObj.isRemote) disperseAir();
if(soundCounter > 0) soundCounter--;
}
/**
* Method invoked every update tick which is used to handle air dispersion. It retrieves the pneumatics connecting
* with this TE, and sends air to it when it has a lower pressure than this TE.
*/
protected void disperseAir(){
if(worldObj.isRemote) return;
disperseAir(getConnectedPneumatics());
}
private void disperseAir(List<Pair<ForgeDirection, IAirHandler>> teList){
boolean shouldRepeat = false;
List<Pair<Integer, Integer>> dispersion = new ArrayList<Pair<Integer, Integer>>();
do {
shouldRepeat = false;
//Add up every volume and air.
int totalVolume = getVolume();
int totalAir = currentAir;
for(Pair<ForgeDirection, IAirHandler> entry : teList) {
IAirHandler airHandler = entry.getValue();
totalVolume += airHandler.getVolume();
totalAir += airHandler.getCurrentAir(entry.getKey().getOpposite());
}
//Only go push based, ignore any machines that have a higher pressure than this block.
Iterator<Pair<ForgeDirection, IAirHandler>> iterator = teList.iterator();
while(iterator.hasNext()) {
Pair<ForgeDirection, IAirHandler> entry = iterator.next();
IAirHandler airHandler = entry.getValue();
int totalMachineAir = (int)((long)totalAir * airHandler.getVolume() / totalVolume);//Calculate the total air the machine is going to get.
int airDispersed = totalMachineAir - airHandler.getCurrentAir(entry.getKey().getOpposite());
if(airDispersed < 0) {
iterator.remove();
shouldRepeat = true;
dispersion.clear();
break;
} else {
dispersion.add(new MutablePair(getMaxDispersion(entry.getKey()), airDispersed));
}
}
} while(shouldRepeat);
int toBeDivided = 0;
int receivers = dispersion.size();
for(Pair<Integer, Integer> disp : dispersion) {
if(disp.getValue() > disp.getKey()) {
toBeDivided += disp.getValue() - disp.getKey();//Any air that wants to go to a neighbor, but can't (because of regulator module) gives back its air.
disp.setValue(disp.getKey());
receivers--;
}
}
while(toBeDivided >= receivers && receivers > 0) {
int dividedValue = toBeDivided / receivers; //try to give every receiver an equal part of the to be divided air.
for(Pair<Integer, Integer> disp : dispersion) {
int maxTransfer = disp.getKey() - disp.getValue();
if(maxTransfer > 0) {
if(maxTransfer <= dividedValue) {
receivers--;//next step this receiver won't be able to receive any air.
}
int transfered = Math.min(dividedValue, maxTransfer);//cap it at the max it can have.
disp.setValue(disp.getValue() + transfered);
toBeDivided -= transfered;
} else {
receivers--;
}
}
}
for(int i = 0; i < teList.size(); i++) {
IAirHandler neighbor = teList.get(i).getValue();
int transferedAir = dispersion.get(i).getValue();
onAirDispersion(transferedAir, teList.get(i).getKey());
neighbor.addAir(transferedAir, teList.get(i).getKey().getOpposite());
addAir(-transferedAir, teList.get(i).getKey());
}
}
/**
* Method fired to get the maximum air allowed to disperse to this side. Used in the regulator tube to prevent air from travelling.
* @param key
* @return
*/
protected int getMaxDispersion(ForgeDirection side){
return Integer.MAX_VALUE;
}
/**
* Method fired when air disperses. Overriden in the Flow Detection Tube to calculate the air passed through.
* return amount that is allowed to be dispersed.
* @param amount of air being dispersed.
* @param side
* @return
*/
protected void onAirDispersion(int amount, ForgeDirection side){}
/**
* Method to release air in the air. It takes air from a specific side, plays a sound effect, and spawns smoke particles.
* @param side
*/
@Override
public void airLeak(ForgeDirection side){
if(worldObj.isRemote || Math.abs(getPressure(side)) < 0.01F) return;
double motionX = side.offsetX;
double motionY = side.offsetY;
double motionZ = side.offsetZ;
if(soundCounter <= 0) {
soundCounter = 20;
NetworkHandler.sendToAllAround(new PacketPlaySound(Sounds.LEAKING_GAS_SOUND, xCoord, yCoord, zCoord, 0.1F, 1.0F, true), worldObj);
}
if(getPressure(side) < 0) {
double speed = getPressure(side) * 0.1F - 0.1F;
NetworkHandler.sendToAllAround(new PacketSpawnParticle("smoke", xCoord + 0.5D + motionX / 2D, yCoord + 0.5D + motionY / 2D, zCoord + 0.5D + motionZ / 2D, motionX * speed, motionY * speed, motionZ * speed), worldObj);
int dispersedAmount = -(int)(getPressure(side) * PneumaticValues.AIR_LEAK_FACTOR) + 20;
if(getCurrentAir(side) > dispersedAmount) dispersedAmount = -getCurrentAir(side);
onAirDispersion(dispersedAmount, side);
addAir(dispersedAmount, side);
} else {
double speed = getPressure(side) * 0.1F + 0.1F;
// if(DateEventHandler.isEvent()) {
//DateEventHandler.spawnFirework(worldObj, xCoord + 0.5D + motionX / 2D, yCoord + 0.5D + motionY / 2D, zCoord + 0.5D + motionZ / 2D);
// } else {
NetworkHandler.sendToAllAround(new PacketSpawnParticle("smoke", xCoord + 0.5D + motionX / 2D, yCoord + 0.5D + motionY / 2D, zCoord + 0.5D + motionZ / 2D, motionX * speed, motionY * speed, motionZ * speed), worldObj);
// }
int dispersedAmount = (int)(getPressure(side) * PneumaticValues.AIR_LEAK_FACTOR) + 20;
if(dispersedAmount > getCurrentAir(side)) dispersedAmount = getCurrentAir(side);
onAirDispersion(-dispersedAmount, side);
addAir(-dispersedAmount, side);
}
}
/**
* Retrieves a list of all the connecting pneumatics. It takes sides in account.
* @return
*/
@Override
public List<Pair<ForgeDirection, IAirHandler>> getConnectedPneumatics(){
List<Pair<ForgeDirection, IAirHandler>> teList = new ArrayList<Pair<ForgeDirection, IAirHandler>>();
for(IAirHandler specialConnection : specialConnectedHandlers) {
teList.add(new ImmutablePair(ForgeDirection.UNKNOWN, specialConnection));
}
for(ForgeDirection direction : ForgeDirection.VALID_DIRECTIONS) {
TileEntity te = getTileCache()[direction.ordinal()].getTileEntity();
IPneumaticMachine machine = ModInteractionUtils.getInstance().getMachine(te);
if(machine != null && isConnectedTo(direction) && machine.isConnectedTo(direction.getOpposite())) {
teList.add(new ImmutablePair(direction, machine.getAirHandler()));
} else if(te instanceof ISidedPneumaticMachine) {
IAirHandler handler = ((ISidedPneumaticMachine)te).getAirHandler(direction.getOpposite());
if(handler != null) {
teList.add(new ImmutablePair(direction, handler));
}
}
}
return teList;
}
/**
* Returns if TE's is connected to the given side of this TE.
* @param side
* @return
*/
@Override
public boolean isConnectedTo(ForgeDirection side){
if(parentTile == null) {
return true;
} else if(parentTile instanceof IPneumaticMachine) {
return ((IPneumaticMachine)parentTile).isConnectedTo(side);
} else {
return ((ISidedPneumaticMachine)parentTile).getAirHandler(side) == this;
}
}
/**
* Adds air to the tank of the given side of this TE. It also updates clients where needed.
* @param amount
* @param side
*/
@Override
@Deprecated
public void addAir(float amount, ForgeDirection side){
addAir((int)amount, side);
}
@Override
public void addAir(int amount, ForgeDirection side){
currentAir += amount;
}
@Override
@Deprecated
public void setVolume(float newVolume){
setVolume((int)newVolume);
}
/**
* Sets the volume of this TE's air tank. When the volume decreases the pressure will remain the same, meaning air will
* be lost. When the volume increases, the air remains the same meaning the pressure will drop.
* Used in the Volume Upgrade calculations.
* @param newVolume
*/
@Override
public void setVolume(int newVolume){
if(newVolume <= 0) throw new IllegalArgumentException("Volume can't be lower or equal than 0!");
if(newVolume < volume) currentAir = (int)(currentAir * (float)newVolume / volume); // lose air when we decrease in volume.
volume = newVolume;
}
@Override
public float getPressure(ForgeDirection sideRequested){
return (float)currentAir / volume;
}
@Override
public void readFromNBT(NBTTagCompound nbt){
if(getClass() != TileEntityPneumaticBase.class && saveTeInternals()) {
super.readFromNBT(nbt);
}
if(nbt.hasKey("pneumatic")) nbt = nbt.getCompoundTag("pneumatic");
currentAir = nbt.getInteger("currentAir");
maxPressure = nbt.getFloat("maxPressure");
volume = nbt.getInteger("volume");
if(volume == 0) {
Log.error("Volume was 0! Assigning default");
volume = DEFAULT_VOLUME;
}
}
@Override
public void writeToNBT(NBTTagCompound nbt){
if(getClass() != TileEntityPneumaticBase.class && saveTeInternals()) {
super.writeToNBT(nbt);
}
nbt.setInteger("currentAir", currentAir);
nbt.setInteger("volume", volume);
nbt.setFloat("maxPressure", maxPressure);
NBTTagCompound tag = new NBTTagCompound();
nbt.setTag("pneumatic", tag);
nbt = tag;
nbt.setInteger("currentAir", currentAir);
nbt.setInteger("volume", volume);
nbt.setFloat("maxPressure", maxPressure);
}
protected boolean saveTeInternals(){
return true;
}
protected int getVolumeFromUpgrades(int[] upgradeSlots){
return getUpgrades(ItemMachineUpgrade.UPGRADE_VOLUME_DAMAGE, upgradeSlots) * PneumaticValues.VOLUME_VOLUME_UPGRADE;
}
@Override
public void printManometerMessage(EntityPlayer player, List<String> curInfo){
curInfo.add(EnumChatFormatting.GREEN + "Current pressure: " + PneumaticCraftUtils.roundNumberTo(getPressure(ForgeDirection.UNKNOWN), 1) + " bar.");
}
@Override
public int getVolume(){
return volume;
}
@Override
public float getMaxPressure(){
return maxPressure;
}
@Override
public int getCurrentAir(ForgeDirection sideRequested){
return currentAir;
}
@Override
public IAirHandler getAirHandler(){
return this;
}
@Override
public int getXCoord(){
return xCoord;
}
@Override
public int getYCoord(){
return yCoord;
}
@Override
public int getZCoord(){
return zCoord;
}
@Override
protected void addLuaMethods(){
super.addLuaMethods();
luaMethods.add(new LuaMethod("getPressure"){
@Override
public Object[] call(Object[] args) throws Exception{
if(args.length == 0) {
return new Object[]{getPressure(ForgeDirection.UNKNOWN)};
} else if(args.length == 1) {
return new Object[]{getPressure(getDirForString((String)args[0]))};
} else {
throw new IllegalArgumentException("getPressure method requires 0 or 1 argument (direction: up, down, east, west, north, south!");
}
}
});
luaMethods.add(new LuaConstant("getDangerPressure", DANGER_PRESSURE));
luaMethods.add(new LuaConstant("getCriticalPressure", CRITICAL_PRESSURE));
luaMethods.add(new LuaConstant("getDefaultVolume", DEFAULT_VOLUME));
}
/*
* End ComputerCraft API
*/
@Override
public void updateEntityI(){
updateEntity();
}
@Override
public void readFromNBTI(NBTTagCompound nbt){
readFromNBT(nbt);
}
@Override
public void writeToNBTI(NBTTagCompound nbt){
writeToNBT(nbt);
}
@Override
public void validateI(TileEntity parent){
parentTile = parent;
worldObj = parent.getWorldObj();
xCoord = parent.xCoord;
yCoord = parent.yCoord;
zCoord = parent.zCoord;
}
@Override
public void onNeighborChange(){
onNeighborTileUpdate();
}
@Override
public World world(){
return worldObj;
}
@Override
public int x(){
return xCoord;
}
@Override
public int y(){
return yCoord;
}
@Override
public int z(){
return zCoord;
}
@Override
public void createConnection(IAirHandler otherHandler){
if(otherHandler == null) throw new NullPointerException("Can't connect with a null air handler!");
if(specialConnectedHandlers.add(otherHandler)) {
otherHandler.createConnection(this);
}
}
@Override
public void removeConnection(IAirHandler otherHandler){
if(otherHandler == null) throw new NullPointerException("Can't disconnect a null air handler!");
if(specialConnectedHandlers.remove(otherHandler)) {
otherHandler.removeConnection(this);
}
}
}