package com.amadornes.framez.tile;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList;
import net.minecraft.nbt.NBTTagString;
import net.minecraft.network.NetworkManager;
import net.minecraft.network.Packet;
import net.minecraft.network.play.server.S35PacketUpdateTileEntity;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.ChatComponentText;
import net.minecraft.world.World;
import net.minecraftforge.common.util.ForgeDirection;
import uk.co.qmunity.lib.helper.RedstoneHelper;
import uk.co.qmunity.lib.misc.Pair;
import uk.co.qmunity.lib.vec.IWorldLocation;
import uk.co.qmunity.lib.vec.Vec3i;
import com.amadornes.framez.api.IDebuggable;
import com.amadornes.framez.api.Priority;
import com.amadornes.framez.api.Priority.PriorityEnum;
import com.amadornes.framez.api.modifier.IMotorModifier;
import com.amadornes.framez.api.movement.BlockMovementType;
import com.amadornes.framez.api.movement.IMotor;
import com.amadornes.framez.api.movement.IMovable;
import com.amadornes.framez.api.movement.IMovement;
import com.amadornes.framez.api.movement.IMovingBlock;
import com.amadornes.framez.api.movement.MotorSetting;
import com.amadornes.framez.movement.MovementHelper;
import com.amadornes.framez.movement.MovementScheduler;
import com.amadornes.framez.movement.MovingBlock;
import com.amadornes.framez.movement.MovingStructure;
import com.amadornes.framez.network.NetworkHandler;
import com.amadornes.framez.network.PacketMotorSetting;
import com.amadornes.framez.util.MotorCache;
import com.amadornes.framez.util.ThreadBlockChecking;
import com.amadornes.framez.util.Timing;
import cpw.mods.fml.common.gameevent.TickEvent.Phase;
public abstract class TileMotor extends TileEntity implements IMotor, IDebuggable, IMovable {
protected MovingStructure structure;
protected List<Vec3i> blocking = new ArrayList<Vec3i>();
private boolean redstoneInput = false;
private boolean scheduled = false;
private ForgeDirection face = ForgeDirection.DOWN;
private HashSet<MotorSetting> settings = new HashSet<MotorSetting>();
private List<IMotorModifier> modifiers = new ArrayList<IMotorModifier>();
private double powerStorageSize = 1000000;
private double storedPower = 0;
@Override
public World getWorld() {
return getWorldObj();
}
@Override
public int getX() {
return xCoord;
}
@Override
public int getY() {
return yCoord;
}
@Override
public int getZ() {
return zCoord;
}
@Override
public Collection<IMotorModifier> getModifiers() {
if (modifiers == null)
modifiers = new ArrayList<IMotorModifier>();
return modifiers;
}
@Override
public boolean canWork() {
return true;
}
@Override
public boolean isWorking() {
return structure != null && structure.getProgress() < 1;
}
@Override
public boolean move() {
try {
if (getWorld() == null || getWorld().isRemote)
return false;
IMovement movement = getMovement();
if (movement == null)
return false;
ForgeDirection face = getFace();
Pair<List<MovingBlock>, List<Vec3i>> p = MovementHelper.findMovedBlocks(getWorld(), getX() + face.offsetX, getY()
+ face.offsetY, getZ() + face.offsetZ, face.getOpposite(), movement);
List<MovingBlock> blocks = p.getKey();
blocks.remove(new MovingBlock(new Vec3i((IWorldLocation) this), null, null));
if (blocks.size() == 0)
return false;
List<Vec3i> old = blocking;
blocking = p.getValue();
boolean send = false;
if ((old.size() != blocking.size())) {
send = true;
} else {
for (Vec3i v : old) {
if (!blocking.contains(v)) {
send = true;
break;
}
}
if (!send) {
for (Vec3i v : blocking) {
if (!old.contains(v)) {
send = true;
break;
}
}
}
}
double totalConsumed = 0;
for (MovingBlock b : blocks) {
b.snapshot();
int parts = b.getMultiparts();
if (parts <= b.getMaxMultiparts()) {
totalConsumed += parts * 10;
} else {
if (!blocking.contains(new Vec3i(b))) {
blocking.add(new Vec3i(b));
send = true;
}
}
totalConsumed += 100;
if (b.getTileEntity() != null)
totalConsumed += 200;
}
if (send)
sendUpdatePacket();
if (blocking.size() > 0)
return false;
double actuallyConsumed = drainPower(totalConsumed, true);
if (actuallyConsumed < totalConsumed)
return false;
drainPower(actuallyConsumed, false);
Timing.SECONDS = 1;
structure = new MovingStructure(this, movement.clone(), 1 / (20 * Timing.SECONDS), blocks);
// structure.tick(Phase.START);
// structure.tick(Phase.END);
MovementScheduler.instance().addStructure(structure);
return true;
} catch (Exception ex) {
ex.printStackTrace();
}
return false;
}
@Override
public Set<MotorSetting> getSettings() {
return settings;
}
@Override
public void configure(MotorSetting setting) {
if (getWorldObj().isRemote) {
NetworkHandler.instance().sendToServer(new PacketMotorSetting(this, setting));
return;
}
if (settings.contains(setting)) {
settings.remove(setting);
} else {
for (MotorSetting s : setting.related)
settings.remove(s);
settings.add(setting);
}
sendUpdatePacket();
onBlockUpdate();
}
private boolean firstTick = true;
@Override
public void updateEntity() {
if (firstTick) {
onFirstTick();
firstTick = false;
}
tick();
}
@Override
public void tick() {
if (structure != null && structure.getProgress() >= 1)
structure = null;
if (scheduled) {
if (!isWorking() && canWork())
if (!move() && !getSettings().contains(MotorSetting.REDSTONE_PULSE))
ThreadBlockChecking.instance().addMotor(this);
scheduled = false;
}
}
@Override
public void onFirstTick() {
}
public void onBlockUpdate() {
boolean lastInput = redstoneInput;
redstoneInput = RedstoneHelper.getInput(getWorld(), getX(), getY(), getZ()) > 0;
if ((lastInput != redstoneInput || !getSettings().contains(MotorSetting.REDSTONE_PULSE))
&& (getSettings().contains(MotorSetting.REDSTONE_INVERTED) ? !redstoneInput : redstoneInput)) {
scheduled = true;
} else {
ThreadBlockChecking.instance().removeMotor(this);
}
}
@Override
public ForgeDirection getFace() {
return face;
}
public boolean setFace(ForgeDirection face) {
this.face = face;
sendUpdatePacket();
return true;
}
public boolean rotate(ForgeDirection axis) {
if (getMovement().rotate(this, axis)) {
sendUpdatePacket();
return true;
}
return true;
}
@Override
public boolean debug(World world, int x, int y, int z, ForgeDirection face, EntityPlayer player) {
if (!world.isRemote)
return true;
player.addChatMessage(new ChatComponentText("Power: " + getEnergyBuffer() + "/" + getEnergyBufferSize()));
player.addChatMessage(new ChatComponentText("Face: " + getFace().name().toLowerCase()));
getMovement().debug(world, x, y, z, face, player);
return true;
}
public MovingStructure getStructure() {
return structure;
}
public void setStructure(MovingStructure structure) {
this.structure = structure;
}
@Override
public double getEnergyBufferSize() {
return powerStorageSize;
}
@Override
public double getEnergyBuffer() {
return storedPower;
}
@Override
public double injectPower(double amount, boolean simulated) {
double injected = Math.min(getEnergyBufferSize() - getEnergyBuffer(), amount);
if (simulated)
return injected;
storedPower += injected;
sendUpdatePacket();
return injected;
}
@Override
public double drainPower(double amount, boolean simulated) {
double drained = Math.min(getEnergyBuffer(), amount);
if (simulated)
return drained;
storedPower -= drained;
sendUpdatePacket();
return drained;
}
// NBT saving and tile updates
@Override
public void writeToNBT(NBTTagCompound tag) {
super.writeToNBT(tag);
tag.setBoolean("redstoneInput", redstoneInput);
tag.setInteger("face", getFace().ordinal());
NBTTagCompound movement = new NBTTagCompound();
getMovement().writeToNBT(movement);
tag.setTag("movement", movement);
NBTTagList l = new NBTTagList();
for (MotorSetting s : settings)
l.appendTag(new NBTTagString(s.ordinal() + ""));
tag.setTag("settings", l);
tag.setDouble("power", getEnergyBuffer());
}
@Override
public void readFromNBT(NBTTagCompound tag) {
super.readFromNBT(tag);
redstoneInput = tag.getBoolean("redstoneInput");
face = ForgeDirection.getOrientation(tag.getInteger("face"));
getMovement().readFromNBT(tag.getCompoundTag("movement"));
settings.clear();
NBTTagList l = tag.getTagList("settings", new NBTTagString().getId());
for (int i = 0; i < l.tagCount(); i++)
settings.add(MotorSetting.values()[Integer.parseInt(l.getStringTagAt(i))]);
storedPower = tag.getDouble("power");
}
protected void writeToPacketNBT(NBTTagCompound tag) {
tag.setInteger("face", getFace().ordinal());
NBTTagCompound movement = new NBTTagCompound();
getMovement().writeToNBT(movement);
tag.setTag("movement", movement);
NBTTagList l = new NBTTagList();
for (MotorSetting s : settings)
l.appendTag(new NBTTagString(s.ordinal() + ""));
tag.setTag("settings", l);
if (blocking.size() > 0) {
NBTTagList blocking = new NBTTagList();
for (Vec3i v : this.blocking) {
NBTTagCompound t = new NBTTagCompound();
t.setInteger("x", v.getX());
t.setInteger("y", v.getY());
t.setInteger("z", v.getZ());
blocking.appendTag(t);
}
tag.setTag("blocking", blocking);
}
tag.setDouble("power", getEnergyBuffer());
}
protected void readFromPacketNBT(NBTTagCompound tag) {
face = ForgeDirection.getOrientation(tag.getInteger("face"));
getMovement().readFromNBT(tag.getCompoundTag("movement"));
settings.clear();
NBTTagList l = tag.getTagList("settings", new NBTTagString().getId());
for (int i = 0; i < l.tagCount(); i++)
settings.add(MotorSetting.values()[Integer.parseInt(l.getStringTagAt(i))]);
blocking = new ArrayList<Vec3i>();
if (tag.hasKey("blocking")) {
NBTTagList blocking = tag.getTagList("blocking", new NBTTagCompound().getId());
for (int i = 0; i < blocking.tagCount(); i++) {
NBTTagCompound t = blocking.getCompoundTagAt(i);
this.blocking.add(new Vec3i(t.getInteger("x"), t.getInteger("y"), t.getInteger("z")));
}
}
storedPower = tag.getDouble("power");
markForRenderUpdate();
}
@Override
public Packet getDescriptionPacket() {
NBTTagCompound tCompound = new NBTTagCompound();
writeToPacketNBT(tCompound);
return new S35PacketUpdateTileEntity(xCoord, yCoord, zCoord, 0, tCompound);
}
@Override
public void onDataPacket(NetworkManager net, S35PacketUpdateTileEntity pkt) {
readFromPacketNBT(pkt.func_148857_g());
}
protected void sendUpdatePacket() {
if (!worldObj.isRemote)
worldObj.markBlockForUpdate(xCoord, yCoord, zCoord);
}
protected void markForRenderUpdate() {
if (worldObj != null)
worldObj.markBlockRangeForRenderUpdate(xCoord, yCoord, zCoord, xCoord, yCoord, zCoord);
}
protected void notifyNeighborBlockUpdate() {
worldObj.notifyBlocksOfNeighborChange(xCoord, yCoord, zCoord, getBlockType());
}
@Override
@Priority(PriorityEnum.OVERRIDE)
public BlockMovementType getMovementType(World world, int x, int y, int z, ForgeDirection side, IMovement movement) {
if (side == getFace())
return BlockMovementType.UNMOVABLE;
return !isWorking() ? BlockMovementType.MOVABLE : BlockMovementType.UNMOVABLE;
}
@Override
public boolean startMoving(IMovingBlock block) {
return false;
}
@Override
public boolean finishMoving(IMovingBlock block) {
return false;
}
@Override
public void onUnload() {
MovingStructure s = getStructure();
if (s != null)
while (s.getProgress() < 1) {
s.tick(Phase.START);
s.tick(Phase.END);
}
}
public List<Vec3i> getBlocking() {
return blocking;
}
@Override
public void validate() {
super.validate();
MotorCache.onLoad(this);
}
@Override
public void invalidate() {
super.invalidate();
MotorCache.onUnload(this);
onUnload();
}
}