package mekanism.common.tile;
import io.netty.buffer.ByteBuf;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import mekanism.api.Chunk3D;
import mekanism.api.Coord4D;
import mekanism.api.ObfuscatedNames;
import mekanism.api.Range4D;
import mekanism.api.util.ReflectionUtils;
import mekanism.common.Mekanism;
import mekanism.common.MekanismBlocks;
import mekanism.common.PacketHandler;
import mekanism.common.Upgrade;
import mekanism.common.base.IRedstoneControl;
import mekanism.common.base.IUpgradeTile;
import mekanism.common.block.states.BlockStateMachine;
import mekanism.common.chunkloading.IChunkLoader;
import mekanism.common.frequency.Frequency;
import mekanism.common.frequency.FrequencyManager;
import mekanism.common.frequency.IFrequencyHandler;
import mekanism.common.integration.IComputerIntegration;
import mekanism.common.network.PacketEntityMove.EntityMoveMessage;
import mekanism.common.network.PacketPortalFX.PortalFXMessage;
import mekanism.common.network.PacketTileEntity.TileEntityMessage;
import mekanism.common.security.ISecurityTile;
import mekanism.common.tile.component.TileComponentChunkLoader;
import mekanism.common.tile.component.TileComponentSecurity;
import mekanism.common.tile.component.TileComponentUpgrade;
import mekanism.common.util.ChargeUtils;
import mekanism.common.util.MekanismUtils;
import net.minecraft.block.state.IBlockState;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityList;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.init.SoundEvents;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.network.play.server.SPacketEntityEffect;
import net.minecraft.network.play.server.SPacketRespawn;
import net.minecraft.network.play.server.SPacketSetExperience;
import net.minecraft.potion.PotionEffect;
import net.minecraft.server.MinecraftServer;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.ChunkPos;
import net.minecraft.world.World;
import net.minecraft.world.WorldServer;
import net.minecraftforge.fml.common.FMLCommonHandler;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
public class TileEntityTeleporter extends TileEntityElectricBlock implements IComputerIntegration, IChunkLoader, IFrequencyHandler, IRedstoneControl, ISecurityTile, IUpgradeTile
{
private MinecraftServer server = FMLCommonHandler.instance().getMinecraftServerInstance();
public AxisAlignedBB teleportBounds = null;
public Set<UUID> didTeleport = new HashSet<UUID>();
public int teleDelay = 0;
public boolean shouldRender;
public boolean prevShouldRender;
public Frequency frequency;
public List<Frequency> publicCache = new ArrayList<Frequency>();
public List<Frequency> privateCache = new ArrayList<Frequency>();
/** This teleporter's current status. */
public byte status = 0;
public RedstoneControl controlType = RedstoneControl.DISABLED;
public TileComponentSecurity securityComponent;
public TileComponentChunkLoader chunkLoaderComponent;
public TileComponentUpgrade upgradeComponent;
public TileEntityTeleporter()
{
super("Teleporter", BlockStateMachine.MachineType.TELEPORTER.baseEnergy);
inventory = new ItemStack[2];
securityComponent = new TileComponentSecurity(this);
chunkLoaderComponent = new TileComponentChunkLoader(this);
upgradeComponent = new TileComponentUpgrade(this, 1);
upgradeComponent.clearSupportedTypes();
upgradeComponent.setSupported(Upgrade.ANCHOR);
}
@Override
public void onUpdate()
{
super.onUpdate();
if(teleportBounds == null)
{
resetBounds();
}
if(!worldObj.isRemote)
{
FrequencyManager manager = getManager(frequency);
if(manager != null)
{
if(frequency != null && !frequency.valid)
{
frequency = manager.validateFrequency(getSecurity().getOwner(), Coord4D.get(this), frequency);
}
if(frequency != null)
{
frequency = manager.update(getSecurity().getOwner(), Coord4D.get(this), frequency);
}
}
else {
frequency = null;
}
status = canTeleport();
if(MekanismUtils.canFunction(this) && status == 1 && teleDelay == 0)
{
teleport();
}
if(teleDelay == 0 && didTeleport.size() > 0)
{
cleanTeleportCache();
}
shouldRender = status == 1 || status > 4;
if(shouldRender != prevShouldRender)
{
Mekanism.packetHandler.sendToAllAround(new TileEntityMessage(Coord4D.get(this), getNetworkedData(new ArrayList())), Coord4D.get(this).getTargetPoint(40D));
}
prevShouldRender = shouldRender;
teleDelay = Math.max(0, teleDelay-1);
}
ChargeUtils.discharge(0, this);
}
@Override
public Frequency getFrequency(FrequencyManager manager)
{
if(manager == Mekanism.securityFrequencies)
{
return getSecurity().getFrequency();
}
return frequency;
}
public Coord4D getClosest()
{
if(frequency != null)
{
return frequency.getClosestCoords(Coord4D.get(this));
}
return null;
}
public void setFrequency(String name, boolean publicFreq)
{
FrequencyManager manager = getManager(new Frequency(name, null).setPublic(publicFreq));
manager.deactivate(Coord4D.get(this));
for(Frequency freq : manager.getFrequencies())
{
if(freq.name.equals(name))
{
frequency = freq;
frequency.activeCoords.add(Coord4D.get(this));
return;
}
}
Frequency freq = new Frequency(name, getSecurity().getOwner()).setPublic(publicFreq);
freq.activeCoords.add(Coord4D.get(this));
manager.addFrequency(freq);
frequency = freq;
MekanismUtils.saveChunk(this);
}
public FrequencyManager getManager(Frequency freq)
{
if(getSecurity().getOwner() == null || freq == null)
{
return null;
}
if(freq.isPublic())
{
return Mekanism.publicTeleporters;
}
else {
if(!Mekanism.privateTeleporters.containsKey(getSecurity().getOwner()))
{
FrequencyManager manager = new FrequencyManager(Frequency.class, Frequency.TELEPORTER, getSecurity().getOwner());
Mekanism.privateTeleporters.put(getSecurity().getOwner(), manager);
manager.createOrLoad(worldObj);
}
return Mekanism.privateTeleporters.get(getSecurity().getOwner());
}
}
public static FrequencyManager loadManager(String owner, World world)
{
if(Mekanism.privateTeleporters.containsKey(owner))
{
return Mekanism.privateTeleporters.get(owner);
}
return FrequencyManager.loadOnly(world, owner, Frequency.class, "Teleporter");
}
@Override
public void onChunkUnload()
{
super.onChunkUnload();
if(!worldObj.isRemote && frequency != null)
{
FrequencyManager manager = getManager(frequency);
if(manager != null)
{
manager.deactivate(Coord4D.get(this));
}
}
}
@Override
public void invalidate()
{
super.invalidate();
if(!worldObj.isRemote)
{
if(frequency != null)
{
FrequencyManager manager = getManager(frequency);
if(manager != null)
{
manager.deactivate(Coord4D.get(this));
}
}
}
}
public void cleanTeleportCache()
{
List<UUID> list = new ArrayList<UUID>();
for(Entity e : (List<Entity>)worldObj.getEntitiesWithinAABB(Entity.class, teleportBounds))
{
list.add(e.getPersistentID());
}
Set<UUID> teleportCopy = (Set<UUID>)((HashSet<UUID>)didTeleport).clone();
for(UUID id : teleportCopy)
{
if(!list.contains(id))
{
didTeleport.remove(id);
}
}
}
@Override
public int[] getSlotsForFace(EnumFacing side)
{
return new int[] {0};
}
@Override
public boolean isItemValidForSlot(int slotID, ItemStack itemstack)
{
if(slotID == 0)
{
return ChargeUtils.canBeDischarged(itemstack);
}
return true;
}
public void resetBounds()
{
teleportBounds = new AxisAlignedBB(getPos(), getPos().add(1, 3, 1));
}
/**
* 1: yes
* 2: no frame
* 3: no link found
* 4: not enough electricity
* @return
*/
public byte canTeleport()
{
if(!hasFrame())
{
return 2;
}
if(getClosest() == null)
{
return 3;
}
List<Entity> entitiesInPortal = getToTeleport();
Coord4D closestCoords = getClosest();
int electricityNeeded = 0;
for(Entity entity : entitiesInPortal)
{
electricityNeeded += calculateEnergyCost(entity, closestCoords);
}
if(getEnergy() < electricityNeeded)
{
return 4;
}
return 1;
}
public void teleport()
{
if(worldObj.isRemote) return;
List<Entity> entitiesInPortal = getToTeleport();
Coord4D closestCoords = getClosest();
if(closestCoords == null)
{
return;
}
for(Entity entity : entitiesInPortal)
{
World teleWorld = FMLCommonHandler.instance().getMinecraftServerInstance().worldServerForDimension(closestCoords.dimensionId);
TileEntityTeleporter teleporter = (TileEntityTeleporter)closestCoords.getTileEntity(teleWorld);
if(teleporter != null)
{
teleporter.didTeleport.add(entity.getPersistentID());
teleporter.teleDelay = 5;
if(entity instanceof EntityPlayerMP)
{
teleportPlayerTo((EntityPlayerMP)entity, closestCoords, teleporter);
alignPlayer((EntityPlayerMP)entity, closestCoords);
}
else {
teleportEntityTo(entity, closestCoords, teleporter);
}
for(Coord4D coords : frequency.activeCoords)
{
Mekanism.packetHandler.sendToAllAround(new PortalFXMessage(coords), coords.getTargetPoint(40D));
}
setEnergy(getEnergy() - calculateEnergyCost(entity, closestCoords));
worldObj.playSound(entity.posX, entity.posY, entity.posZ, SoundEvents.ENTITY_ENDERMEN_TELEPORT, entity.getSoundCategory(), 1.0F, 1.0F, false);
}
}
}
public static void teleportPlayerTo(EntityPlayerMP player, Coord4D coord, TileEntityTeleporter teleporter)
{
if(player.dimension != coord.dimensionId)
{
int id = player.dimension;
WorldServer oldWorld = player.mcServer.worldServerForDimension(player.dimension);
player.dimension = coord.dimensionId;
WorldServer newWorld = player.mcServer.worldServerForDimension(player.dimension);
player.connection.sendPacket(new SPacketRespawn(player.dimension, player.worldObj.getDifficulty(), newWorld.getWorldInfo().getTerrainType(), player.interactionManager.getGameType()));
oldWorld.removeEntityDangerously(player);
player.isDead = false;
if(player.isEntityAlive())
{
newWorld.spawnEntityInWorld(player);
player.setLocationAndAngles(coord.xCoord+0.5, coord.yCoord+1, coord.zCoord+0.5, player.rotationYaw, player.rotationPitch);
newWorld.updateEntityWithOptionalForce(player, false);
player.setWorld(newWorld);
}
player.mcServer.getPlayerList().preparePlayer(player, oldWorld);
player.connection.setPlayerLocation(coord.xCoord+0.5, coord.yCoord+1, coord.zCoord+0.5, player.rotationYaw, player.rotationPitch);
player.interactionManager.setWorld(newWorld);
player.mcServer.getPlayerList().updateTimeAndWeatherForPlayer(player, newWorld);
player.mcServer.getPlayerList().syncPlayerInventory(player);
for(PotionEffect potioneffect : player.getActivePotionEffects())
{
player.connection.sendPacket(new SPacketEntityEffect(player.getEntityId(), potioneffect));
}
player.connection.sendPacket(new SPacketSetExperience(player.experience, player.experienceTotal, player.experienceLevel)); // Force XP sync
FMLCommonHandler.instance().firePlayerChangedDimensionEvent(player, id, coord.dimensionId);
}
else {
player.connection.setPlayerLocation(coord.xCoord+0.5, coord.yCoord+1, coord.zCoord+0.5, player.rotationYaw, player.rotationPitch);
}
}
public void teleportEntityTo(Entity entity, Coord4D coord, TileEntityTeleporter teleporter)
{
WorldServer world = server.worldServerForDimension(coord.dimensionId);
if(entity.worldObj.provider.getDimension() != coord.dimensionId)
{
entity.worldObj.removeEntity(entity);
entity.isDead = false;
world.spawnEntityInWorld(entity);
entity.setLocationAndAngles(coord.xCoord+0.5, coord.yCoord+1, coord.zCoord+0.5, entity.rotationYaw, entity.rotationPitch);
world.updateEntityWithOptionalForce(entity, false);
entity.setWorld(world);
world.resetUpdateEntityTick();
Entity e = EntityList.createEntityByName(EntityList.getEntityString(entity), world);
if(e != null)
{
try {
Method m = ReflectionUtils.getPrivateMethod(Entity.class, ObfuscatedNames.Entity_copyDataFromOld, Entity.class);
m.invoke(e, entity);
} catch(Exception exception) {
exception.printStackTrace();
}
world.spawnEntityInWorld(e);
teleporter.didTeleport.add(e.getPersistentID());
}
entity.isDead = true;
}
else {
entity.setLocationAndAngles(coord.xCoord+0.5, coord.yCoord+1, coord.zCoord+0.5, entity.rotationYaw, entity.rotationPitch);
Mekanism.packetHandler.sendToReceivers(new EntityMoveMessage(entity), new Range4D(new Coord4D(entity)));
}
}
public static void alignPlayer(EntityPlayerMP player, Coord4D coord)
{
Coord4D upperCoord = coord.offset(EnumFacing.UP);
EnumFacing side = null;
float yaw = player.rotationYaw;
for(EnumFacing iterSide : MekanismUtils.SIDE_DIRS)
{
if(upperCoord.offset(iterSide).isAirBlock(player.worldObj))
{
side = iterSide;
break;
}
}
if(side != null)
{
switch(side)
{
case NORTH:
yaw = 180;
break;
case SOUTH:
yaw = 0;
break;
case WEST:
yaw = 90;
break;
case EAST:
yaw = 270;
break;
default:
break;
}
}
player.connection.setPlayerLocation(player.posX, player.posY, player.posZ, yaw, player.rotationPitch);
}
public List<Entity> getToTeleport()
{
List<Entity> entities = worldObj.getEntitiesWithinAABB(Entity.class, teleportBounds);
List<Entity> ret = new ArrayList<Entity>();
for(Entity entity : entities)
{
if(!didTeleport.contains(entity.getPersistentID()))
{
ret.add(entity);
}
}
return ret;
}
public int calculateEnergyCost(Entity entity, Coord4D coords)
{
int energyCost = 1000;
if(entity.worldObj.provider.getDimension() != coords.dimensionId)
{
energyCost+=10000;
}
int distance = (int)entity.getDistance(coords.xCoord, coords.yCoord, coords.zCoord);
energyCost+=(distance*10);
return energyCost;
}
public boolean hasFrame()
{
if(isFrame(getPos().getX()-1, getPos().getY(), getPos().getZ()) && isFrame(getPos().getX()+1, getPos().getY(), getPos().getZ())
&& isFrame(getPos().getX()-1, getPos().getY()+1, getPos().getZ()) && isFrame(getPos().getX()+1, getPos().getY()+1, getPos().getZ())
&& isFrame(getPos().getX()-1, getPos().getY()+2, getPos().getZ()) && isFrame(getPos().getX()+1, getPos().getY()+2, getPos().getZ())
&& isFrame(getPos().getX()-1, getPos().getY()+3, getPos().getZ()) && isFrame(getPos().getX()+1, getPos().getY()+3, getPos().getZ())
&& isFrame(getPos().getX(), getPos().getY()+3, getPos().getZ())) {return true;}
if(isFrame(getPos().getX(), getPos().getY(), getPos().getZ()-1) && isFrame(getPos().getX(), getPos().getY(), getPos().getZ()+1)
&& isFrame(getPos().getX(), getPos().getY()+1, getPos().getZ()-1) && isFrame(getPos().getX(), getPos().getY()+1, getPos().getZ()+1)
&& isFrame(getPos().getX(), getPos().getY()+2, getPos().getZ()-1) && isFrame(getPos().getX(), getPos().getY()+2, getPos().getZ()+1)
&& isFrame(getPos().getX(), getPos().getY()+3, getPos().getZ()-1) && isFrame(getPos().getX(), getPos().getY()+3, getPos().getZ()+1)
&& isFrame(getPos().getX(), getPos().getY()+3, getPos().getZ())) {return true;}
return false;
}
public boolean isFrame(int x, int y, int z)
{
IBlockState state = worldObj.getBlockState(new BlockPos(x, y, z));
return state.getBlock() == MekanismBlocks.BasicBlock && state.getBlock().getMetaFromState(state) == 7;
}
@Override
public void readFromNBT(NBTTagCompound nbtTags)
{
super.readFromNBT(nbtTags);
controlType = RedstoneControl.values()[nbtTags.getInteger("controlType")];
if(nbtTags.hasKey("frequency"))
{
frequency = new Frequency(nbtTags.getCompoundTag("frequency"));
frequency.valid = false;
}
}
@Override
public NBTTagCompound writeToNBT(NBTTagCompound nbtTags)
{
super.writeToNBT(nbtTags);
nbtTags.setInteger("controlType", controlType.ordinal());
if(frequency != null)
{
NBTTagCompound frequencyTag = new NBTTagCompound();
frequency.write(frequencyTag);
nbtTags.setTag("frequency", frequencyTag);
}
return nbtTags;
}
@Override
public void handlePacketData(ByteBuf dataStream)
{
if(FMLCommonHandler.instance().getEffectiveSide().isServer())
{
int type = dataStream.readInt();
if(type == 0)
{
String name = PacketHandler.readString(dataStream);
boolean isPublic = dataStream.readBoolean();
setFrequency(name, isPublic);
}
else if(type == 1)
{
String freq = PacketHandler.readString(dataStream);
boolean isPublic = dataStream.readBoolean();
FrequencyManager manager = getManager(new Frequency(freq, null).setPublic(isPublic));
if(manager != null)
{
manager.remove(freq, getSecurity().getOwner());
}
}
return;
}
super.handlePacketData(dataStream);
if(FMLCommonHandler.instance().getEffectiveSide().isClient())
{
if(dataStream.readBoolean())
{
frequency = new Frequency(dataStream);
}
else {
frequency = null;
}
status = dataStream.readByte();
shouldRender = dataStream.readBoolean();
controlType = RedstoneControl.values()[dataStream.readInt()];
publicCache.clear();
privateCache.clear();
int amount = dataStream.readInt();
for(int i = 0; i < amount; i++)
{
publicCache.add(new Frequency(dataStream));
}
amount = dataStream.readInt();
for(int i = 0; i < amount; i++)
{
privateCache.add(new Frequency(dataStream));
}
}
}
@Override
public ArrayList<Object> getNetworkedData(ArrayList<Object> data)
{
super.getNetworkedData(data);
if(frequency != null)
{
data.add(true);
frequency.write(data);
}
else {
data.add(false);
}
data.add(status);
data.add(shouldRender);
data.add(controlType.ordinal());
data.add(Mekanism.publicTeleporters.getFrequencies().size());
for(Frequency freq : Mekanism.publicTeleporters.getFrequencies())
{
freq.write(data);
}
FrequencyManager manager = getManager(new Frequency(null, null).setPublic(false));
if(manager != null)
{
data.add(manager.getFrequencies().size());
for(Frequency freq : manager.getFrequencies())
{
freq.write(data);
}
}
else {
data.add(0);
}
return data;
}
@Override
public boolean canExtractItem(int slotID, ItemStack itemstack, EnumFacing side)
{
return ChargeUtils.canBeOutputted(itemstack, false);
}
private static final String[] methods = new String[] {"getEnergy", "canTeleport", "getMaxEnergy", "teleport", "setFrequency"};
@Override
public String[] getMethods()
{
return methods;
}
@Override
public Object[] invoke(int method, Object[] arguments) throws Exception
{
switch(method)
{
case 0:
return new Object[] {getEnergy()};
case 1:
return new Object[] {canTeleport()};
case 2:
return new Object[] {getMaxEnergy()};
case 3:
teleport();
return new Object[] {"Attempted to teleport."};
case 4:
if(!(arguments[0] instanceof String) || !(arguments[1] instanceof Boolean))
{
return new Object[] {"Invalid parameters."};
}
String freq = ((String)arguments[0]).trim();
boolean isPublic = (Boolean)arguments[1];
setFrequency(freq, isPublic);
return new Object[] {"Frequency set."};
default:
throw new NoSuchMethodException();
}
}
@Override
@SideOnly(Side.CLIENT)
public AxisAlignedBB getRenderBoundingBox()
{
return INFINITE_EXTENT_AABB;
}
@Override
public RedstoneControl getControlType()
{
return controlType;
}
@Override
public void setControlType(RedstoneControl type)
{
controlType = type;
}
@Override
public boolean canPulse()
{
return false;
}
@Override
public TileComponentSecurity getSecurity()
{
return securityComponent;
}
@Override
public TileComponentChunkLoader getChunkLoader()
{
return chunkLoaderComponent;
}
@Override
public Set<ChunkPos> getChunkSet()
{
Set<ChunkPos> ret = new HashSet<ChunkPos>();
ret.add(new Chunk3D(Coord4D.get(this)).getPos());
return ret;
}
@Override
public TileComponentUpgrade getComponent()
{
return upgradeComponent;
}
}