buildcraft.robotics.EntityRobot.java Source code

Java tutorial

Introduction

Here is the source code for buildcraft.robotics.EntityRobot.java

Source

/**
 * Copyright (c) 2011-2015, SpaceToad and the BuildCraft Team
 * http://www.mod-buildcraft.com
 * <p/>
 * BuildCraft is distributed under the terms of the Minecraft Mod Public
 * License 1.0, or MMPL. Please check the contents of the license located in
 * http://www.mod-buildcraft.com/MMPL-1.0.txt
 */
package buildcraft.robotics;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.WeakHashMap;

import com.google.common.collect.Iterables;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
import io.netty.buffer.ByteBuf;

import net.minecraft.client.Minecraft;
import net.minecraft.enchantment.EnchantmentHelper;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.SharedMonsterAttributes;
import net.minecraft.entity.ai.attributes.AttributeModifier;
import net.minecraft.entity.item.EntityFallingBlock;
import net.minecraft.entity.monster.IMob;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.inventory.IInventory;
import net.minecraft.item.Item;
import net.minecraft.item.ItemArmor;
import net.minecraft.item.ItemSkull;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList;
import net.minecraft.nbt.NBTUtil;
import net.minecraft.server.MinecraftServer;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.AxisAlignedBB;
import net.minecraft.util.DamageSource;
import net.minecraft.util.EntityDamageSource;
import net.minecraft.util.IIcon;
import net.minecraft.util.MathHelper;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.StatCollector;
import net.minecraft.util.StringUtils;
import net.minecraft.util.Vec3;
import net.minecraft.world.World;
import net.minecraft.world.WorldServer;

import cpw.mods.fml.common.registry.IEntityAdditionalSpawnData;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import net.minecraftforge.common.ForgeHooks;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.common.util.Constants;
import net.minecraftforge.common.util.Constants.NBT;
import net.minecraftforge.common.util.ForgeDirection;
import net.minecraftforge.event.entity.player.AttackEntityEvent;
import net.minecraftforge.fluids.Fluid;
import net.minecraftforge.fluids.FluidContainerRegistry;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.FluidTankInfo;
import net.minecraftforge.fluids.IFluidHandler;

import buildcraft.BuildCraftCore;
import buildcraft.api.boards.RedstoneBoardNBT;
import buildcraft.api.boards.RedstoneBoardRegistry;
import buildcraft.api.boards.RedstoneBoardRobot;
import buildcraft.api.boards.RedstoneBoardRobotNBT;
import buildcraft.api.core.BCLog;
import buildcraft.api.core.BlockIndex;
import buildcraft.api.core.IZone;
import buildcraft.api.events.RobotEvent;
import buildcraft.api.robots.AIRobot;
import buildcraft.api.robots.DockingStation;
import buildcraft.api.robots.EntityRobotBase;
import buildcraft.api.robots.IRobotOverlayItem;
import buildcraft.api.robots.RobotManager;
import buildcraft.api.statements.StatementSlot;
import buildcraft.api.tiles.IDebuggable;
import buildcraft.core.DefaultProps;
import buildcraft.core.ItemWrench;
import buildcraft.core.LaserData;
import buildcraft.core.lib.RFBattery;
import buildcraft.core.lib.network.command.CommandWriter;
import buildcraft.core.lib.network.command.ICommandReceiver;
import buildcraft.core.lib.network.command.PacketCommand;
import buildcraft.core.lib.utils.NetworkUtils;
import buildcraft.core.proxy.CoreProxy;
import buildcraft.robotics.ai.AIRobotMain;
import buildcraft.robotics.ai.AIRobotShutdown;
import buildcraft.robotics.ai.AIRobotSleep;
import buildcraft.robotics.statements.ActionRobotWorkInArea;
import buildcraft.robotics.statements.ActionRobotWorkInArea.AreaType;

