package crazypants.enderio.teleport.telepad; import java.util.EnumSet; import java.util.List; import java.util.Queue; import net.minecraft.block.Block; import net.minecraft.entity.Entity; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.network.NetworkManager; import net.minecraft.network.Packet; import net.minecraft.network.play.server.S35PacketUpdateTileEntity; import net.minecraft.server.MinecraftServer; import net.minecraft.tileentity.TileEntity; import net.minecraft.util.AxisAlignedBB; import net.minecraft.util.MathHelper; import net.minecraft.util.ResourceLocation; import net.minecraft.util.Vec3; import net.minecraft.world.Teleporter; import net.minecraft.world.World; import net.minecraft.world.WorldServer; import net.minecraftforge.common.util.ForgeDirection; import cofh.api.energy.EnergyStorage; import com.enderio.core.api.common.util.IProgressTile; import com.enderio.core.common.util.BlockCoord; import com.enderio.core.common.util.Util; import com.google.common.base.Throwables; import com.google.common.collect.Lists; import com.google.common.collect.Queues; import cpw.mods.fml.client.FMLClientHandler; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import crazypants.enderio.api.teleport.ITelePad; import crazypants.enderio.api.teleport.TravelSource; import crazypants.enderio.config.Config; import crazypants.enderio.machine.AbstractMachineEntity; import crazypants.enderio.machine.MachineSound; import crazypants.enderio.machine.PacketPowerStorage; import crazypants.enderio.network.PacketHandler; import crazypants.enderio.power.IInternalPowerReceiver; import crazypants.enderio.rail.TeleporterEIO; import crazypants.enderio.teleport.TravelController; import crazypants.enderio.teleport.anchor.TileTravelAnchor; import crazypants.enderio.teleport.packet.PacketTravelEvent; import crazypants.enderio.teleport.telepad.PacketTeleport.Type; public class TileTelePad extends TileTravelAnchor implements IInternalPowerReceiver, ITelePad, IProgressTile { private boolean inNetwork; private boolean isMaster; private EnergyStorage energy = new EnergyStorage(Config.telepadPowerStorageRF, Config.telepadPowerPerTickRF, Config.telepadPowerPerTickRF); private TileTelePad master = null; private boolean autoUpdate = false; private boolean coordsChanged = false; private BlockCoord target = new BlockCoord(); private int targetDim = Integer.MIN_VALUE; private int lastSyncPowerStored; private Queue<Entity> toTeleport = Queues.newArrayDeque(); private int powerUsed; private int maxPower; private static final ResourceLocation activeRes = AbstractMachineEntity.getSoundFor("telepad.active"); private MachineSound activeSound = null; private boolean redstoneActivePrev; public static final String TELEPORTING_KEY = "eio:teleporting"; public static final String PROGRESS_KEY = "teleportprogress"; boolean wasBlocked = false; // Clientside rendering data public float[] bladeRots = new float[3]; public float spinSpeed = 0; public float speedMult = 2.5f; @Override public void doUpdate() { super.doUpdate(); // my master is gone! if(master != null && master.isInvalid()) { master.breakNetwork(); } if(autoUpdate) { updateConnectedState(true); autoUpdate = false; } if(targetDim == Integer.MIN_VALUE) { targetDim = worldObj.provider.dimensionId; } if(worldObj.isRemote && isMaster()) { updateRotations(); if(activeSound != null) { activeSound.setPitch(MathHelper.clamp_float(0.5f + (spinSpeed / 1.5f), 0.5f, 2)); } if(active()) { if(activeSound == null) { activeSound = new MachineSound(activeRes, xCoord, yCoord, zCoord, 0.3f, 1); playSound(); } updateQueuedEntities(); } else if(!active() && activeSound != null) { if(activeSound.getPitch() <= 0.5f) { activeSound.endPlaying(); activeSound = null; } } } else if(!worldObj.isRemote) { if(active()) { if(powerUsed >= maxPower) { teleport(toTeleport.poll()); powerUsed = 0; } else { powerUsed += energy.extractEnergy(Math.min(getUsage(), maxPower), false); } if(shouldDoWorkThisTick(5)) { updateQueuedEntities(); } } boolean powerChanged = (lastSyncPowerStored != getEnergyStored() && shouldDoWorkThisTick(5)); if(powerChanged) { lastSyncPowerStored = getEnergyStored(); PacketHandler.sendToAllAround(new PacketPowerStorage(this), this); } if(coordsChanged && inNetwork() && master != null && isMaster()) { coordsChanged = false; PacketHandler.sendToAllAround(new PacketUpdateCoords(master, master.getX(), master.getY(), master.getZ(), master.getTargetDim()), master); } } } @SideOnly(Side.CLIENT) private void playSound() { FMLClientHandler.instance().getClient().getSoundHandler().playSound(activeSound); } private void updateQueuedEntities() { if(worldObj.isRemote) { if(active()) { getCurrentTarget().getEntityData().setFloat(PROGRESS_KEY, getProgress()); } } List<Entity> toRemove = Lists.newArrayList(); for (Entity e : toTeleport) { if(!isEntityInRange(e) || e.isDead) { toRemove.add(e); } } for (Entity e : toRemove) { dequeueTeleport(e, true); } } public void updateConnectedState(boolean fromBlock) { EnumSet<ForgeDirection> connections = EnumSet.noneOf(ForgeDirection.class); for (BlockCoord bc : getSurroundingCoords()) { TileEntity te = bc.getTileEntity(worldObj); ForgeDirection con = Util.getDirFromOffset(bc.x - xCoord, 0, bc.z - zCoord); if(te instanceof TileTelePad) { // let's find the master and let him do the work if (fromBlock) { // Recurse to all adjacent (diagonal and axis-aligned) telepads, but only 1 deep. ((TileTelePad) te).updateConnectedState(false); // If that telepad turned into a master, we can stop our search, as we were added to its network if (((TileTelePad) te).inNetwork() && !inNetwork) { return; } } // otherwise we either are the master or this is a secondary call, so update connections if (con != ForgeDirection.UNKNOWN && !((TileTelePad) te).inNetwork()) { connections.add(con); } } else { connections.remove(con); if(master == this) { breakNetwork(); updateBlock(); } else if(con != ForgeDirection.UNKNOWN) { if(inNetwork() && master != null && fromBlock) { master.updateConnectedState(false); } } } } if(connections.size() == 4 && !inNetwork()) { inNetwork = formNetwork(); updateBlock(); if(inNetwork()) { if(target.equals(new BlockCoord())) { target = new BlockCoord(this); } } } } public void updateRedstoneState() { if(!inNetwork()) { return; } boolean redstone = isPoweredRedstone(); if(!master.redstoneActivePrev && redstone) { teleportAll(); } master.redstoneActivePrev = redstone; } private boolean formNetwork() { List<TileTelePad> temp = Lists.newArrayList(); for (BlockCoord c : getSurroundingCoords()) { TileEntity te = c.getTileEntity(worldObj); if (!(te instanceof TileTelePad) || ((TileTelePad)te).inNetwork()) { return false; } temp.add((TileTelePad) te); } for (TileTelePad te : temp) { te.master = this; te.inNetwork = true; te.updateBlock(); te.updateNeighborTEs(); } this.master = this; this.isMaster = true; return true; } private void breakNetwork() { master = null; inNetwork = false; isMaster = false; for (BlockCoord c : getSurroundingCoords()) { TileEntity te = c.getTileEntity(worldObj); if(te instanceof TileTelePad) { TileTelePad telepad = (TileTelePad) te; telepad.master = null; telepad.inNetwork = false; telepad.updateBlock(); telepad.updateNeighborTEs(); } } } private List<BlockCoord> getSurroundingCoords() { List<BlockCoord> ret = Lists.newArrayList(); for (int x = -1; x <= 1; x++) { for (int z = -1; z <= 1; z++) { if(x != 0 || z != 0) { ret.add(new BlockCoord(xCoord + x, yCoord, zCoord + z)); } } } return ret; } private void updateNeighborTEs() { BlockCoord bc = new BlockCoord(this); for (ForgeDirection dir : ForgeDirection.VALID_DIRECTIONS) { BlockCoord neighbor = bc.getLocation(dir); Block block = neighbor.getBlock(worldObj); if(!(block instanceof BlockTelePad)) { block.onNeighborChange(worldObj, neighbor.x, neighbor.y, neighbor.z, xCoord, yCoord, zCoord); } } } @Override public boolean shouldUpdate() { return true; } @Override protected void writeCustomNBT(NBTTagCompound root) { super.writeCustomNBT(root); energy.writeToNBT(root); target.writeToNBT(root); root.setInteger("targetDim", targetDim); root.setBoolean("redstoneActive", redstoneActivePrev); } @Override protected void readCustomNBT(NBTTagCompound root) { super.readCustomNBT(root); energy.readFromNBT(root); target = BlockCoord.readFromNBT(root); targetDim = root.getInteger("targetDim"); redstoneActivePrev = root.getBoolean("redstoneActive"); autoUpdate = true; } @Override public Packet getDescriptionPacket() { S35PacketUpdateTileEntity pkt = (S35PacketUpdateTileEntity) super.getDescriptionPacket(); // pkt.func_148857_g().setBoolean("inNetwork", inNetwork); return pkt; } @Override public void onDataPacket(NetworkManager net, S35PacketUpdateTileEntity pkt) { super.onDataPacket(net, pkt); // this.inNetwork = pkt.func_148857_g().getBoolean("inNetwork"); } @Override public void invalidate() { super.invalidate(); if(worldObj.isRemote) { stopPlayingSound(); } } @Override public void onChunkUnload() { super.onChunkUnload(); if(worldObj.isRemote) { stopPlayingSound(); } } private void stopPlayingSound() { if(activeSound != null) { activeSound.endPlaying(); activeSound = null; } } public int getPowerScaled(int scale) { return (int) ((((float) getEnergyStored()) / ((float) getMaxEnergyStored())) * scale); } private int calculateTeleportPower() { if (worldObj.provider.dimensionId == targetDim) { int distance = new BlockCoord(this).getDist(target); double base = Math.log((0.005 * distance) + 1); this.maxPower = (int) (base * Config.telepadPowerCoefficient); if (this.maxPower <= 0) { this.maxPower = 1; } } else { this.maxPower = Config.telepadPowerInterdimensional; } return this.maxPower; } public boolean active() { return !toTeleport.isEmpty(); } public Entity getCurrentTarget() { return toTeleport.peek(); } public AxisAlignedBB getBoundingBox() { if(!inNetwork()) { return AxisAlignedBB.getBoundingBox(xCoord, yCoord, zCoord, xCoord + 1, yCoord + 1, zCoord + 1); } TileTelePad master = getMaster(); return AxisAlignedBB.getBoundingBox(master.xCoord - 1, master.yCoord, master.zCoord - 1, master.xCoord + 2, master.yCoord + 1, master.zCoord + 2); } @Override public AxisAlignedBB getRenderBoundingBox() { return getBoundingBox(); } public void updateRotations() { if(active()) { spinSpeed = getProgress() * 2; } else { spinSpeed = Math.max(0, spinSpeed - 0.025f); } for (int i = 0; i < bladeRots.length; i++) { bladeRots[i] += spinSpeed * ((i * 2) + 20); bladeRots[i] %= 360; } } /* IProgressTile */ @Override public float getProgress() { return ((float) powerUsed) / ((float) maxPower); } @Override protected int getProgressUpdateFreq() { return 1; } @Override public void setProgress(float progress) { this.powerUsed = progress < 0 ? 0 : (int) (((float) maxPower) * progress); } @Override public TileEntity getTileEntity() { return this; } /* ITelePad */ @Override public boolean isMaster() { return isMaster; } @Override public TileTelePad getMaster() { return master; } @Override public boolean inNetwork() { return inNetwork; } @Override public int getX() { if(inNetwork()) { return master.target.x; } return target.x; } @Override public int getY() { if(inNetwork()) { return master.target.y; } return target.y; } @Override public int getZ() { if(inNetwork()) { return master.target.z; } return target.z; } @Override public int getTargetDim() { if(inNetwork()) { return master.targetDim; } return targetDim; } @Override public ITelePad setX(int x) { return Config.telepadLockCoords ? null : setX_internal(x); } @Override public ITelePad setY(int y) { return Config.telepadLockCoords ? null : setY_internal(y); } @Override public ITelePad setZ(int z) { return Config.telepadLockCoords ? null : setZ_internal(z); } @Override public ITelePad setTargetDim(int dimID) { return Config.telepadLockDimension ? null : setTargetDim_internal(dimID); } @Override public void setCoords(BlockCoord coords) { if(!Config.telepadLockCoords) { setCoords_internal(coords); } } ITelePad setX_internal(int x) { if(inNetwork()) { setCoords(new BlockCoord(x, target.y, target.z)); return master; } return null; } ITelePad setY_internal(int y) { if(inNetwork()) { setCoords(new BlockCoord(target.x, y, target.z)); return master; } return null; } ITelePad setZ_internal(int z) { if(inNetwork()) { setCoords(new BlockCoord(target.x, target.y, z)); return master; } return null; } ITelePad setTargetDim_internal(int dimID) { if(inNetwork()) { master.targetDim = dimID; coordsChanged = true; return master; } return null; } void setCoords_internal(BlockCoord coords) { if(inNetwork()) { if(isMaster()) { this.target = coords; this.coordsChanged = true; } else { this.master.setCoords_internal(coords); } } } @Override public void teleportSpecific(Entity entity) { if(!inNetwork()) { return; } if(isMaster()) { if(isEntityInRange(entity)) { enqueueTeleport(entity, true); } } else { master.teleportSpecific(entity); } } @Override public void teleportAll() { if(!inNetwork()) { return; } if(isMaster()) { for (Entity e : getEntitiesInRange()) { enqueueTeleport(e, true); } } else { master.teleportAll(); } } @SuppressWarnings("unchecked") private List<Entity> getEntitiesInRange() { return worldObj.getEntitiesWithinAABB(Entity.class, getRange()); } private boolean isEntityInRange(Entity entity) { return getRange().isVecInside(Vec3.createVectorHelper(entity.posX, entity.posY, entity.posZ)); } private AxisAlignedBB getRange() { return AxisAlignedBB.getBoundingBox(xCoord - 1, yCoord, zCoord - 1, xCoord + 2, yCoord + 3, zCoord + 2); } void enqueueTeleport(Entity entity, boolean sendUpdate) { if(entity == null || toTeleport.contains(entity)) { return; } calculateTeleportPower(); entity.getEntityData().setBoolean(TELEPORTING_KEY, true); toTeleport.add(entity); if(sendUpdate) { if(entity.worldObj.isRemote) { PacketHandler.INSTANCE.sendToServer(new PacketTeleport(Type.BEGIN, this, entity.getEntityId())); } else { PacketHandler.INSTANCE.sendToAll(new PacketTeleport(Type.BEGIN, this, entity.getEntityId())); } } } void dequeueTeleport(Entity entity, boolean sendUpdate) { if (entity == null) { return; } toTeleport.remove(entity); entity.getEntityData().setBoolean(TELEPORTING_KEY, false); if(sendUpdate) { if(worldObj.isRemote) { PacketHandler.INSTANCE.sendToServer(new PacketTeleport(Type.END, this, entity.getEntityId())); } else { PacketHandler.INSTANCE.sendToAll(new PacketTeleport(Type.END, this, entity.getEntityId())); } } if(!active()) { powerUsed = 0; } } private boolean teleport(Entity entity) { if(maxPower > 0) { entity.getEntityData().setBoolean(TELEPORTING_KEY, false); wasBlocked = !(entity.worldObj.isRemote ? clientTeleport(entity) : serverTeleport(entity)); PacketHandler.INSTANCE.sendToAll(new PacketTeleport(Type.TELEPORT, this, wasBlocked)); return !wasBlocked; } return false; } private boolean clientTeleport(Entity entity) { if(entity.worldObj.provider.dimensionId == targetDim) { return TravelController.instance.doClientTeleport(entity, target, TravelSource.TELEPAD, 0, false); } return true; } private boolean serverTeleport(Entity entity) { dequeueTeleport(entity, true); int from = entity.dimension; if(from != targetDim) { MinecraftServer server = MinecraftServer.getServer(); WorldServer fromDim = server.worldServerForDimension(from); WorldServer toDim = server.worldServerForDimension(targetDim); Teleporter teleporter = new TeleporterEIO(toDim); server.worldServerForDimension(entity.dimension).playSoundEffect(entity.posX, entity.posY, entity.posZ, TravelSource.TELEPAD.sound, 1.0F, 1.0F); if (entity instanceof EntityPlayer) { EntityPlayerMP player = (EntityPlayerMP) entity; server.getConfigurationManager().transferPlayerToDimension(player, targetDim, teleporter); if (from == 1 && entity.isEntityAlive()) { // get around vanilla End hacks toDim.spawnEntityInWorld(entity); toDim.updateEntityWithOptionalForce(entity, false); } } else { NBTTagCompound tagCompound = new NBTTagCompound(); float rotationYaw = entity.rotationYaw; float rotationPitch = entity.rotationPitch; entity.writeToNBT(tagCompound); Class<? extends Entity> entityClass = entity.getClass(); fromDim.removeEntity(entity); try { Entity newEntity = entityClass.getConstructor(World.class).newInstance(toDim); newEntity.readFromNBT(tagCompound); newEntity.setLocationAndAngles(target.x, target.y, target.z, rotationYaw, rotationPitch); newEntity.forceSpawn = true; toDim.spawnEntityInWorld(newEntity); newEntity.forceSpawn = false; // necessary? } catch (Exception e) { Throwables.propagate(e); } } } return PacketTravelEvent.doServerTeleport(entity, target.x, target.y, target.z, 0, false, TravelSource.TELEPAD); } /* ITravelAccessable overrides */ @Override public boolean canSeeBlock(EntityPlayer playerName) { return isMaster() && inNetwork(); } /* IInternalPowerReceiver */ @Override public int getMaxEnergyRecieved(ForgeDirection dir) { return inNetwork() && master != null ? master == this ? energy.getMaxReceive() : master.getMaxEnergyRecieved(dir) : 0; } @Override public int getMaxEnergyStored() { return inNetwork() && master != null ? master == this ? energy.getMaxEnergyStored() : master.getMaxEnergyStored() : 0; } @Override public boolean displayPower() { return inNetwork() && master != null; } @Override public int getEnergyStored() { return inNetwork() && master != null ? master == this ? energy.getEnergyStored() : master.getEnergyStored() : 0; } @Override public void setEnergyStored(int storedEnergy) { if(inNetwork() && master != null) { if(master == this) { energy.setEnergyStored(storedEnergy); } else { master.setEnergyStored(storedEnergy); } } } @Override public boolean canConnectEnergy(ForgeDirection from) { return inNetwork() && master != null; } @Override public int receiveEnergy(ForgeDirection from, int maxReceive, boolean simulate) { return inNetwork() && master != null ? master == this ? energy.receiveEnergy(maxReceive, simulate) : master.receiveEnergy(from, maxReceive, simulate) : 0; } @Override public int getEnergyStored(ForgeDirection from) { return inNetwork() && master != null ? master == this ? energy.getEnergyStored() : master.getEnergyStored() : 0; } @Override public int getMaxEnergyStored(ForgeDirection from) { return inNetwork() && master != null ? master == this ? energy.getMaxEnergyStored() : master.getMaxEnergyStored() : 0; } public int getUsage() { return energy.getMaxReceive(); } }