package eiteam.esteemedinnovation.transport.steam; import codechicken.lib.raytracer.IndexedCuboid6; import codechicken.lib.raytracer.RayTracer; import codechicken.lib.vec.Cuboid6; import eiteam.esteemedinnovation.api.SteamTransporter; import eiteam.esteemedinnovation.api.steamnet.SteamNetwork; import eiteam.esteemedinnovation.api.tile.SteamTransporterTileEntity; import eiteam.esteemedinnovation.api.wrench.Wrenchable; import eiteam.esteemedinnovation.commons.EsteemedInnovation; import net.minecraft.block.Block; import net.minecraft.block.SoundType; import net.minecraft.block.properties.PropertyBool; import net.minecraft.block.state.IBlockState; import net.minecraft.entity.item.EntityItem; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.network.NetworkManager; import net.minecraft.network.play.server.SPacketUpdateTileEntity; import net.minecraft.tileentity.TileEntity; import net.minecraft.util.EnumFacing; import net.minecraft.util.EnumHand; import net.minecraft.util.EnumParticleTypes; import net.minecraft.util.SoundCategory; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.RayTraceResult; import net.minecraft.world.World; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; public class TileEntitySteamPipe extends SteamTransporterTileEntity implements Wrenchable { //protected FluidTank dummyFluidTank = FluidRegistry.isFluidRegistered("steam") ? new FluidTank(new FluidStack(FluidRegistry.getFluid("steam"), 0),10000) : null; public ArrayList<Integer> blacklistedSides = new ArrayList<>(); public Block disguiseBlock = null; public int disguiseMeta = 0; public boolean isOriginalPipe = false; public boolean isOtherPipe = false; protected boolean isLeaking = false; private boolean lastWrench = false; public TileEntitySteamPipe() { super(EnumFacing.VALUES); name = "Pipe"; } public TileEntitySteamPipe(int capacity) { this(); this.capacity = capacity; } @Override public SPacketUpdateTileEntity getUpdatePacket() { NBTTagCompound access = super.getUpdateTag(); access.setBoolean("isLeaking", isLeaking); NBTTagCompound list = new NBTTagCompound(); int g = 0; for (int i : blacklistedSides) { list.setInteger(Integer.toString(g), i); g++; } list.setInteger("size", g); access.setTag("blacklistedSides", list); access.setInteger("disguiseBlock", Block.getIdFromBlock(disguiseBlock)); access.setInteger("disguiseMeta", disguiseMeta); return new SPacketUpdateTileEntity(pos, 1, access); } @Override public void onDataPacket(NetworkManager net, SPacketUpdateTileEntity pkt) { super.onDataPacket(net, pkt); NBTTagCompound access = pkt.getNbtCompound(); isLeaking = access.getBoolean("isLeaking"); NBTTagCompound sidesList = access.getCompoundTag("blacklistedSides"); int length = sidesList.getInteger("size"); Integer[] sidesInt = new Integer[length]; for (int i = 0; i < length; i++) { sidesInt[i] = sidesList.getInteger(Integer.toString(i)); } blacklistedSides = new ArrayList<>(Arrays.asList(sidesInt)); disguiseBlock = Block.getBlockById(access.getInteger("disguiseBlock")); disguiseMeta = access.getInteger("disguiseMeta"); markDirty(); } @Override public void readFromNBT(NBTTagCompound access) { super.readFromNBT(access); NBTTagCompound sidesList = access.getCompoundTag("blacklistedSides"); int length = sidesList.getInteger("size"); Integer[] sidesInt = new Integer[length]; for (int i = 0; i < length; i++) { sidesInt[i] = sidesList.getInteger(Integer.toString(i)); } blacklistedSides = new ArrayList<>(Arrays.asList(sidesInt)); disguiseBlock = Block.getBlockById(access.getInteger("disguiseBlock")); disguiseMeta = access.getInteger("disguiseMeta"); } @Override public NBTTagCompound writeToNBT(NBTTagCompound access) { super.writeToNBT(access); NBTTagCompound list = new NBTTagCompound(); int g = 0; for (int i : blacklistedSides) { list.setInteger(Integer.toString(g), i); g++; } list.setInteger("size", g); access.setTag("blacklistedSides", list); access.setInteger("disguiseBlock", Block.getIdFromBlock(disguiseBlock)); access.setInteger("disguiseMeta", disguiseMeta); return access; } /* Supersuper methods for bypassing TileEntitySteamPipe's behavior. TODO: Create a base TileEntityPipe class for TileEntitySteamPipe (this), TileEntityValvePipe, and TileEntitySteamHeater to inherit. */ protected void superUpdate() { super.safeUpdate(); } protected NBTTagCompound superWriteToNBT(NBTTagCompound access) { return super.writeToNBT(access); } protected void superReadFromNBT(NBTTagCompound access) { super.readFromNBT(access); } public ArrayList<EnumFacing> getMyDirections() { ArrayList<EnumFacing> myDirections = new ArrayList<>(); for (EnumFacing direction : getConnectionSides()) { TileEntity tile = worldObj.getTileEntity(getOffsetPos(direction)); if (tile instanceof SteamTransporter) { SteamTransporter target = (SteamTransporter) tile; if (target.doesConnect(direction.getOpposite())) { myDirections.add(direction); } } } return myDirections; } /** * Handles the leaking logic. Checks if there is only one connection, if it can leak, etc. * If it is leaking and shouldn't, this stops it, and if it isn't but should, this makes it leak. */ public void leak() { ArrayList<EnumFacing> myDirections = getMyDirections(); if (myDirections.size() != 1) { return; } EnumFacing direction = myDirections.get(0).getOpposite(); while (!doesConnect(direction) || direction == myDirections.get(0)) { direction = EnumFacing.VALUES[(direction.ordinal() + 1) % 5]; } if (!worldObj.isRemote) { int i = 0; IBlockState myState = worldObj.getBlockState(pos); if (canLeak(direction)) { worldObj.playSound(null, pos, EsteemedInnovation.SOUND_LEAK, SoundCategory.BLOCKS, 2F, 0.9F); if (!isLeaking) { isLeaking = true; markForResync(myState); markDirty(); } } else { if (isLeaking) { isLeaking = false; markForResync(myState); markDirty(); } } while (isLeaking && i < 10) { decrSteam(10); i++; } } else if (isLeaking) { worldObj.spawnParticle(EnumParticleTypes.SMOKE_NORMAL, pos.getX() + 0.5F, pos.getY() + 0.5F, pos.getZ() + 0.5F, direction.getFrontOffsetX() * 0.1F, direction.getFrontOffsetY() * 0.1F, direction.getFrontOffsetZ() * 0.1F); } } /** * Gets whether this tileentity is able to leak from this side. * @param direction The side * @return Whether it can leak. */ public boolean canLeak(EnumFacing direction) { BlockPos dirPos = getOffsetPos(direction); return (getSteamShare() > 0 && (worldObj.isAirBlock(dirPos) || !worldObj.isSideSolid(dirPos, direction.getOpposite()))); } @Override public boolean canUpdate(IBlockState target) { return target.getBlock() instanceof BlockSteamPipe; } @Override public void safeUpdate() { /* TODO: Port this. if (worldObj.isRemote) { boolean hasWrench = BlockSteamPipeRenderer.updateWrenchStatus(); if (hasWrench != lastWrench && !(disguiseBlock == null || disguiseBlock == Blocks.AIR)) { markDirty(); } lastWrench = hasWrench; } */ leak(); super.safeUpdate(); } @Override public HashSet<EnumFacing> getConnectionSides() { HashSet<EnumFacing> out = new HashSet<>(); HashSet<EnumFacing> blacklist = new HashSet<>(); for (int i : blacklistedSides) { blacklist.add(EnumFacing.getFront(i)); } for (EnumFacing d : distributionDirections) { if (!blacklist.contains(d)) { out.add(d); } } return out; } @Override public boolean doesConnect(EnumFacing face) { for (int i : blacklistedSides) { if (EnumFacing.VALUES[i] == face) { return false; } } return super.doesConnect(face); } @Override public int getSteamShare() { SteamNetwork network = getNetwork(); if (network == null) { this.network = null; networkName = null; return 0; } else { return (int) Math.floor((double) network.getPressure() * (double) capacity); } } /** * Gets whether the pipe can connect to something in the given direction. * @param direction The direction * @return 0 if it cannot connect. 2 if it can connect to another pipe. 1 if it can connect, but not to another pipe. */ public int canConnectSide(EnumFacing direction) { BlockPos pos = getOffsetPos(direction); TileEntity tile = worldObj.getTileEntity(pos); if (tile != null && tile instanceof SteamTransporter) { SteamTransporter target = (SteamTransporter) tile; if (target.doesConnect(direction.getOpposite())) { return target instanceof TileEntitySteamPipe ? 2 : 1; } if (target instanceof TileEntitySteamPipe && ((TileEntitySteamPipe) target).blacklistedSides.contains(direction.getOpposite().ordinal())) { return 2; } } return 0; } /** * Checks whether the pipe is connected at the given direction, or the opposite and only the opposite. * This is referred to in several comments as a "long" pipe, eg. =.=.=, the first and last are the "long" pipes, * because they do not connect to anything on more than one side, but are still rendered as "long", as if they were. * * This method will be significantly slower than the other shouldStretchInDirection method in most use cases, * as it manually calls getMyDirections. When this is being called > 1 times every tick or update, this should not * be used, and instead, the fast version should be called to prevent the performance hit of calling * getMyDirections() repeatedly. * @param actualState The actual state for the block * @param direction The first direction * @param opposite The second direction, usually the opposite. */ public boolean shouldStretchInDirection(IBlockState actualState, PropertyBool direction, PropertyBool opposite) { return shouldStretchInDirection(actualState.getValue(direction), actualState.getValue(opposite), getMyDirections().size()); } /** * A variant of shouldStretchInDirection that does not actually manually get all of the values, because, particularly * with the getMyDirections method, it might cause some serious performance hits. Recommended usage: * ``` * boolean hasDown = actualState.getValue(DOWN); * boolean hasUp = actualState.getValue(UP); * int numDirs = pipe.getMyDirections().size(); * shouldStretchInDirection(hasDown, hasUp, numDirs); * ``` * * This method is simply a wrapper for a very common if statement in relation to pipe bounding and collision boxes. * * This method also has the benefit of being static, which the other shouldStretchInDirection method is not. * * @param hasDir Whether it is connected at this dir * @param hasOpposite Whether it is connected at this dir, usually the opposite of the dir used in hasDir * @param numDirs The number of directions that this pipe is connected at. */ public static boolean shouldStretchInDirection(boolean hasDir, boolean hasOpposite, int numDirs) { return hasDir || (hasOpposite && numDirs == 1); } public void addTraceableCuboids(List<IndexedCuboid6> cuboids) { float min = BlockSteamPipe.BASE_MIN; float max = BlockSteamPipe.BASE_MAX; int x = pos.getX(); int y = pos.getY(); int z = pos.getZ(); IBlockState actualState = worldObj.getBlockState(pos).getActualState(worldObj, pos); boolean hasDown = actualState.getValue(BlockSteamPipe.DOWN); boolean hasUp = actualState.getValue(BlockSteamPipe.UP); boolean hasNorth = actualState.getValue(BlockSteamPipe.NORTH); boolean hasSouth = actualState.getValue(BlockSteamPipe.SOUTH); boolean hasWest = actualState.getValue(BlockSteamPipe.WEST); boolean hasEast = actualState.getValue(BlockSteamPipe.EAST); int numDirs = getMyDirections().size(); if (shouldStretchInDirection(hasDown, hasUp, numDirs)) { int connectDown = canConnectSide(EnumFacing.DOWN); float bottom = connectDown == 2 ? -5F / 16F : 0F; cuboids.add(new IndexedCuboid6(0, new Cuboid6(x + min, y + bottom, z + min, x + max, y + max, z + max))); } if (shouldStretchInDirection(hasUp, hasDown, numDirs)) { int connectUp = canConnectSide(EnumFacing.UP); float top = connectUp == 2 ? 21F / 16F : 1F; cuboids.add(new IndexedCuboid6(1, new Cuboid6(x + min, y + min, z + min, x + max, y + top, z + max))); } if (shouldStretchInDirection(hasNorth, hasSouth, numDirs)) { int connectNorth = canConnectSide(EnumFacing.NORTH); float bottom = connectNorth == 2 ? -5F / 16F : 0F; cuboids.add(new IndexedCuboid6(2, new Cuboid6(x + min, y + min, z + bottom, x + max, y + max, z + max))); } if (shouldStretchInDirection(hasSouth, hasNorth, numDirs)) { int connectSouth = canConnectSide(EnumFacing.SOUTH); float top = connectSouth == 2 ? 21F / 16F : 1F; cuboids.add(new IndexedCuboid6(3, new Cuboid6(x + min, y + min, z + min, x + max, y + max, z + top))); } if (shouldStretchInDirection(hasWest, hasEast, numDirs)) { int connectWest = canConnectSide(EnumFacing.WEST); float bottom = connectWest == 2 ? -5F / 16F : 0F; cuboids.add(new IndexedCuboid6(4, new Cuboid6(x + bottom, y + min, z + min, x + max, y + max, z + max))); } if (shouldStretchInDirection(hasEast, hasWest, numDirs)) { int connectEast = canConnectSide(EnumFacing.EAST); float top = connectEast == 2 ? 21F / 16F : 1F; cuboids.add(new IndexedCuboid6(5, new Cuboid6(x + min, y + min, z + min, x + top, y + max, z + max))); } cuboids.add(new IndexedCuboid6(6, new Cuboid6(x + min, y + min, z + min, x + 12F / 16F, y + 12F / 16F, z + 12F / 16F))); } @Override public boolean onWrench(ItemStack stack, EntityPlayer player, World world, BlockPos pos, EnumHand hand, EnumFacing facing, IBlockState state, float hitX, float hitY, float hitZ) { if (player.isSneaking()) { if (this.disguiseBlock != null) { if (!player.capabilities.isCreativeMode) { EntityItem entityItem = new EntityItem(world, player.posX, player.posY, player.posZ, new ItemStack(disguiseBlock, 1, disguiseMeta)); world.spawnEntityInWorld(entityItem); } SoundType sound = disguiseBlock.getSoundType(); world.playSound((double) pos.getX() + 0.5F, (double) (pos.getY() + 0.5F), (double) (pos.getZ() + 0.5F), sound.getBreakSound(), SoundCategory.BLOCKS, (sound.getVolume() + 1.0F) / 2.0F, sound.getPitch() * 0.8F, false); disguiseBlock = null; markDirty(); return true; } } else { if (this.worldObj.isRemote) { RayTraceResult hit = RayTracer.retraceBlock(world, player, pos); // Use ryatracer to get the subpart that was hit. The # corresponds with a forge direction. if (hit == null) { return false; } ConnectPacket packet = new ConnectPacket(pos, hit.subHit); EsteemedInnovation.channel.sendToServer(packet); } } return false; } public void connectDisconnect(World world, BlockPos pos, int subHit) { // Use raytracer to get the subpart that was hit. The # corresponds with a forge direction. // If hit a part from 0 to 5 (direction) and hit me IBlockState state = world.getBlockState(pos); if ((subHit >= 0) && (subHit < 6) && state instanceof BlockSteamPipe) { //Make sure that you can't make an 'end cap' by allowing less than 2 directions to connect int sidesConnect = 0; for (int i = 0; i < 6; i++) { if (doesConnect(EnumFacing.getFront(i))) { sidesConnect++; } } boolean netChange = false; //If does connect on this side, and has adequate sides left EnumFacing direction = EnumFacing.getFront(subHit); if (doesConnect(direction)) { BlockPos offsetPos = getOffsetPos(direction); TileEntity tile = world.getTileEntity(offsetPos); if (tile instanceof TileEntitySteamPipe && ((TileEntitySteamPipe) tile).blacklistedSides.contains(direction.getOpposite().ordinal())) { TileEntitySteamPipe pipe = (TileEntitySteamPipe) tile; pipe.blacklistedSides.remove((Integer) direction.getOpposite().ordinal()); //network stuff pipe.shouldJoin(); pipe.isOtherPipe = true; //pipe.getNetwork().addSteam(steam); world.notifyBlockUpdate(offsetPos, world.getBlockState(offsetPos), world.getBlockState(offsetPos), 0); } else if (sidesConnect > 2) { //add to blacklist blacklistedSides.add(subHit); { if (tile != null && tile instanceof SteamTransporter) { SteamTransporter p = (SteamTransporter) tile; SteamNetwork network = p.getNetwork(); if (network != null) { network.shouldRefresh(); } } } isOriginalPipe = true; //bad network stuff int steam = getNetwork().split(this, false); shouldJoin(); //this.getNetwork().addSteam(steam); refreshNeighbors(); network.shouldRefresh(); markForResync(); } } //else if doesn't connect else if (!doesConnect(direction)) { if (blacklistedSides.contains(subHit)) { //remove from whitelist blacklistedSides.remove((Integer) subHit); //network stuff int steam = getNetwork().split(this, false); shouldJoin(); //this.getNetwork().addSteam(steam); ////EsteemedInnovation.log.debug("C"); ////EsteemedInnovation.log.debug(this.getNetworkName()); ////EsteemedInnovation.log.debug("steam: "+steam+"; nw steam: "+this.getNetwork().getSteam()); markForResync(); } } // if (getSteamShare() > 0) { //world.playSoundEffect(x+0.5F, y+0.5F, z+0.5F, EsteemedInnovation.MOD_ID + ":leaking", 2.0F, 0.9F); // } world.playSound(pos.getX() + 0.5F, pos.getY() + 0.5F, pos.getZ() + 0.5F, EsteemedInnovation.SOUND_WRENCH, SoundCategory.BLOCKS, 2F, 0.9F, false); } } @Override public void refresh() { super.refresh(); isOriginalPipe = false; isOtherPipe = false; } private void refreshNeighbors() { //log.debug("Refreshing neighbors"); for (EnumFacing dir : EnumFacing.VALUES) { TileEntity te = worldObj.getTileEntity(getOffsetPos(dir)); if (te != null && te instanceof SteamTransporter) { SteamTransporter trans = (SteamTransporter) te; SteamNetwork transNetwork = trans.getNetwork(); if (transNetwork != null && transNetwork != getNetwork()) { transNetwork.shouldRefresh(); } } } } }