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; } }