package openblocks.common.entity;
import com.google.common.collect.ImmutableSet;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Random;
import java.util.Set;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.MathHelper;
import net.minecraft.world.World;
import openblocks.OpenBlocks.Items;
import openblocks.client.renderer.entity.EntitySelectionHandler.ISelectAware;
import openblocks.common.MapDataBuilder;
import openblocks.common.MapDataBuilder.ChunkJob;
import openblocks.common.item.ItemCartographer;
import openblocks.common.item.ItemEmptyMap;
import openblocks.common.item.ItemHeightMap;
import openmods.Log;
import openmods.api.VisibleForDocumentation;
import openmods.sync.ISyncMapProvider;
import openmods.sync.SyncMap;
import openmods.sync.SyncMapEntity;
import openmods.sync.SyncObjectScanner;
import openmods.sync.SyncableBoolean;
import openmods.sync.SyncableInt;
import openmods.sync.SyncableObjectBase;
import openmods.utils.BitSet;
import openmods.utils.ByteUtils;
import openmods.utils.ItemUtils;
@VisibleForDocumentation
public class EntityCartographer extends EntityAssistant implements ISelectAware, ISyncMapProvider {
private static final int MAP_JOB_DELAY = 5;
private static final int MOVE_DELAY = 35;
public static final Random RANDOM = new Random();
@SideOnly(Side.CLIENT)
public float eyeYaw, eyePitch, targetYaw, targetPitch;
public static class MapJobs extends SyncableObjectBase {
private BitSet bits = new BitSet();
private Set<ChunkJob> jobs;
private int size;
public boolean test(int bit) {
return bits.testBit(bit);
}
@Override
public void readFromStream(DataInputStream input) throws IOException {
size = ByteUtils.readVLI(input);
bits.readFromStream(input);
}
@Override
public void writeToStream(DataOutputStream output) throws IOException {
ByteUtils.writeVLI(output, size);
bits.writeToStream(output);
}
@Override
public void writeToNBT(NBTTagCompound tag, String name) {
NBTTagCompound result = new NBTTagCompound();
bits.writeToNBT(result);
tag.setTag(name, result);
}
@Override
public void readFromNBT(NBTTagCompound tag, String name) {
NBTTagCompound info = tag.getCompoundTag(name);
bits.readFromNBT(info);
}
public void runJob(World world, int x, int z) {
if (jobs == null) {
Log.severe("STOP ABUSING CARTOGRAPHER RIGHT NOW! YOU BROKE IT!");
jobs = ImmutableSet.of();
}
ChunkJob job = MapDataBuilder.doNextChunk(world, x, z, jobs);
if (job != null) {
jobs.remove(job);
bits.setBit(job.bitNum);
markDirty();
}
}
public void resumeMapping(World world, int mapId) {
MapDataBuilder builder = new MapDataBuilder(mapId);
builder.loadMap(world);
builder.resizeIfNeeded(bits); // better to lost progress than to break world
size = builder.size();
jobs = builder.createJobs(bits);
markDirty();
}
public void startMapping(World world, int mapId, int x, int z) {
MapDataBuilder builder = new MapDataBuilder(mapId);
builder.resetMap(world, x, z);
builder.resize(bits);
size = builder.size();
jobs = builder.createJobs(bits);
markDirty();
}
public void stopMapping() {
jobs.clear();
bits.resize(0);
size = 0;
markDirty();
}
public int size() {
return size;
}
}
public final SyncableInt scale = new SyncableInt(2);
public final SyncableBoolean isMapping = new SyncableBoolean(false);
public final MapJobs jobs = new MapJobs();
private ItemStack mapItem;
private int mappingDimension;
private int countdownToAction = MAP_JOB_DELAY;
private int countdownToMove = MOVE_DELAY;
private float randomDelta;
private final SyncMapEntity<EntityCartographer> syncMap = new SyncMapEntity<EntityCartographer>(this);
{
SyncObjectScanner.INSTANCE.registerAllFields(syncMap, this);
setSize(0.2f, 0.2f);
}
public EntityCartographer(World world) {
super(world, null);
}
public EntityCartographer(World world, EntityPlayer owner, ItemStack stack) {
super(world, owner);
setSpawnPosition(owner);
NBTTagCompound tag = ItemUtils.getItemTag(stack);
readOwnDataFromNBT(tag);
}
@Override
protected void entityInit() {}
@Override
public void onUpdate() {
if (!worldObj.isRemote) {
float yaw = 0;
if (isMapping.get()) {
if (countdownToMove-- <= 0) {
countdownToMove = MOVE_DELAY;
randomDelta = 2 * (float)Math.PI * RANDOM.nextFloat();
}
yaw = randomDelta;
} else {
EntityPlayer owner = findOwner();
if (owner != null) yaw = (float)Math.toRadians(owner.rotationYaw);
}
ownerOffsetX = MathHelper.sin(-yaw);
ownerOffsetZ = MathHelper.cos(-yaw);
}
super.onUpdate();
if (!worldObj.isRemote) {
if (worldObj.provider.dimensionId == mappingDimension && isMapping.get() && countdownToAction-- <= 0) {
jobs.runJob(worldObj, (int)posX, (int)posZ);
countdownToAction = MAP_JOB_DELAY;
}
syncMap.sync();
}
}
@Override
protected void readEntityFromNBT(NBTTagCompound tag) {
super.readEntityFromNBT(tag);
readOwnDataFromNBT(tag);
}
private void readOwnDataFromNBT(NBTTagCompound tag) {
syncMap.readFromNBT(tag);
if (tag.hasKey("MapItem")) {
NBTTagCompound mapItem = tag.getCompoundTag("MapItem");
this.mapItem = ItemUtils.readStack(mapItem);
if (this.mapItem != null && isMapping.get()) {
int mapId = this.mapItem.getItemDamage();
jobs.resumeMapping(worldObj, mapId);
}
mappingDimension = tag.getInteger("Dimension");
}
}
@Override
protected void writeEntityToNBT(NBTTagCompound tag) {
super.writeEntityToNBT(tag);
writeOwnDataToNBT(tag);
}
private void writeOwnDataToNBT(NBTTagCompound tag) {
syncMap.writeToNBT(tag);
if (mapItem != null) {
NBTTagCompound mapItem = ItemUtils.writeStack(this.mapItem);
tag.setTag("MapItem", mapItem);
tag.setInteger("Dimension", mappingDimension);
}
}
@Override
public ItemStack toItemStack() {
ItemStack result = Items.cartographer.createStack(ItemCartographer.AssistantType.CARTOGRAPHER);
NBTTagCompound tag = ItemUtils.getItemTag(result);
writeOwnDataToNBT(tag);
return result;
}
@Override
public boolean interactFirst(EntityPlayer player) {
if (player instanceof EntityPlayerMP && player.isSneaking() && getDistanceToEntity(player) < 3) {
ItemStack holding = player.getHeldItem();
if (holding == null && mapItem != null) {
player.setCurrentItemOrArmor(0, mapItem);
mapItem = null;
isMapping.toggle();
jobs.stopMapping();
} else if (holding != null && mapItem == null) {
Item itemType = holding.getItem();
if (itemType instanceof ItemHeightMap || itemType instanceof ItemEmptyMap) {
ItemStack inserted = holding.splitStack(1);
if (holding.stackSize <= 0) player.setCurrentItemOrArmor(0, null);
mapItem = inserted;
mappingDimension = worldObj.provider.dimensionId;
isMapping.toggle();
mapItem = MapDataBuilder.upgradeToMap(worldObj, mapItem);
int mapId = mapItem.getItemDamage();
jobs.startMapping(worldObj, mapId, getNewMapCenterX(), getNewMapCenterZ());
}
}
return true;
}
return false;
}
@Override
@SideOnly(Side.CLIENT)
public boolean canRenderOnFire() {
return false;
}
@Override
public boolean canBeCollidedWith() {
return true;
}
@Override
public SyncMap<EntityCartographer> getSyncMap() {
return syncMap;
}
public int getNewMapCenterX() {
return ((int)posX) & ~0x0F;
}
public int getNewMapCenterZ() {
return ((int)posZ) & ~0x0F;
}
@SideOnly(Side.CLIENT)
public void updateEye() {
float diffYaw = (targetYaw - eyeYaw) % (float)Math.PI;
float diffPitch = (targetPitch - eyePitch) % (float)Math.PI;
if (Math.abs(diffYaw) + Math.abs(diffPitch) < 0.0001) {
targetPitch = RANDOM.nextFloat() * 2 * (float)Math.PI;
targetYaw = RANDOM.nextFloat() * 2 * (float)Math.PI;
} else {
// No, it's not supposed to be correct
eyeYaw = eyeYaw - diffYaw / 50.0f; // HERP
eyePitch = eyePitch - diffPitch / 50.0f; // DERP
}
}
}