package pneumaticCraft.common.tileentity;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.inventory.IInventory;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.AxisAlignedBB;
import net.minecraft.world.ChunkPosition;
import net.minecraftforge.common.util.ForgeDirection;
import net.minecraftforge.event.entity.player.PlayerInteractEvent;
import pneumaticCraft.api.tileentity.IPneumaticMachine;
import pneumaticCraft.api.universalSensor.IEventSensorSetting;
import pneumaticCraft.api.universalSensor.IPollSensorSetting;
import pneumaticCraft.api.universalSensor.ISensorSetting;
import pneumaticCraft.client.gui.GuiUniversalSensor;
import pneumaticCraft.client.render.RenderRangeLines;
import pneumaticCraft.common.block.Blockss;
import pneumaticCraft.common.item.ItemGPSTool;
import pneumaticCraft.common.item.ItemMachineUpgrade;
import pneumaticCraft.common.item.Itemss;
import pneumaticCraft.common.network.DescSynced;
import pneumaticCraft.common.network.GuiSynced;
import pneumaticCraft.common.network.NetworkHandler;
import pneumaticCraft.common.network.PacketRenderRangeLines;
import pneumaticCraft.common.sensor.SensorHandler;
import pneumaticCraft.common.thirdparty.ThirdPartyManager;
import pneumaticCraft.common.thirdparty.computercraft.LuaConstant;
import pneumaticCraft.common.thirdparty.computercraft.LuaMethod;
import pneumaticCraft.lib.ModIds;
import pneumaticCraft.lib.PneumaticValues;
import pneumaticCraft.lib.TileEntityConstants;
import cpw.mods.fml.common.Optional;
import cpw.mods.fml.common.eventhandler.Event;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import dan200.computercraft.api.peripheral.IComputerAccess;
public class TileEntityUniversalSensor extends TileEntityPneumaticBase implements IInventory, IRangeLineShower,
IGUITextFieldSensitive, IMinWorkingPressure, IRedstoneControl{
@DescSynced
public boolean[] sidesConnected = new boolean[6];
private ItemStack[] inventory = new ItemStack[5];
public static final int UPGRADE_SLOT_1 = 0;
public static final int UPGRADE_SLOT_4 = 3;
public static final int INVENTORY_SIZE = 5;
@GuiSynced
private String sensorSetting = "";
private int ticksExisted;
public int redstoneStrength;
private int eventTimer;
public float dishRotation;
public float oldDishRotation;
public float dishSpeed;
@GuiSynced
public boolean invertedRedstone;
@DescSynced
public boolean isSensorActive;
@GuiSynced
private String sensorGuiText = ""; //optional parameter text for sensors.
private boolean requestPollPullEvent;
private Set<ChunkPosition> positions;
private int oldSensorRange; //range used by the range line renderer, to figure out if the range has been changed.
private final RenderRangeLines rangeLineRenderer = new RenderRangeLines(0x330000FF);
private final List<IComputerAccess> attachedComputers = new ArrayList<IComputerAccess>(); //keep track of the computers so we can raise a os.pullevent.
public TileEntityUniversalSensor(){
super(PneumaticValues.DANGER_PRESSURE_UNIVERSAL_SENSOR, PneumaticValues.MAX_PRESSURE_UNIVERSAL_SENSOR, PneumaticValues.VOLUME_UNIVERSAL_SENSOR);
setUpgradeSlots(new int[]{UPGRADE_SLOT_1, 1, 2, UPGRADE_SLOT_4});
}
@Override
public void updateEntity(){
oldDishRotation = dishRotation;
if(isSensorActive) {
dishSpeed = Math.min(dishSpeed + 0.2F, 10);
} else {
dishSpeed = Math.max(dishSpeed - 0.2F, 0);
}
dishRotation += dishSpeed;
if(worldObj.isRemote) {
int sensorRange = getRange();
if(oldSensorRange != sensorRange || oldSensorRange == 0) {
oldSensorRange = sensorRange;
if(!firstRun) rangeLineRenderer.resetRendering(sensorRange);
}
rangeLineRenderer.update();
}
super.updateEntity();
if(!worldObj.isRemote) {
ticksExisted++;
ISensorSetting sensor = SensorHandler.instance().getSensorFromPath(sensorSetting);
if(sensor != null && getPressure(ForgeDirection.UNKNOWN) > PneumaticValues.MIN_PRESSURE_UNIVERSAL_SENSOR) {
isSensorActive = true;
addAir(-PneumaticValues.USAGE_UNIVERSAL_SENSOR, ForgeDirection.UNKNOWN);
if(sensor instanceof IPollSensorSetting) {
if(ticksExisted % ((IPollSensorSetting)sensor).getPollFrequency(this) == 0) {
int newRedstoneStrength = ((IPollSensorSetting)sensor).getRedstoneValue(worldObj, xCoord, yCoord, zCoord, getRange(), sensorGuiText);
if(invertedRedstone) newRedstoneStrength = 15 - newRedstoneStrength;
if(newRedstoneStrength != redstoneStrength) {
redstoneStrength = newRedstoneStrength;
if(requestPollPullEvent) {
notifyComputers(redstoneStrength);
}
updateNeighbours();
}
}
eventTimer = 0;
} else {
if(eventTimer > 0) {
eventTimer--;
if(eventTimer == 0 && redstoneStrength != (invertedRedstone ? 15 : 0)) {
redstoneStrength = invertedRedstone ? 15 : 0;
updateNeighbours();
}
}
}
} else {
isSensorActive = false;
if(redstoneStrength != (invertedRedstone ? 15 : 0)) {
redstoneStrength = invertedRedstone ? 15 : 0;
updateNeighbours();
}
}
}
}
/**
* Will initiate the wireframe rendering. When invoked on the server, it sends a packet to every client to render the box.
*/
@Override
public void showRangeLines(){
if(worldObj.isRemote) {
rangeLineRenderer.resetRendering(getRange());
} else {
NetworkHandler.sendToAllAround(new PacketRenderRangeLines(this), worldObj, TileEntityConstants.PACKET_UPDATE_DISTANCE + getRange());
}
}
@SideOnly(Side.CLIENT)
public void renderRangeLines(){
rangeLineRenderer.render();
}
@Override
@SideOnly(Side.CLIENT)
public AxisAlignedBB getRenderBoundingBox(){
if(!rangeLineRenderer.isCurrentlyRendering()) return super.getRenderBoundingBox();
int range = getRange();
return AxisAlignedBB.getBoundingBox(xCoord - range, yCoord - range, zCoord - range, xCoord + 1 + range, yCoord + 1 + range, zCoord + 1 + range);
}
public void onEvent(Event event){
ISensorSetting sensor = SensorHandler.instance().getSensorFromPath(sensorSetting);
if(sensor != null && sensor instanceof IEventSensorSetting && getPressure(ForgeDirection.UNKNOWN) > PneumaticValues.MIN_PRESSURE_UNIVERSAL_SENSOR) {
int newRedstoneStrength = ((IEventSensorSetting)sensor).emitRedstoneOnEvent(event, this, getRange(), sensorGuiText);
if(newRedstoneStrength != 0) eventTimer = ((IEventSensorSetting)sensor).getRedstonePulseLength();
if(invertedRedstone) newRedstoneStrength = 15 - newRedstoneStrength;
if(eventTimer > 0 && ThirdPartyManager.computerCraftLoaded) {
if(event instanceof PlayerInteractEvent) {
PlayerInteractEvent e = (PlayerInteractEvent)event;
notifyComputers(newRedstoneStrength, e.x, e.y, e.z);
} else {
notifyComputers(newRedstoneStrength);
}
}
if(newRedstoneStrength != redstoneStrength) {
redstoneStrength = newRedstoneStrength;
updateNeighbours();
}
}
}
public int getRange(){
return getUpgrades(ItemMachineUpgrade.UPGRADE_RANGE, getUpgradeSlots()) + 2;
}
public void setSensorSetting(String sensorPath){
sensorSetting = sensorPath;
if(worldObj != null && worldObj.isRemote) {
GuiScreen guiScreen = Minecraft.getMinecraft().currentScreen;
if(guiScreen instanceof GuiUniversalSensor) {
((GuiUniversalSensor)guiScreen).updateButtons();
}
}
}
private boolean setSensorSetting(ISensorSetting sensor){
if(areGivenUpgradesInserted(SensorHandler.instance().getRequiredStacksFromText(sensor.getSensorPath()))) {
setSensorSetting(sensor.getSensorPath());
return true;
} else {
return false;
}
}
public String getSensorSetting(){
return sensorSetting;
}
@Override
public void onGuiUpdate(){
setSensorSetting(sensorSetting);
}
@Override
public void writeToNBT(NBTTagCompound tag){
super.writeToNBT(tag);
tag.setString("sensorSetting", sensorSetting);
tag.setBoolean("invertedRedstone", invertedRedstone);
tag.setFloat("dishSpeed", dishSpeed);
tag.setString("sensorText", sensorGuiText);
// Write the ItemStacks in the inventory to NBT
NBTTagList tagList = new NBTTagList();
for(int currentIndex = 0; currentIndex < inventory.length; ++currentIndex) {
if(inventory[currentIndex] != null) {
NBTTagCompound tagCompound = new NBTTagCompound();
tagCompound.setByte("Slot", (byte)currentIndex);
inventory[currentIndex].writeToNBT(tagCompound);
tagList.appendTag(tagCompound);
}
}
tag.setTag("Items", tagList);
}
@Override
public void readFromNBT(NBTTagCompound tag){
super.readFromNBT(tag);
setSensorSetting(tag.getString("sensorSetting"));
invertedRedstone = tag.getBoolean("invertedRedstone");
dishSpeed = tag.getFloat("dishSpeed");
sensorGuiText = tag.getString("sensorText");
// Read in the ItemStacks in the inventory from NBT
NBTTagList tagList = tag.getTagList("Items", 10);
inventory = new ItemStack[getSizeInventory()];
for(int i = 0; i < tagList.tagCount(); ++i) {
NBTTagCompound tagCompound = tagList.getCompoundTagAt(i);
byte slot = tagCompound.getByte("Slot");
if(slot >= 0 && slot < inventory.length) {
inventory[slot] = ItemStack.loadItemStackFromNBT(tagCompound);
}
}
positions = getGPSPositionsStatic(this, getRange());
}
@Override
public void handleGUIButtonPress(int buttonID, EntityPlayer player){
if(buttonID >= 10 && buttonID % 10 == 0) {
String[] directories = SensorHandler.instance().getDirectoriesAtLocation(getSensorSetting());
if(buttonID / 10 <= directories.length) {// <= because of the redstone button being 0.
if(getSensorSetting().equals("")) {
setSensorSetting(directories[buttonID / 10 - 1]);
} else {
setSensorSetting(getSensorSetting() + "/" + directories[buttonID / 10 - 1]);
}
}
} else if(buttonID == 1) {//the 'back' button
String[] folders = getSensorSetting().split("/");
String newPath = getSensorSetting().replace(folders[folders.length - 1], "");
if(newPath.endsWith("/")) {
newPath = newPath.substring(0, newPath.length() - 1);
}
setSensorSetting(newPath);
} else if(buttonID == 0) {
invertedRedstone = !invertedRedstone;
redstoneStrength = 15 - redstoneStrength;
updateNeighbours();
}
}
public boolean areGivenUpgradesInserted(ItemStack[] requiredStacks){
for(ItemStack requiredStack : requiredStacks) {
if(requiredStack.getItem() != Itemss.GPSTool) {
if(getUpgrades(requiredStack.getItemDamage(), getUpgradeSlots()) <= 0) {
return false;
}
} else {
if(getUpgrades(-1, getUpgradeSlots()) <= 0) {
return false;
}
}
}
return true;
}
@Override
public void onNeighborTileUpdate(){
super.onNeighborTileUpdate();
for(ForgeDirection direction : ForgeDirection.VALID_DIRECTIONS) {
TileEntity te = worldObj.getTileEntity(xCoord + direction.offsetX, yCoord + direction.offsetY, zCoord + direction.offsetZ);
if(te instanceof IPneumaticMachine) {
sidesConnected[direction.ordinal()] = ((IPneumaticMachine)te).isConnectedTo(direction.getOpposite());
} else {
sidesConnected[direction.ordinal()] = false;
}
}
}
// INVENTORY METHODS- && NBT
// ------------------------------------------------------------
/**
* We need to override this, as we need to add the GPS Tool to the mapping.
*/
@Override
protected int getUpgrades(int upgradeDamage, int... upgradeSlots){
int upgrades = 0;
if(this instanceof IInventory) {// this always should be true.
IInventory inv = this;
for(int i : upgradeSlots) {
if(inv.getStackInSlot(i) != null) {
if(inv.getStackInSlot(i).getItem() == Itemss.machineUpgrade && inv.getStackInSlot(i).getItemDamage() == upgradeDamage) {
upgrades += inv.getStackInSlot(i).stackSize;
} else if(upgradeDamage == -1 && inv.getStackInSlot(i).getItem() == Itemss.GPSTool) {
upgrades += inv.getStackInSlot(i).stackSize;
}
}
}
}
return upgrades;
}
/**
* Returns the number of slots in the inventory.
*/
@Override
public int getSizeInventory(){
return inventory.length;
}
/**
* Returns the stack in slot i
*/
@Override
public ItemStack getStackInSlot(int slot){
return inventory[slot];
}
@Override
public ItemStack decrStackSize(int slot, int amount){
ItemStack itemStack = getStackInSlot(slot);
if(itemStack != null) {
if(itemStack.stackSize <= amount) {
setInventorySlotContents(slot, null);
} else {
itemStack = itemStack.splitStack(amount);
if(itemStack.stackSize == 0) {
setInventorySlotContents(slot, null);
}
}
}
return itemStack;
}
@Override
public ItemStack getStackInSlotOnClosing(int slot){
ItemStack itemStack = getStackInSlot(slot);
if(itemStack != null) {
setInventorySlotContents(slot, null);
}
return itemStack;
}
@Override
public void setInventorySlotContents(int slot, ItemStack itemStack){
// super.setInventorySlotContents(slot, itemStack);
inventory[slot] = itemStack;
if(itemStack != null && itemStack.stackSize > getInventoryStackLimit()) {
itemStack.stackSize = getInventoryStackLimit();
}
if(!worldObj.isRemote && !getSensorSetting().equals("") && !areGivenUpgradesInserted(SensorHandler.instance().getRequiredStacksFromText(getSensorSetting()))) {
setSensorSetting("");
}
positions = getGPSPositionsStatic(this, getRange());
}
public Set<ChunkPosition> getGPSPositions(){
return positions;
}
private static Set<ChunkPosition> getGPSPositionsStatic(TileEntityUniversalSensor teUs, int sensorRange){
List<ChunkPosition> gpsPositions = new ArrayList<ChunkPosition>();
for(int i = TileEntityUniversalSensor.UPGRADE_SLOT_1; i <= TileEntityUniversalSensor.UPGRADE_SLOT_4; i++) {
ItemStack gps = teUs.getStackInSlot(i);
if(gps != null && gps.getItem() == Itemss.GPSTool) {
ChunkPosition pos = ItemGPSTool.getGPSLocation(gps);
if(pos != null && Math.abs(pos.chunkPosX - teUs.xCoord) <= sensorRange && Math.abs(pos.chunkPosY - teUs.yCoord) <= sensorRange && Math.abs(pos.chunkPosZ - teUs.zCoord) <= sensorRange) {
gpsPositions.add(pos);
}
}
}
if(gpsPositions.size() == 1) {
return new HashSet(gpsPositions);
} else if(gpsPositions.size() > 1) {
int minX = Math.min(gpsPositions.get(0).chunkPosX, gpsPositions.get(1).chunkPosX);
int minY = Math.min(gpsPositions.get(0).chunkPosY, gpsPositions.get(1).chunkPosY);
int minZ = Math.min(gpsPositions.get(0).chunkPosZ, gpsPositions.get(1).chunkPosZ);
int maxX = Math.max(gpsPositions.get(0).chunkPosX, gpsPositions.get(1).chunkPosX);
int maxY = Math.max(gpsPositions.get(0).chunkPosY, gpsPositions.get(1).chunkPosY);
int maxZ = Math.max(gpsPositions.get(0).chunkPosZ, gpsPositions.get(1).chunkPosZ);
Set<ChunkPosition> positions = new HashSet<ChunkPosition>();
for(int x = minX; x <= maxX; x++) {
for(int y = Math.min(255, maxY); y >= minY && y >= 0; y--) {
for(int z = minZ; z <= maxZ; z++) {
positions.add(new ChunkPosition(x, y, z));
}
}
}
return positions;
}
return null;
}
@Override
public int getInventoryStackLimit(){
return 64;
}
@Override
public String getInventoryName(){
return Blockss.universalSensor.getUnlocalizedName();
}
@Override
public boolean isItemValidForSlot(int i, ItemStack itemstack){
return itemstack.getItem() == Itemss.machineUpgrade;
}
@Override
public void setText(int textFieldID, String text){
sensorGuiText = text;
if(!worldObj.isRemote) scheduleDescriptionPacket();
}
@Override
public String getText(int textFieldID){
return sensorGuiText;
}
@Override
public boolean hasCustomInventoryName(){
return false;
}
/*
* COMPUTERCRAFT API
*/
@Override
public String getType(){
return "universalSensor";
}
@Override
public void addLuaMethods(){
super.addLuaMethods();
luaMethods.add(new LuaMethod("getSensorNames"){
@Override
public Object[] call(Object[] args) throws Exception{
if(args.length == 0) {
return SensorHandler.instance().getSensorNames();
} else {
throw new IllegalArgumentException("getSensorNames doesn't accept any arguments!");
}
}
});
luaMethods.add(new LuaMethod("setSensor"){
@Override
public Object[] call(Object[] args) throws Exception{
if(args.length == 1) {
ISensorSetting sensor = null;
if(args[0] instanceof String) {
sensor = SensorHandler.instance().getSensorForName((String)args[0]);
} else {
sensor = SensorHandler.instance().getSensorByIndex(((Double)args[0]).intValue() - 1);
}
if(sensor != null) return new Object[]{setSensorSetting(sensor)};
throw new IllegalArgumentException("Invalid sensor name/index: " + args[0]);
} else if(args.length == 0) {
setSensorSetting("");
return new Object[]{true};
} else {
throw new IllegalArgumentException("setSensor needs one argument(a number as index, or a sensor name).");
}
}
});
luaMethods.add(new LuaMethod("getSensor"){
@Override
public Object[] call(Object[] args) throws Exception{
if(args.length == 0) {
ISensorSetting curSensor = SensorHandler.instance().getSensorFromPath(getSensorSetting());
return curSensor == null ? null : new Object[]{getSensorSetting().substring(getSensorSetting().lastIndexOf('/') + 1)};
} else {
throw new IllegalArgumentException("getSensor doesn't take any arguments!");
}
}
});
luaMethods.add(new LuaMethod("setTextfield"){
@Override
public Object[] call(Object[] args) throws Exception{
if(args.length == 1) {
setText(0, (String)args[0]);
return null;
} else {
throw new IllegalArgumentException("setTextfield takes one argument (string)");
}
}
});
luaMethods.add(new LuaMethod("getTextfield"){
@Override
public Object[] call(Object[] args) throws Exception{
if(args.length == 0) {
return new Object[]{getText(0)};
} else {
throw new IllegalArgumentException("getTextfield takes no arguments");
}
}
});
luaMethods.add(new LuaMethod("isSensorEventBased"){
@Override
public Object[] call(Object[] args) throws Exception{
if(args.length == 0) {
return new Object[]{SensorHandler.instance().getSensorFromPath(getSensorSetting()) instanceof IEventSensorSetting};
} else {
throw new IllegalArgumentException("isSensorEventBased takes no arguments");
}
}
});
luaMethods.add(new LuaMethod("getSensorValue"){
@Override
public Object[] call(Object[] args) throws Exception{
if(args.length == 0) {
ISensorSetting s = SensorHandler.instance().getSensorFromPath(getSensorSetting());
if(s instanceof IPollSensorSetting) {
requestPollPullEvent = true;
return new Object[]{redstoneStrength};
} else if(s != null) {
throw new IllegalArgumentException("The selected sensor is pull event based. You can't poll the value.");
} else {
throw new IllegalArgumentException("There's no sensor selected!");
}
} else {
throw new IllegalArgumentException("getSensorValue takes no arguments");
}
}
});
luaMethods.add(new LuaConstant("getMinWorkingPressure", PneumaticValues.MIN_PRESSURE_UNIVERSAL_SENSOR));
luaMethods.add(new LuaMethod("setGPSToolCoordinate"){
@Override
public Object[] call(Object[] args) throws Exception{
if(args.length == 4) {
ItemStack stack = getStackInSlot(((Double)args[0]).intValue() - 1); //minus one, as lua is 1-oriented.
if(stack != null && stack.getItem() == Itemss.GPSTool) {
ItemGPSTool.setGPSLocation(stack, ((Double)args[1]).intValue(), ((Double)args[2]).intValue(), ((Double)args[3]).intValue());
return new Object[]{true};
} else {
return new Object[]{false};
}
} else {
throw new IllegalArgumentException("setGPSToolCoordinate needs 4 arguments: slot, x, y, z");
}
}
});
luaMethods.add(new LuaMethod("getGPSToolCoordinate"){
@Override
public Object[] call(Object[] args) throws Exception{
if(args.length == 1) {
ItemStack stack = getStackInSlot(((Double)args[0]).intValue() - 1); //minus one, as lua is 1-oriented.
if(stack != null && stack.getItem() == Itemss.GPSTool) {
ChunkPosition pos = ItemGPSTool.getGPSLocation(stack);
if(pos != null) {
return new Object[]{pos.chunkPosX, pos.chunkPosY, pos.chunkPosZ};
} else {
return new Object[]{0, 0, 0};
}
} else {
return null;
}
} else {
throw new IllegalArgumentException("setGPSToolCoordinate needs 1 argument: slot");
}
}
});
}
@Override
@Optional.Method(modid = ModIds.COMPUTERCRAFT)
public void attach(IComputerAccess computer){
attachedComputers.add(computer);
}
@Override
@Optional.Method(modid = ModIds.COMPUTERCRAFT)
public void detach(IComputerAccess computer){
attachedComputers.remove(computer);
}
/**
* Called on a event sensor
* @param newRedstoneStrength
*/
@Optional.Method(modid = ModIds.COMPUTERCRAFT)
private void notifyComputers(Object... arguments){
for(IComputerAccess computer : attachedComputers) {
computer.queueEvent(getType(), arguments);
}
}
@Override
public boolean isUseableByPlayer(EntityPlayer var1){
return isGuiUseableByPlayer(var1);
}
@Override
public void openInventory(){}
@Override
public void closeInventory(){}
@Override
public int getRedstoneMode(){
return invertedRedstone ? 1 : 0;
}
@Override
public float getMinWorkingPressure(){
return PneumaticValues.MIN_PRESSURE_UNIVERSAL_SENSOR;
}
}