public class EntityRobot extends EntityRobotBase
        implements IEntityAdditionalSpawnData, IInventory, IFluidHandler, ICommandReceiver, IDebuggable {

    public static final ResourceLocation ROBOT_BASE = new ResourceLocation(
            DefaultProps.TEXTURE_PATH_ROBOTS + "/robot_base.png");
    public static final int MAX_WEARABLES = 8;

    private static Set<Integer> blacklistedItemsForUpdate = Sets.newHashSet();

    public LaserData laser = new LaserData();
    public DockingStation linkedDockingStation;
    public BlockIndex linkedDockingStationIndex;
    public ForgeDirection linkedDockingStationSide;

    public BlockIndex currentDockingStationIndex;
    public ForgeDirection currentDockingStationSide;

    public boolean isDocked = false;

    public RedstoneBoardRobot board;
    public AIRobotMain mainAI;

    public ItemStack itemInUse;
    public float itemAngle1 = 0;
    public float itemAngle2 = 0;
    public boolean itemActive = false;
    public float itemActiveStage = 0;
    public long lastUpdateTime = 0;

    private DockingStation currentDockingStation;
    private List<ItemStack> wearables = new ArrayList<ItemStack>();

    private boolean needsUpdate = false;
    private ItemStack[] inv = new ItemStack[4];
    private FluidStack tank;
    private int maxFluid = FluidContainerRegistry.BUCKET_VOLUME * 4;
    private ResourceLocation texture;

    private WeakHashMap<Entity, Long> unreachableEntities = new WeakHashMap<Entity, Long>();

    private NBTTagList stackRequestNBT;

    private RFBattery battery = new RFBattery(MAX_ENERGY, MAX_ENERGY, 100);

    private boolean firstUpdateDone = false;

    private boolean isActiveClient = false;

    private long robotId = EntityRobotBase.NULL_ROBOT_ID;

    private int energySpendPerCycle = 0;
    private int ticksCharging = 0;
    private float energyFX = 0;
    private int steamDx = 0;
    private int steamDy = -1;
    private int steamDz = 0;

    public EntityRobot(World world, RedstoneBoardRobotNBT boardNBT) {
        this(world);

        board = boardNBT.create(this);
        dataWatcher.updateObject(16, board.getNBTHandler().getID());

        if (!world.isRemote) {
            mainAI = new AIRobotMain(this);
            mainAI.start();
        }
    }

    public EntityRobot(World world) {
        super(world);

        motionX = 0;
        motionY = 0;
        motionZ = 0;

        ignoreFrustumCheck = true;
        laser.isVisible = false;
        entityCollisionReduction = 1F;

        width = 0.25F;
        height = 0.25F;
    }

    @Override
    protected void entityInit() {
        super.entityInit();

        setNullBoundingBox();

        preventEntitySpawning = false;
        noClip = true;
        isImmuneToFire = true;
        this.func_110163_bv(); // persistenceRequired = true

        dataWatcher.addObject(12, (float) 0);
        dataWatcher.addObject(13, (float) 0);
        dataWatcher.addObject(14, (float) 0);
        dataWatcher.addObject(15, (byte) 0);
        dataWatcher.addObject(16, "");
        dataWatcher.addObject(17, (float) 0);
        dataWatcher.addObject(18, (float) 0);
        dataWatcher.addObject(19, 0);
        dataWatcher.addObject(20, (byte) 0);
        dataWatcher.addObject(21, 0);
    }

    protected void updateDataClient() {
        laser.tail.x = dataWatcher.getWatchableObjectFloat(12);
        laser.tail.y = dataWatcher.getWatchableObjectFloat(13);
        laser.tail.z = dataWatcher.getWatchableObjectFloat(14);
        laser.isVisible = dataWatcher.getWatchableObjectByte(15) == 1;

        RedstoneBoardNBT<?> boardNBT = RedstoneBoardRegistry.instance
                .getRedstoneBoard(dataWatcher.getWatchableObjectString(16));

        if (boardNBT != null) {
            texture = ((RedstoneBoardRobotNBT) boardNBT).getRobotTexture();
        }

        itemAngle1 = dataWatcher.getWatchableObjectFloat(17);
        itemAngle2 = dataWatcher.getWatchableObjectFloat(18);
        energySpendPerCycle = dataWatcher.getWatchableObjectInt(19);
        isActiveClient = dataWatcher.getWatchableObjectByte(20) == 1;
        battery.setEnergy(dataWatcher.getWatchableObjectInt(21));
    }

    protected void updateDataServer() {
        dataWatcher.updateObject(12, (float) laser.tail.x);
        dataWatcher.updateObject(13, (float) laser.tail.y);
        dataWatcher.updateObject(14, (float) laser.tail.z);
        dataWatcher.updateObject(15, (byte) (laser.isVisible ? 1 : 0));
        dataWatcher.updateObject(17, itemAngle1);
        dataWatcher.updateObject(18, itemAngle2);
    }

    public boolean isActive() {
        if (worldObj.isRemote) {
            return isActiveClient;
        } else {
            return mainAI.getActiveAI() instanceof AIRobotSleep || mainAI.getActiveAI() instanceof AIRobotShutdown;
        }
    }

    protected void init() {
        if (worldObj.isRemote) {
            BuildCraftCore.instance.sendToServer(new PacketCommand(this, "requestInitialization", null));
        }
    }

    public void setLaserDestination(float x, float y, float z) {
        if (x != laser.tail.x || y != laser.tail.y || z != laser.tail.z) {
            laser.tail.x = x;
            laser.tail.y = y;
            laser.tail.z = z;

            needsUpdate = true;
        }
    }

    public void showLaser() {
        if (!laser.isVisible) {
            laser.isVisible = true;
            needsUpdate = true;
        }
    }

    public void hideLaser() {
        if (laser.isVisible) {
            laser.isVisible = false;
            needsUpdate = true;
        }
    }

    protected void firstUpdate() {
        if (stackRequestNBT != null) {

        }

        if (!worldObj.isRemote) {
            getRegistry().registerRobot(this);
        }
    }

    @Override
    public String getCommandSenderName() {
        return StatCollector.translateToLocal("item.robot.name");
    }

    @Override
    public void onEntityUpdate() {
        this.worldObj.theProfiler.startSection("bcEntityRobot");
        if (!firstUpdateDone) {
            firstUpdate();
            firstUpdateDone = true;
        }

        if (ticksCharging > 0) {
            ticksCharging--;
        }

        if (!worldObj.isRemote) {
            // The client-side sleep indicator should also display if the robot is charging.
            // To not break gates and other things checking for sleep, this is done here.
            dataWatcher.updateObject(20, (byte) ((isActive() && ticksCharging == 0) ? 1 : 0));
            dataWatcher.updateObject(21, getEnergy());

            if (needsUpdate) {
                updateDataServer();
                needsUpdate = false;
            }
        }

        if (worldObj.isRemote) {
            updateDataClient();
            updateRotationYaw(60.0f);
            updateEnergyFX();
        }

        if (currentDockingStation != null) {
            motionX = 0;
            motionY = 0;
            motionZ = 0;
            posX = currentDockingStation.x() + 0.5F + currentDockingStation.side().offsetX * 0.5F;
            posY = currentDockingStation.y() + 0.5F + currentDockingStation.side().offsetY * 0.5F;
            posZ = currentDockingStation.z() + 0.5F + currentDockingStation.side().offsetZ * 0.5F;
        }

        if (!worldObj.isRemote) {
            if (linkedDockingStation == null) {
                if (linkedDockingStationIndex != null) {
                    linkedDockingStation = getRegistry().getStation(linkedDockingStationIndex.x,
                            linkedDockingStationIndex.y, linkedDockingStationIndex.z, linkedDockingStationSide);
                }

                if (linkedDockingStation == null) {
                    shutdown("no docking station");
                } else {
                    if (linkedDockingStation.robotTaking() != this) {
                        if (linkedDockingStation.robotIdTaking() == robotId) {
                            BCLog.logger.warn("A robot entity was not properly unloaded");
                            linkedDockingStation.invalidateRobotTakingEntity();
                        }
                        if (linkedDockingStation.robotTaking() != this) {
                            shutdown("wrong docking station");
                        }
                    }
                }
            }

            if (currentDockingStationIndex != null && currentDockingStation == null) {
                currentDockingStation = getRegistry().getStation(currentDockingStationIndex.x,
                        currentDockingStationIndex.y, currentDockingStationIndex.z, currentDockingStationSide);
            }

            if (posY < -128) {
                isDead = true;

                BCLog.logger.info("Destroying robot " + this.toString() + " - Fallen into Void");
                getRegistry().killRobot(this);
            }

            if (linkedDockingStation == null || linkedDockingStation.isInitialized()) {
                this.worldObj.theProfiler.startSection("bcRobotAI");
                mainAI.cycle();
                this.worldObj.theProfiler.endSection();

                if (energySpendPerCycle != mainAI.getActiveAI().getEnergyCost()) {
                    energySpendPerCycle = mainAI.getActiveAI().getEnergyCost();
                    dataWatcher.updateObject(19, energySpendPerCycle);
                }
            }
        }

        // tick all carried itemstacks
        for (int i = 0; i < inv.length; i++) {
            updateItem(inv[i], i, false);
        }

        // tick the item the robot is currently holding
        updateItem(itemInUse, 0, true);

        // do not tick wearables or equipment from EntityLiving

        super.onEntityUpdate();
        this.worldObj.theProfiler.endSection();
    }

    @Override
    protected void updateEntityActionState() {
    }

    @Override
    public boolean handleWaterMovement() {
        return false;
    }

    @SideOnly(Side.CLIENT)
    private void updateEnergyFX() {
        energyFX += energySpendPerCycle;

        if (energyFX >= (100 << (2 * Minecraft.getMinecraft().gameSettings.particleSetting))) {
            energyFX = 0;
            spawnEnergyFX();
        }
    }

    @SideOnly(Side.CLIENT)
    private void spawnEnergyFX() {
        Minecraft.getMinecraft().effectRenderer.addEffect(new EntityRobotEnergyParticle(worldObj,
                posX + steamDx * 0.25, posY + steamDy * 0.25, posZ + steamDz * 0.25, steamDx * 0.05, steamDy * 0.05,
                steamDz * 0.05, energySpendPerCycle * 0.075F < 1 ? 1 : energySpendPerCycle * 0.075F));
    }

    @Override
    public AxisAlignedBB getBoundingBox() {
        return AxisAlignedBB.getBoundingBox(posX - 0.25F, posY - 0.25F, posZ - 0.25F, posX + 0.25F, posY + 0.25F,
                posZ + 0.25F);
    }

    public void setNullBoundingBox() {
        width = 0F;
        height = 0F;

        boundingBox.minX = posX;
        boundingBox.minY = posY;
        boundingBox.minZ = posZ;

        boundingBox.maxX = posX;
        boundingBox.maxY = posY;
        boundingBox.maxZ = posZ;
    }

    private void shutdown(String reason) {
        if (!(mainAI.getDelegateAI() instanceof AIRobotShutdown)) {
            BCLog.logger.info("Shutting down robot " + this.toString() + " - " + reason);
            mainAI.startDelegateAI(new AIRobotShutdown(this));
        }
    }

    @Override
    public void writeSpawnData(ByteBuf data) {
        data.writeByte(wearables.size());
        for (ItemStack s : wearables) {
            NetworkUtils.writeStack(data, s);
        }
    }

    @Override
    public void readSpawnData(ByteBuf data) {
        int amount = data.readUnsignedByte();
        while (amount > 0) {
            wearables.add(NetworkUtils.readStack(data));
            amount--;
        }
        init();
    }

    @Override
    public ItemStack getHeldItem() {
        return itemInUse;
    }

    @Override
    public void setCurrentItemOrArmor(int i, ItemStack itemstack) {
    }

    @Override
    public ItemStack[] getLastActiveItems() {
        return new ItemStack[0];
    }

    @Override
    protected void fall(float par1) {
    }

    @Override
    protected void updateFallState(double par1, boolean par3) {
    }

    @Override
    public void moveEntityWithHeading(float par1, float par2) {
        this.setPosition(posX + motionX, posY + motionY, posZ + motionZ);
    }

    @Override
    public boolean isOnLadder() {
        return false;
    }

    public ResourceLocation getTexture() {
        return texture;
    }

    @Override
    public void writeEntityToNBT(NBTTagCompound nbt) {
        super.writeEntityToNBT(nbt);

        if (linkedDockingStationIndex != null) {
            NBTTagCompound linkedStationNBT = new NBTTagCompound();
            NBTTagCompound linkedStationIndexNBT = new NBTTagCompound();
            linkedDockingStationIndex.writeTo(linkedStationIndexNBT);
            linkedStationNBT.setTag("index", linkedStationIndexNBT);
            linkedStationNBT.setByte("side", (byte) linkedDockingStationSide.ordinal());
            nbt.setTag("linkedStation", linkedStationNBT);
        }

        if (currentDockingStationIndex != null) {
            NBTTagCompound currentStationNBT = new NBTTagCompound();
            NBTTagCompound currentStationIndexNBT = new NBTTagCompound();
            currentDockingStationIndex.writeTo(currentStationIndexNBT);
            currentStationNBT.setTag("index", currentStationIndexNBT);
            currentStationNBT.setByte("side", (byte) currentDockingStationSide.ordinal());
            nbt.setTag("currentStation", currentStationNBT);
        }

        NBTTagCompound nbtLaser = new NBTTagCompound();
        laser.writeToNBT(nbtLaser);
        nbt.setTag("laser", nbtLaser);

        NBTTagCompound batteryNBT = new NBTTagCompound();
        battery.writeToNBT(batteryNBT);
        nbt.setTag("battery", batteryNBT);

        if (itemInUse != null) {
            NBTTagCompound itemNBT = new NBTTagCompound();
            itemInUse.writeToNBT(itemNBT);
            nbt.setTag("itemInUse", itemNBT);
            nbt.setBoolean("itemActive", itemActive);
        }

        for (int i = 0; i < inv.length; ++i) {
            NBTTagCompound stackNbt = new NBTTagCompound();

            if (inv[i] != null) {
                nbt.setTag("inv[" + i + "]", inv[i].writeToNBT(stackNbt));
            }
        }

        if (wearables.size() > 0) {
            NBTTagList wearableList = new NBTTagList();

            for (ItemStack wearable : wearables) {
                NBTTagCompound item = new NBTTagCompound();
                wearable.writeToNBT(item);
                wearableList.appendTag(item);
            }

            nbt.setTag("wearables", wearableList);
        }

        NBTTagCompound ai = new NBTTagCompound();
        mainAI.writeToNBT(ai);
        nbt.setTag("mainAI", ai);

        if (mainAI.getDelegateAI() != board) {
            NBTTagCompound boardNBT = new NBTTagCompound();
            board.writeToNBT(boardNBT);
            nbt.setTag("board", boardNBT);
        }

        nbt.setLong("robotId", robotId);

        if (tank != null) {
            NBTTagCompound tankNBT = new NBTTagCompound();

            tank.writeToNBT(tankNBT);

            nbt.setTag("tank", tankNBT);
        }
    }

    @Override
    public void readEntityFromNBT(NBTTagCompound nbt) {
        super.readEntityFromNBT(nbt);

        if (nbt.hasKey("linkedStation")) {
            NBTTagCompound linkedStationNBT = nbt.getCompoundTag("linkedStation");
            linkedDockingStationIndex = new BlockIndex(linkedStationNBT.getCompoundTag("index"));
            linkedDockingStationSide = ForgeDirection.values()[linkedStationNBT.getByte("side")];
        }

        if (nbt.hasKey("currentStation")) {
            NBTTagCompound currentStationNBT = nbt.getCompoundTag("currentStation");
            currentDockingStationIndex = new BlockIndex(currentStationNBT.getCompoundTag("index"));
            currentDockingStationSide = ForgeDirection.values()[currentStationNBT.getByte("side")];

        }

        laser.readFromNBT(nbt.getCompoundTag("laser"));

        battery.readFromNBT(nbt.getCompoundTag("battery"));

        wearables.clear();
        if (nbt.hasKey("wearables")) {
            NBTTagList list = nbt.getTagList("wearables", 10);
            for (int i = 0; i < list.tagCount(); i++) {
                ItemStack stack = ItemStack.loadItemStackFromNBT(list.getCompoundTagAt(i));
                if (stack != null) {
                    wearables.add(stack);
                }
            }
        }

        if (nbt.hasKey("itemInUse")) {
            itemInUse = ItemStack.loadItemStackFromNBT(nbt.getCompoundTag("itemInUse"));
            itemActive = nbt.getBoolean("itemActive");
        }

        for (int i = 0; i < inv.length; ++i) {
            inv[i] = ItemStack.loadItemStackFromNBT(nbt.getCompoundTag("inv[" + i + "]"));
        }

        NBTTagCompound ai = nbt.getCompoundTag("mainAI");
        mainAI = (AIRobotMain) AIRobot.loadAI(ai, this);

        if (nbt.hasKey("board")) {
            board = (RedstoneBoardRobot) AIRobot.loadAI(nbt.getCompoundTag("board"), this);
        } else {
            board = (RedstoneBoardRobot) mainAI.getDelegateAI();
        }

        if (board == null) {
            board = RedstoneBoardRegistry.instance.getEmptyRobotBoard().create(this);
        }

        dataWatcher.updateObject(16, board.getNBTHandler().getID());

        stackRequestNBT = nbt.getTagList("stackRequests", Constants.NBT.TAG_COMPOUND);

        if (nbt.hasKey("robotId")) {
            robotId = nbt.getLong("robotId");
        }

        if (nbt.hasKey("tank")) {
            tank = FluidStack.loadFluidStackFromNBT(nbt.getCompoundTag("tank"));
        } else {
            tank = null;
        }

        // Restore robot persistence on pre-6.1.9 robotics
        this.func_110163_bv();
    }

    @Override
    public void dock(DockingStation station) {
        currentDockingStation = station;

        setSteamDirection(currentDockingStation.side.offsetX, currentDockingStation.side.offsetY,
                currentDockingStation.side.offsetZ);

        currentDockingStationIndex = currentDockingStation.index();
        currentDockingStationSide = currentDockingStation.side();
    }

    @Override
    public void undock() {
        if (currentDockingStation != null) {
            currentDockingStation.release(this);
            currentDockingStation = null;

            setSteamDirection(0, -1, 0);

            currentDockingStationIndex = null;
            currentDockingStationSide = null;
        }
    }

    @Override
    public DockingStation getDockingStation() {
        return currentDockingStation;
    }

    @Override
    public void setMainStation(DockingStation station) {
        if (linkedDockingStation != null && linkedDockingStation != station) {
            linkedDockingStation.unsafeRelease(this);
        }

        linkedDockingStation = station;
        if (station != null) {
            linkedDockingStationIndex = linkedDockingStation.index();
            linkedDockingStationSide = linkedDockingStation.side();
        } else {
            linkedDockingStationIndex = null;
            linkedDockingStationSide = ForgeDirection.UNKNOWN;
        }
    }

    @Override
    public ItemStack getEquipmentInSlot(int var1) {
        return null;
    }

    @Override
    public int getSizeInventory() {
        return inv.length;
    }

    @Override
    public ItemStack getStackInSlot(int var1) {
        return inv[var1];
    }

    @Override
    public ItemStack decrStackSize(int var1, int var2) {
        ItemStack result = inv[var1].splitStack(var2);

        if (inv[var1].stackSize == 0) {
            inv[var1] = null;
        }

        updateClientSlot(var1);

        return result;
    }

    @Override
    public ItemStack getStackInSlotOnClosing(int var1) {
        ItemStack stack = inv[var1];
        inv[var1] = null;
        return stack;
    }

    @Override
    public void setInventorySlotContents(int var1, ItemStack var2) {
        inv[var1] = var2;

        updateClientSlot(var1);
    }

    @Override
    public String getInventoryName() {
        return null;
    }

    @Override
    public boolean hasCustomInventoryName() {
        return false;
    }

    @Override
    public int getInventoryStackLimit() {
        return 64;
    }

    @Override
    public void markDirty() {
    }

    public void updateClientSlot(final int slot) {
        BuildCraftCore.instance.sendToEntity(new PacketCommand(this, "clientSetInventory", new CommandWriter() {
            public void write(ByteBuf data) {
                data.writeShort(slot);
                NetworkUtils.writeStack(data, inv[slot]);
            }
        }), this);
    }

    @Override
    public boolean isUseableByPlayer(EntityPlayer var1) {
        return false;
    }

    @Override
    public void openInventory() {
    }

    @Override
    public void closeInventory() {
    }

    @Override
    public boolean isItemValidForSlot(int var1, ItemStack var2) {
        return inv[var1] == null || (inv[var1].isItemEqual(var2) && inv[var1].isStackable()
                && inv[var1].stackSize + var2.stackSize <= inv[var1].getItem().getItemStackLimit(inv[var1]));
    }

    @Override
    public boolean isMoving() {
        return motionX != 0 || motionY != 0 || motionZ != 0;
    }

    @Override
    public void setItemInUse(ItemStack stack) {
        itemInUse = stack;
        BuildCraftCore.instance.sendToEntity(new PacketCommand(this, "clientSetItemInUse", new CommandWriter() {
            public void write(ByteBuf data) {
                NetworkUtils.writeStack(data, itemInUse);
            }
        }), this);
    }

    private void setSteamDirection(final int x, final int y, final int z) {
        if (!worldObj.isRemote) {
            BuildCraftCore.instance.sendToEntity(new PacketCommand(this, "setSteamDirection", new CommandWriter() {
                public void write(ByteBuf data) {
                    data.writeInt(x);
                    data.writeShort(y);
                    data.writeInt(z);
                }
            }), this);
        } else {
            Vec3 v = Vec3.createVectorHelper(x, y, z);
            v = v.normalize();

            steamDx = (int) v.xCoord;
            steamDy = (int) v.yCoord;
            steamDz = (int) v.zCoord;
        }
    }

    @Override
    public void receiveCommand(String command, Side side, Object sender, ByteBuf stream) {
        if (side.isClient()) {
            if ("clientSetItemInUse".equals(command)) {
                itemInUse = NetworkUtils.readStack(stream);
            } else if ("clientSetInventory".equals(command)) {
                int slot = stream.readUnsignedShort();
                inv[slot] = NetworkUtils.readStack(stream);
            } else if ("initialize".equals(command)) {
                itemInUse = NetworkUtils.readStack(stream);
                itemActive = stream.readBoolean();
            } else if ("setItemActive".equals(command)) {
                itemActive = stream.readBoolean();
                itemActiveStage = 0;
                lastUpdateTime = new Date().getTime();

                if (!itemActive) {
                    setSteamDirection(0, -1, 0);
                }
            } else if ("setSteamDirection".equals(command)) {
                setSteamDirection(stream.readInt(), stream.readShort(), stream.readInt());
            } else if ("syncWearables".equals(command)) {
                wearables.clear();

                int amount = stream.readUnsignedByte();
                while (amount > 0) {
                    wearables.add(NetworkUtils.readStack(stream));
                    amount--;
                }
            }
        } else if (side.isServer()) {
            EntityPlayer p = (EntityPlayer) sender;
            if ("requestInitialization".equals(command)) {
                BuildCraftCore.instance.sendToPlayer(p, new PacketCommand(this, "initialize", new CommandWriter() {
                    public void write(ByteBuf data) {
                        NetworkUtils.writeStack(data, itemInUse);
                        data.writeBoolean(itemActive);
                    }
                }));

                for (int i = 0; i < inv.length; ++i) {
                    final int j = i;
                    BuildCraftCore.instance.sendToPlayer(p,
                            new PacketCommand(this, "clientSetInventory", new CommandWriter() {
                                public void write(ByteBuf data) {
                                    data.writeShort(j);
                                    NetworkUtils.writeStack(data, inv[j]);
                                }
                            }));
                }

                if (currentDockingStation != null) {
                    setSteamDirection(currentDockingStation.side.offsetX, currentDockingStation.side.offsetY,
                            currentDockingStation.side.offsetZ);
                } else {
                    setSteamDirection(0, -1, 0);
                }
            }
        }
    }

    @Override
    public void setHealth(float par1) {
        // deactivate health management
    }

    @Override
    public boolean attackEntityFrom(DamageSource source, float f) {
        // Ignore hits from mobs or when docked.
        Entity src = source.getSourceOfDamage();
        if (src != null && !(src instanceof EntityFallingBlock) && !(src instanceof IMob)
                && currentDockingStation == null) {
            if (ForgeHooks.onLivingAttack(this, source, f)) {
                return false;
            }

            if (!worldObj.isRemote) {
                hurtTime = maxHurtTime = 10;

                int mul = 2600;
                for (ItemStack s : wearables) {
                    if (s.getItem() instanceof ItemArmor) {
                        mul = mul * 2 / (2 + ((ItemArmor) s.getItem()).damageReduceAmount);
                    } else {
                        mul *= 0.7;
                    }
                }

                int energy = Math.round(f * mul);
                if (battery.getEnergyStored() - energy > 0) {
                    battery.setEnergy(battery.getEnergyStored() - energy);
                    return true;
                } else {
                    onRobotHit(true);
                }
            }
            return true;
        }
        return false;
    }

    @Override
    public float getAimYaw() {
        return itemAngle1;
    }

    @Override
    public float getAimPitch() {
        return itemAngle2;
    }

    @Override
    public void aimItemAt(float yaw, float pitch) {
        itemAngle1 = yaw;
        itemAngle2 = pitch;

        updateDataServer();
    }

    @Override
    public void aimItemAt(int x, int y, int z) {
        int deltaX = x - (int) Math.floor(posX);
        int deltaY = y - (int) Math.floor(posY);
        int deltaZ = z - (int) Math.floor(posZ);

        if (deltaX != 0 || deltaZ != 0) {
            itemAngle1 = (float) (Math.atan2(deltaZ, deltaX) * 180f / Math.PI) + 180f;
        }
        double d3 = MathHelper.sqrt_double(deltaX * deltaX + deltaZ * deltaZ);
        itemAngle2 = (float) (-(Math.atan2(deltaY, d3) * 180.0D / Math.PI));

        setSteamDirection(deltaX, deltaY, deltaZ);

        updateDataServer();
    }

    private void updateRotationYaw(float maxStep) {
        float step = MathHelper.wrapAngleTo180_float(itemAngle1 - rotationYaw);

        if (step > maxStep) {
            step = maxStep;
        }

        if (step < -maxStep) {
            step = -maxStep;
        }

        rotationYaw = rotationYaw + step;
    }

    @Override
    protected float func_110146_f(float targetYaw, float dist) {
        if (worldObj.isRemote) {
            float f2 = MathHelper.wrapAngleTo180_float(this.rotationYaw - this.renderYawOffset);
            this.renderYawOffset += f2 * 0.5F;
            float f3 = MathHelper.wrapAngleTo180_float(this.rotationYaw - this.renderYawOffset);
            boolean flag = f3 < -90.0F || f3 >= 90.0F;

            this.renderYawOffset = this.rotationYaw - f3;

            if (f3 * f3 > 2500.0F) {
                this.renderYawOffset += f3 * 0.2F;
            }

            float newDist = dist;
            if (flag) {
                newDist *= -1.0F;
            }

            return newDist;
        }
        return 0;
    }

    @Override
    public void setItemActive(final boolean isActive) {
        if (isActive != itemActive) {
            itemActive = isActive;
            BuildCraftCore.instance.sendToEntity(new PacketCommand(this, "setItemActive", new CommandWriter() {
                public void write(ByteBuf data) {
                    data.writeBoolean(isActive);
                }
            }), this);
        }
    }

    @Override
    public RedstoneBoardRobot getBoard() {
        return board;
    }

    @Override
    public DockingStation getLinkedStation() {
        return linkedDockingStation;
    }

    @SideOnly(Side.CLIENT)
    @Override
    public boolean isInRangeToRenderDist(double par1) {
        return true;
    }

    @Override
    public int getEnergy() {
        return battery.getEnergyStored();
    }

    @Override
    public RFBattery getBattery() {
        return battery;
    }

    @Override
    protected boolean canDespawn() {
        return false;
    }

    public AIRobot getOverridingAI() {
        return mainAI.getOverridingAI();
    }

    public void overrideAI(AIRobot ai) {
        mainAI.setOverridingAI(ai);
    }

    public void attackTargetEntityWithCurrentItem(Entity par1Entity) {
        if (MinecraftForge.EVENT_BUS.post(new AttackEntityEvent(CoreProxy.proxy
                .getBuildCraftPlayer((WorldServer) worldObj, (int) posX, (int) posY, (int) posZ).get(),
                par1Entity))) {
            return;
        }

        if (par1Entity.canAttackWithItem()) {
            if (!par1Entity.hitByEntity(this)) {
                Multimap<String, AttributeModifier> attributes = itemInUse != null
                        ? (Multimap<String, AttributeModifier>) itemInUse.getAttributeModifiers()
                        : null;
                float attackDamage = 2.0F;
                int knockback = 0;

                if (attributes != null) {
                    for (AttributeModifier modifier : attributes
                            .get(SharedMonsterAttributes.attackDamage.getAttributeUnlocalizedName())) {
                        switch (modifier.getOperation()) {
                        case 0:
                            attackDamage += modifier.getAmount();
                            break;
                        case 1:
                            attackDamage *= modifier.getAmount();
                            break;
                        case 2:
                            attackDamage *= 1.0F + modifier.getAmount();
                            break;
                        }
                    }
                }

                if (par1Entity instanceof EntityLivingBase) {
                    attackDamage += EnchantmentHelper.getEnchantmentModifierLiving(this,
                            (EntityLivingBase) par1Entity);
                    knockback += EnchantmentHelper.getKnockbackModifier(this, (EntityLivingBase) par1Entity);
                }

                if (attackDamage > 0.0F) {
                    int fireAspect = EnchantmentHelper.getFireAspectModifier(this);

                    if (par1Entity instanceof EntityLivingBase && fireAspect > 0 && !par1Entity.isBurning()) {
                        par1Entity.setFire(fireAspect * 4);
                    }

                    if (par1Entity.attackEntityFrom(new EntityDamageSource("robot", this), attackDamage)) {
                        this.setLastAttacker(par1Entity);

                        if (knockback > 0) {
                            par1Entity.addVelocity(
                                    (double) (-MathHelper.sin(this.rotationYaw * (float) Math.PI / 180.0F)
                                            * (float) knockback * 0.5F),
                                    0.1D, (double) (MathHelper.cos(this.rotationYaw * (float) Math.PI / 180.0F)
                                            * (float) knockback * 0.5F));
                            this.motionX *= 0.6D;
                            this.motionZ *= 0.6D;
                            this.setSprinting(false);
                        }

                        if (par1Entity instanceof EntityLivingBase) {
                            EnchantmentHelper.func_151384_a((EntityLivingBase) par1Entity, this);
                        }

                        EnchantmentHelper.func_151385_b(this, par1Entity);

                        ItemStack itemstack = itemInUse;

                        if (itemstack != null && par1Entity instanceof EntityLivingBase) {
                            itemstack.getItem().hitEntity(itemstack, (EntityLivingBase) par1Entity, this);
                        }

                        if (itemInUse.stackSize == 0) {
                            setItemInUse(null);
                        }
                    }
                }
            }
        }
    }

    @Override
    public IZone getZoneToWork() {
        return getZone(ActionRobotWorkInArea.AreaType.WORK);
    }

    @Override
    public IZone getZoneToLoadUnload() {
        IZone zone = getZone(ActionRobotWorkInArea.AreaType.LOAD_UNLOAD);
        if (zone == null) {
            zone = getZoneToWork();
        }
        return zone;
    }

    private IZone getZone(AreaType areaType) {
        if (linkedDockingStation != null) {
            for (StatementSlot s : linkedDockingStation.getActiveActions()) {
                if (s.statement instanceof ActionRobotWorkInArea
                        && ((ActionRobotWorkInArea) s.statement).getAreaType() == areaType) {
                    IZone zone = ActionRobotWorkInArea.getArea(s);

                    if (zone != null) {
                        return zone;
                    }
                }
            }
        }

        return null;
    }

    @Override
    public boolean containsItems() {
        for (ItemStack element : inv) {
            if (element != null) {
                return true;
            }
        }

        return false;
    }

    @Override
    public boolean hasFreeSlot() {
        for (ItemStack element : inv) {
            if (element == null) {
                return true;
            }
        }

        return false;
    }

    @Override
    public void unreachableEntityDetected(Entity entity) {
        unreachableEntities.put(entity, worldObj.getTotalWorldTime() + 1200);
    }

    @Override
    public boolean isKnownUnreachable(Entity entity) {
        if (unreachableEntities.containsKey(entity)) {
            if (unreachableEntities.get(entity) >= worldObj.getTotalWorldTime()) {
                return true;
            } else {
                unreachableEntities.remove(entity);
                return false;
            }
        } else {
            return false;
        }
    }

    protected void onRobotHit(boolean attacked) {
        if (!worldObj.isRemote) {
            if (attacked) {
                convertToItems();
            } else {
                if (wearables.size() > 0) {
                    entityDropItem(wearables.remove(wearables.size() - 1), 0);
                    syncWearablesToClient();
                } else if (itemInUse != null) {
                    entityDropItem(itemInUse, 0);
                    itemInUse = null;
                } else {
                    convertToItems();
                }
            }
        }
    }

    @Override
    protected boolean interact(EntityPlayer player) {
        ItemStack stack = player.getCurrentEquippedItem();
        if (stack == null || stack.getItem() == null) {
            return false;
        }

        RobotEvent.Interact robotInteractEvent = new RobotEvent.Interact(this, player, stack);
        MinecraftForge.EVENT_BUS.post(robotInteractEvent);
        if (robotInteractEvent.isCanceled()) {
            return false;
        }

        if (player.isSneaking() && stack.getItem() == BuildCraftCore.wrenchItem) {
            RobotEvent.Dismantle robotDismantleEvent = new RobotEvent.Dismantle(this, player);
            MinecraftForge.EVENT_BUS.post(robotDismantleEvent);
            if (robotDismantleEvent.isCanceled()) {
                return false;
            }

            onRobotHit(false);

            if (worldObj.isRemote) {
                ((ItemWrench) stack.getItem()).wrenchUsed(player, 0, 0, 0);
            }
            return true;
        } else if (wearables.size() < MAX_WEARABLES && stack.getItem().isValidArmor(stack, 0, this)) {
            if (!worldObj.isRemote) {
                wearables.add(stack.splitStack(1));
                syncWearablesToClient();
            } else {
                player.swingItem();
            }
            return true;
        } else if (wearables.size() < MAX_WEARABLES && stack.getItem() instanceof IRobotOverlayItem
                && ((IRobotOverlayItem) stack.getItem()).isValidRobotOverlay(stack)) {
            if (!worldObj.isRemote) {
                wearables.add(stack.splitStack(1));
                syncWearablesToClient();
            } else {
                player.swingItem();
            }
            return true;
        } else if (wearables.size() < MAX_WEARABLES && stack.getItem() instanceof ItemSkull) {
            if (!worldObj.isRemote) {
                ItemStack skullStack = stack.splitStack(1);
                initSkullItem(skullStack);
                wearables.add(skullStack);
                syncWearablesToClient();
            } else {
                player.swingItem();
            }
            return true;
        } else {
            return super.interact(player);
        }
    }

    private void initSkullItem(ItemStack skullStack) {
        if (skullStack.hasTagCompound()) {
            NBTTagCompound nbttagcompound = skullStack.getTagCompound();
            GameProfile gameProfile = null;

            if (nbttagcompound.hasKey("SkullOwner", NBT.TAG_COMPOUND)) {
                gameProfile = NBTUtil.func_152459_a(nbttagcompound.getCompoundTag("SkullOwner"));
            } else if (nbttagcompound.hasKey("SkullOwner", NBT.TAG_STRING)
                    && !StringUtils.isNullOrEmpty(nbttagcompound.getString("SkullOwner"))) {
                gameProfile = new GameProfile(null, nbttagcompound.getString("SkullOwner"));
            }
            if (gameProfile != null && !StringUtils.isNullOrEmpty(gameProfile.getName())) {
                if (!gameProfile.isComplete() || !gameProfile.getProperties().containsKey("textures")) {
                    gameProfile = MinecraftServer.getServer().func_152358_ax().func_152655_a(gameProfile.getName());

                    if (gameProfile != null) {
                        Property property = (Property) Iterables
                                .getFirst(gameProfile.getProperties().get("textures"), (Object) null);

                        if (property == null) {
                            gameProfile = MinecraftServer.getServer().func_147130_as()
                                    .fillProfileProperties(gameProfile, true);
                        }
                    }
                }
            }
            if (gameProfile != null && gameProfile.isComplete()
                    && gameProfile.getProperties().containsKey("textures")) {
                NBTTagCompound profileNBT = new NBTTagCompound();
                NBTUtil.func_152460_a(profileNBT, gameProfile);
                nbttagcompound.setTag("SkullOwner", profileNBT);
            } else {
                nbttagcompound.removeTag("SkullOwner");
            }
        }
    }

    private void syncWearablesToClient() {
        BuildCraftCore.instance.sendToEntity(new PacketCommand(this, "syncWearables", new CommandWriter() {
            public void write(ByteBuf data) {
                data.writeByte(wearables.size());
                for (ItemStack s : wearables) {
                    NetworkUtils.writeStack(data, s);
                }
            }
        }), this);
    }

    private List<ItemStack> getDrops() {
        List<ItemStack> drops = new ArrayList<ItemStack>();
        drops.add(ItemRobot.createRobotStack(board.getNBTHandler(), battery.getEnergyStored()));
        if (itemInUse != null) {
            drops.add(itemInUse);
        }
        for (ItemStack element : inv) {
            if (element != null) {
                drops.add(element);
            }
        }
        drops.addAll(wearables);
        return drops;
    }

    private void convertToItems() {
        if (!worldObj.isRemote && !isDead) {
            if (mainAI != null) {
                mainAI.abort();
            }
            List<ItemStack> drops = getDrops();
            for (ItemStack stack : drops) {
                entityDropItem(stack, 0);
            }
            isDead = true;
        }

        getRegistry().killRobot(this);
    }

    @Override
    public void setDead() {
        if (worldObj.isRemote) {
            super.setDead();
        }
    }

    @Override
    public void onChunkUnload() {
        getRegistry().unloadRobot(this);
    }

    @Override
    public boolean canBePushed() {
        return false;
    }

    @Override
    protected void collideWithEntity(Entity par1Entity) {

    }

    @Override
    public void applyEntityCollision(Entity par1Entity) {

    }

    public void setUniqueRobotId(long iRobotId) {
        robotId = iRobotId;
    }

    @Override
    public long getRobotId() {
        return robotId;
    }

    @Override
    public RobotRegistry getRegistry() {
        return (RobotRegistry) RobotManager.registryProvider.getRegistry(worldObj);
    }

    @Override
    public void releaseResources() {
        getRegistry().releaseResources(this);
    }

    /**
     * Tries to receive items in parameters, return items that are left after
     * the operation.
     */
    @Override
    public ItemStack receiveItem(TileEntity tile, ItemStack stack) {
        if (currentDockingStation != null && currentDockingStation.index().nextTo(new BlockIndex(tile))
                && mainAI != null) {

            return mainAI.getActiveAI().receiveItem(stack);
        } else {
            return stack;
        }
    }

    @Override
    public int fill(ForgeDirection from, FluidStack resource, boolean doFill) {
        int result;

        if (tank != null && !tank.isFluidEqual(resource)) {
            return 0;
        }

        if (tank == null) {
            tank = new FluidStack(resource.getFluid(), 0);
        }

        if (tank.amount + resource.amount <= maxFluid) {
            result = resource.amount;

            if (doFill) {
                tank.amount += resource.amount;
            }
        } else {
            result = maxFluid - tank.amount;

            if (doFill) {
                tank.amount = maxFluid;
            }
        }

        if (tank != null && tank.amount == 0) {
            tank = null;
        }

        return result;
    }

    @Override
    public FluidStack drain(ForgeDirection from, FluidStack resource, boolean doDrain) {
        if (tank != null && tank.isFluidEqual(resource)) {
            return drain(from, resource.amount, doDrain);
        } else {
            return null;
        }
    }

    @Override
    public FluidStack drain(ForgeDirection from, int maxDrain, boolean doDrain) {
        FluidStack result;

        if (tank == null) {
            result = null;
        } else if (tank.amount <= maxDrain) {
            result = tank.copy();

            if (doDrain) {
                tank = null;
            }
        } else {
            result = tank.copy();
            result.amount = maxDrain;

            if (doDrain) {
                tank.amount -= maxDrain;
            }
        }

        if (tank != null && tank.amount == 0) {
            tank = null;
        }

        return result;
    }

    @Override
    public boolean canFill(ForgeDirection from, Fluid fluid) {
        return tank == null || tank.amount == 0
                || (tank.amount < maxFluid && tank.getFluid().getID() == fluid.getID());
    }

    @Override
    public boolean canDrain(ForgeDirection from, Fluid fluid) {
        return tank != null && tank.amount != 0 && tank.getFluid().getID() == fluid.getID();
    }

    @Override
    public FluidTankInfo[] getTankInfo(ForgeDirection from) {
        return new FluidTankInfo[] { new FluidTankInfo(tank, maxFluid) };
    }

    @SideOnly(Side.CLIENT)
    public IIcon getItemIcon(ItemStack stack, int renderPass) {
        IIcon iicon = super.getItemIcon(stack, renderPass);

        if (iicon == null) {
            iicon = stack.getItem().getIcon(stack, renderPass, null, itemInUse, 0);
        }

        return iicon;
    }

    @Override
    public void getDebugInfo(List<String> info, ForgeDirection side, ItemStack debugger, EntityPlayer player) {
        info.add("Robot " + board.getNBTHandler().getID() + " (" + getBattery().getEnergyStored() + "/"
                + getBattery().getMaxEnergyStored() + " RF)");
        info.add(String.format("Position: %.2f, %.2f, %.2f", posX, posY, posZ));
        info.add("AI tree:");
        AIRobot aiRobot = mainAI;
        while (aiRobot != null) {
            info.add("- " + RobotManager.getAIRobotName(aiRobot.getClass()) + " (" + aiRobot.getEnergyCost()
                    + " RF/t)");
            if (aiRobot instanceof IDebuggable) {
                ((IDebuggable) aiRobot).getDebugInfo(info, side, debugger, player);
            }
            aiRobot = aiRobot.getDelegateAI();
        }
    }

    public int receiveEnergy(int maxReceive, boolean simulate) {
        int energyReceived = getBattery().receiveEnergy(maxReceive, simulate);

        // 5 RF/t is set as the "sleep threshold" for detecting charging.
        if (!simulate && energyReceived > 5 && ticksCharging <= 25) {
            ticksCharging += 5;
        }

        return energyReceived;
    }

    public List<ItemStack> getWearables() {
        return wearables;
    }

    private void updateItem(ItemStack stack, int i, boolean held) {
        if (stack != null && stack.getItem() != null) {
            int id = Item.getIdFromItem(stack.getItem());
            // did this item not throw an exception before?
            if (!blacklistedItemsForUpdate.contains(id)) {
                try {
                    stack.getItem().onUpdate(stack, worldObj, this, i, held);
                } catch (Exception e) {
                    // the item threw an exception, print it and do not let it update once more
                    e.printStackTrace();
                    blacklistedItemsForUpdate.add(id);
                }
            }
        }
    }
}