package micdoodle8.mods.galacticraft.core.fluid; import micdoodle8.mods.galacticraft.api.block.IPartialSealableBlock; import micdoodle8.mods.galacticraft.api.vector.BlockVec3; import micdoodle8.mods.galacticraft.core.GCBlocks; import micdoodle8.mods.galacticraft.core.blocks.BlockUnlitTorch; import micdoodle8.mods.galacticraft.core.tick.TickHandlerServer; import micdoodle8.mods.galacticraft.core.tile.TileEntityOxygenSealer; import micdoodle8.mods.galacticraft.core.util.ConfigManagerCore; import micdoodle8.mods.galacticraft.core.util.GCCoreUtil; import micdoodle8.mods.galacticraft.core.util.GCLog; import micdoodle8.mods.galacticraft.core.wrappers.ScheduledBlockChange; import net.minecraft.block.*; import net.minecraft.block.material.Material; import net.minecraft.block.state.IBlockState; import net.minecraft.init.Blocks; import net.minecraft.tileentity.TileEntity; import net.minecraft.util.BlockPos; import net.minecraft.util.EnumFacing; import net.minecraft.world.World; import java.util.*; import java.util.concurrent.atomic.AtomicBoolean; public class ThreadFindSeal { public AtomicBoolean sealedFinal = new AtomicBoolean(); public static AtomicBoolean anylooping = new AtomicBoolean(); public AtomicBoolean looping = new AtomicBoolean(); private World world; private BlockVec3 head; private boolean sealed; private List<TileEntityOxygenSealer> sealers; private static intBucket[] buckets; private static int checkedSize; private int checkCount; private HashMap<BlockVec3, TileEntityOxygenSealer> sealersAround; private List<BlockVec3> currentLayer; private List<BlockVec3> airToReplace; private List<BlockVec3> fireToReplace; private List<BlockVec3> breatheableToReplace; private List<BlockVec3> airToReplaceBright; private List<BlockVec3> breatheableToReplaceBright; private List<BlockVec3> ambientThermalTracked; private List<BlockVec3> ambientThermalTrackedBright; private List<TileEntityOxygenSealer> otherSealers; private List<BlockVec3> torchesToUpdate; private boolean foundAmbientThermal; public List<BlockVec3> leakTrace; static { buckets = new intBucket[256]; checkedInit(); } public ThreadFindSeal(TileEntityOxygenSealer sealer) { this(sealer.getWorld(), sealer.getPos().up(), sealer.getFindSealChecks(), new ArrayList<TileEntityOxygenSealer>(Collections.singletonList(sealer))); } @SuppressWarnings("unchecked") public ThreadFindSeal(World world, BlockPos head, int checkCount, List<TileEntityOxygenSealer> sealers) { if (ThreadFindSeal.anylooping.getAndSet(true)) { return; } this.world = world; this.head = new BlockVec3(head); this.checkCount = checkCount; this.sealers = sealers; this.foundAmbientThermal = false; checkedClear(); checkedSize = 0; this.torchesToUpdate = new LinkedList<BlockVec3>(); this.sealersAround = TileEntityOxygenSealer.getSealersAround(world, head, 1024 * 1024); //If called by a sealer test the head block and if partiallySealable mark its sides done as required if (!sealers.isEmpty()) { if (checkCount > 0) { Block headBlock = this.world.getBlockState(head).getBlock(); if (headBlock != null && !(headBlock.isAir(world, head))) { this.canBlockPassAirCheck(headBlock, this.head, 1); //reset the checkCount as canBlockPassAirCheck might have changed it this.checkCount = checkCount; } } this.looping.set(true); for (TileEntityOxygenSealer eachSealer : sealers) { eachSealer.threadSeal = this; } // if (ConfigManagerCore.enableSealerMultithreading) // { // new ThreadedFindSeal(); // } // else // { this.check(); // } } else //If not called by a sealer, it's a breathable air edge check { //Run this in the main thread this.check(); } ThreadFindSeal.anylooping.set(false); } //Multi-threaded version of the code for sealer updates (not for edge checks). public class ThreadedFindSeal extends Thread { public ThreadedFindSeal() { super("GC Sealer Roomfinder Thread"); if (this.isAlive()) { this.interrupt(); } //Run this as a separate thread this.start(); } @Override public void run() { ThreadFindSeal.this.check(); ThreadFindSeal.anylooping.set(false); } } public void check() { long time1 = System.nanoTime(); this.sealed = true; TileEntity tile = this.head.getTileEntityOnSide(world, EnumFacing.DOWN); this.foundAmbientThermal = tile instanceof TileEntityOxygenSealer && ((TileEntityOxygenSealer) tile).thermalControlEnabled(); this.checkedAdd(this.head); this.currentLayer = new LinkedList<BlockVec3>(); this.airToReplace = new LinkedList<BlockVec3>(); this.airToReplaceBright = new LinkedList<BlockVec3>(); this.ambientThermalTracked = new LinkedList<BlockVec3>(); this.ambientThermalTrackedBright = new LinkedList<BlockVec3>(); if (this.checkCount > 0) { this.currentLayer.add(this.head); if (this.head.x < -29990000 || this.head.z < -29990000 || this.head.x >= 29990000 || this.head.z >= 29990000) { Block b = this.head.getBlockID_noChunkLoad(this.world); if (Blocks.air == b) { this.airToReplace.add(this.head.clone()); } else if (b == GCBlocks.brightAir) { this.airToReplaceBright.add(this.head.clone()); } this.doLayerNearMapEdge(); } else { Block headblock = this.head.getBlockIDsafe_noChunkLoad(this.world); if (Blocks.air == headblock) { this.airToReplace.add(this.head.clone()); } else if (headblock == GCBlocks.brightAir) { this.airToReplaceBright.add(this.head.clone()); } this.doLayer(); } } else { this.sealed = false; } long time2 = System.nanoTime(); //Can only be properly sealed if there is at least one sealer here (on edge check) if (this.sealers.isEmpty()) { this.sealed = false; } if (this.sealed) { this.makeSealGood(this.foundAmbientThermal); this.leakTrace = null; } else { int checkedSave = checkedSize; checkedClear(); this.breatheableToReplace = new LinkedList<BlockVec3>(); this.breatheableToReplaceBright = new LinkedList<BlockVec3>(); this.fireToReplace = new LinkedList<BlockVec3>(); this.otherSealers = new LinkedList<TileEntityOxygenSealer>(); // unseal() will mark breatheableAir blocks for change as it // finds them, also searches for unchecked sealers this.currentLayer.clear(); this.currentLayer.add(this.head); this.torchesToUpdate.clear(); if (this.head.x < -29990000 || this.head.z < -29990000 || this.head.x >= 29990000 || this.head.z >= 29990000) { this.unsealNearMapEdge(); } else { this.unseal(); } if (!this.otherSealers.isEmpty()) { // OtherSealers will have members if the space to be made // unbreathable actually still has an unchecked sealer in it List<TileEntityOxygenSealer> sealersSave = this.sealers; List<BlockVec3> torchesSave = this.torchesToUpdate; List<TileEntityOxygenSealer> sealersDone = new ArrayList(); sealersDone.addAll(this.sealers); for (TileEntityOxygenSealer otherSealer : this.otherSealers) { // If it hasn't already been counted, need to check the // other sealer immediately in case it can keep the space // sealed if (!sealersDone.contains(otherSealer) && otherSealer.getFindSealChecks() > 0) { BlockVec3 newhead = new BlockVec3(otherSealer).translate(0, 1, 0); this.sealed = true; this.checkCount = otherSealer.getFindSealChecks(); this.sealers = new LinkedList<TileEntityOxygenSealer>(); this.sealers.add(otherSealer); if (otherSealer.thermalControlEnabled()) { foundAmbientThermal = true; } checkedClear(); this.checkedAdd(newhead); this.currentLayer.clear(); this.airToReplace.clear(); this.airToReplaceBright.clear(); this.torchesToUpdate = new LinkedList<BlockVec3>(); this.currentLayer.add(newhead.clone()); if (newhead.x < -29990000 || newhead.z < -29990000 || newhead.x >= 29990000 || newhead.z >= 29990000) { this.doLayerNearMapEdge(); } else { this.doLayer(); } // If found a sealer which can still seal the space, it // should take over as head if (this.sealed) { if (ConfigManagerCore.enableDebug) { GCLog.info("Oxygen Sealer replacing head at x" + this.head.x + " y" + (this.head.y - 1) + " z" + this.head.z); } if (!sealersSave.isEmpty()) { TileEntityOxygenSealer oldHead = sealersSave.get(0); if (!this.sealers.contains(oldHead)) { this.sealers.add(oldHead); if (oldHead.thermalControlEnabled()) { foundAmbientThermal = true; } } } this.head = newhead.clone(); otherSealer.threadSeal = this; otherSealer.stopSealThreadCooldown = 75 + TileEntityOxygenSealer.countEntities; checkedSave += checkedSize; break; } else { sealersDone.addAll(this.sealers); } checkedSave += checkedSize; } } // Restore sealers to what it was, if this search did not // result in a seal if (!this.sealed) { this.sealers = sealersSave; this.torchesToUpdate = torchesSave; } else { //If the second search sealed the area, there may also be air or torches to update this.makeSealGood(foundAmbientThermal); } } checkedSize = checkedSave; if (!this.sealed) { if (this.head.getBlockID(this.world) == GCBlocks.breatheableAir) { this.breatheableToReplace.add(this.head); } if (this.head.getBlockID(this.world) == GCBlocks.brightBreatheableAir) { this.breatheableToReplaceBright.add(this.head); } this.makeSealBad(); } else { this.leakTrace = null; } } // Set any sealers found which are not the head sealer, not to run their // own seal checks for a while // (The player can control which is the head sealer in a space by // enabling just that one and disabling all the others) TileEntityOxygenSealer headSealer = this.sealersAround.get(this.head.clone().translate(0, -1, 0)); //TODO: if multi-threaded, this final code block giving access to the sealer tiles needs to be threadsafe // If it is sealed, cooldown can be extended as frequent checks are not needed if (headSealer != null) { headSealer.stopSealThreadCooldown = 75 + TileEntityOxygenSealer.countEntities; } for (TileEntityOxygenSealer sealer : this.sealers) { // Sealers which are not the head sealer: put them on cooldown so // the inactive ones don't start their own threads and so unseal // this volume // and update threadSeal reference of all sealers found (even the // inactive ones) if (sealer != headSealer && headSealer != null) { sealer.threadSeal = this; sealer.stopSealThreadCooldown = headSealer.stopSealThreadCooldown + 51; } } this.sealedFinal.set(this.sealed); this.looping.set(false); if (ConfigManagerCore.enableDebug) { long time3 = System.nanoTime(); float total = (time3 - time1) / 1000000.0F; float looping = (time2 - time1) / 1000000.0F; float replacing = (time3 - time2) / 1000000.0F; GCLog.info("Oxygen Sealer Check Completed at x" + this.head.x + " y" + this.head.y + " z" + this.head.z); GCLog.info(" Sealed: " + this.sealed + " ~ " + this.sealers.size() + " sealers ~ " + (checkedSize - 1) + " blocks"); GCLog.info(" Total Time taken: " + String.format("%.2f", total) + "ms ~ " + String.format("%.2f", looping) + " + " + String.format("%.2f", replacing) + ""); } } private void makeSealGood(boolean ambientThermal) { if (!this.airToReplace.isEmpty() || !this.airToReplaceBright.isEmpty() || !ambientThermalTracked.isEmpty() || !ambientThermalTracked.isEmpty()) { List<ScheduledBlockChange> changeList = new LinkedList<ScheduledBlockChange>(); Block breatheableAirID = GCBlocks.breatheableAir; Block breatheableAirIDBright = GCBlocks.brightBreatheableAir; int metadata = ambientThermal ? 1 : 0; //TODO: Can we somehow detect only changes in state of ambientThermal since last check? tricky... for (BlockVec3 checkedVec : this.airToReplace) { //No block update for performance reasons; deal with unlit torches separately changeList.add(new ScheduledBlockChange(checkedVec.toBlockPos(), breatheableAirID, metadata, 0)); } for (BlockVec3 checkedVec : this.airToReplaceBright) { changeList.add(new ScheduledBlockChange(checkedVec.toBlockPos(), breatheableAirIDBright, metadata, 0)); } for (BlockVec3 checkedVec : this.ambientThermalTracked) { if (checkedVec.getBlockMetadata(this.world) != metadata) { changeList.add(new ScheduledBlockChange(checkedVec.toBlockPos(), breatheableAirID, metadata, 0)); } } for (BlockVec3 checkedVec : this.ambientThermalTrackedBright) { if (checkedVec.getBlockMetadata(this.world) != metadata) { changeList.add(new ScheduledBlockChange(checkedVec.toBlockPos(), breatheableAirIDBright, metadata, 0)); } } TickHandlerServer.scheduleNewBlockChange(GCCoreUtil.getDimensionID(this.world), changeList); } if (!this.torchesToUpdate.isEmpty()) { TickHandlerServer.scheduleNewTorchUpdate(GCCoreUtil.getDimensionID(this.world), this.torchesToUpdate); } } private void makeSealBad() { if (!this.breatheableToReplace.isEmpty() || !this.breatheableToReplaceBright.isEmpty()) { List<ScheduledBlockChange> changeList = new LinkedList<ScheduledBlockChange>(); for (BlockVec3 checkedVec : this.breatheableToReplace) { changeList.add(new ScheduledBlockChange(checkedVec.toBlockPos(), Blocks.air, 0, 0)); } for (BlockVec3 checkedVec : this.fireToReplace) { changeList.add(new ScheduledBlockChange(checkedVec.toBlockPos(), Blocks.air, 0, 2)); } for (BlockVec3 checkedVec : this.breatheableToReplaceBright) { changeList.add(new ScheduledBlockChange(checkedVec.toBlockPos(), GCBlocks.brightAir, 0, 0)); } TickHandlerServer.scheduleNewBlockChange(GCCoreUtil.getDimensionID(this.world), changeList); } if (!this.torchesToUpdate.isEmpty()) { TickHandlerServer.scheduleNewTorchUpdate(GCCoreUtil.getDimensionID(this.world), this.torchesToUpdate); } } private void unseal() { //Local variables are fractionally faster than statics Block breatheableAirID = GCBlocks.breatheableAir; Block breatheableAirIDBright = GCBlocks.brightBreatheableAir; Block oxygenSealerID = GCBlocks.oxygenSealer; Block fireBlock = Blocks.fire; Block airBlock = Blocks.air; Block airBlockBright = GCBlocks.brightAir; List<BlockVec3> toReplaceLocal = this.breatheableToReplace; List<BlockVec3> toReplaceLocalBright = this.breatheableToReplaceBright; LinkedList nextLayer = new LinkedList<BlockVec3>(); World world = this.world; int side, bits; while (this.currentLayer.size() > 0) { for (BlockVec3 vec : this.currentLayer) { side = 0; bits = vec.sideDoneBits; do { if ((bits & (1 << side)) == 0) { if (!checkedContains(vec, side)) { BlockVec3 sideVec = vec.newVecSide(side); Block id = sideVec.getBlockIDsafe_noChunkLoad(world); if (id == breatheableAirID) { toReplaceLocal.add(sideVec); nextLayer.add(sideVec); checkedAdd(sideVec); } else if (id == breatheableAirIDBright) { toReplaceLocalBright.add(sideVec); nextLayer.add(sideVec); checkedAdd(sideVec); } else if (id == fireBlock) { this.fireToReplace.add(sideVec); nextLayer.add(sideVec); checkedAdd(sideVec); } else if (id == oxygenSealerID) { TileEntityOxygenSealer sealer = this.sealersAround.get(sideVec); if (sealer != null && !this.sealers.contains(sealer)) { if (side == 0) { //Accessing the vent side of the sealer, so add it this.otherSealers.add(sealer); checkedAdd(sideVec); } //if side is not 0, do not add to checked so can be rechecked from other sides } else { checkedAdd(sideVec); } } else { if (id != null && id != airBlock && id != airBlockBright) { //This test applies any necessary checkedAdd(); if (this.canBlockPassAirCheck(id, sideVec, side)) { //Look outbound through partially sealable blocks in case there is breatheableAir to clear beyond nextLayer.add(sideVec); } } else { if (id != null) checkedAdd(sideVec); } } } } side++; } while (side < 6); } // Set up the next layer as current layer for the while loop this.currentLayer = nextLayer; nextLayer = new LinkedList<BlockVec3>(); } } /** * Literally the only difference from unseal() should be this: * Block id = sideVec.getBlockID_noChunkLoad(world); * * In this code, there is a map edge check on the x, z coordinates (outside map edge at 30,000,000 blocks?) * This check is skipped in the "safe" version of the same code, for higher performance * because doing this check 50000 times when looking at blocks around a sealer at spawn is obviously dumb */ private void unsealNearMapEdge() { //Local variables are fractionally faster than statics Block breatheableAirID = GCBlocks.breatheableAir; Block breatheableAirIDBright = GCBlocks.brightBreatheableAir; Block oxygenSealerID = GCBlocks.oxygenSealer; Block fireBlock = Blocks.fire; Block airBlock = Blocks.air; Block airBlockBright = GCBlocks.brightAir; List<BlockVec3> toReplaceLocal = this.breatheableToReplace; LinkedList nextLayer = new LinkedList<BlockVec3>(); World world = this.world; int side, bits; while (this.currentLayer.size() > 0) { for (BlockVec3 vec : this.currentLayer) { side = 0; bits = vec.sideDoneBits; do { if ((bits & (1 << side)) == 0) { if (!checkedContains(vec, side)) { BlockVec3 sideVec = vec.newVecSide(side); Block id = sideVec.getBlockID_noChunkLoad(world); if (id == breatheableAirID) { toReplaceLocal.add(sideVec); nextLayer.add(sideVec); checkedAdd(sideVec); } else if (id == breatheableAirIDBright) { this.breatheableToReplaceBright.add(sideVec); nextLayer.add(sideVec); checkedAdd(sideVec); } else if (id == fireBlock) { this.fireToReplace.add(sideVec); nextLayer.add(sideVec); checkedAdd(sideVec); } else if (id == oxygenSealerID) { TileEntityOxygenSealer sealer = this.sealersAround.get(sideVec); if (sealer != null && !this.sealers.contains(sealer)) { if (side == 0) { //Accessing the vent side of the sealer, so add it this.otherSealers.add(sealer); checkedAdd(sideVec); } //if side is not 0, do not add to checked so can be rechecked from other sides } else { checkedAdd(sideVec); } } else { if (id != null && id != airBlock && id != airBlockBright) { //This test applies any necessary checkedAdd(); if (this.canBlockPassAirCheck(id, sideVec, side)) { //Look outbound through partially sealable blocks in case there is breatheableAir to clear beyond nextLayer.add(sideVec); } } else { if (id != null) checkedAdd(sideVec); } } } } side++; } while (side < 6); } // Set up the next layer as current layer for the while loop this.currentLayer = nextLayer; nextLayer = new LinkedList<BlockVec3>(); } } private void doLayer() { //Local variables are fractionally faster than statics Block breatheableAirID = GCBlocks.breatheableAir; Block airID = Blocks.air; Block breatheableAirIDBright = GCBlocks.brightBreatheableAir; Block airIDBright = GCBlocks.brightAir; Block oxygenSealerID = GCBlocks.oxygenSealer; LinkedList nextLayer = new LinkedList<BlockVec3>(); World world = this.world; int side, bits; while (this.sealed && this.currentLayer.size() > 0) { for (BlockVec3 vec : this.currentLayer) { //This is for side = 0 to 5 - but using do...while() is fractionally quicker side = 0; bits = vec.sideDoneBits; do { //Skip the side which this was entered from //This is also used to skip looking on the solid sides of partially sealable blocks if ((bits & (1 << side)) == 0) { // The sides 0 to 5 correspond with the EnumFacings // but saves a bit of time not to call EnumFacing if (!checkedContains(vec, side)) { BlockVec3 sideVec = vec.newVecSide(side); if (this.checkCount > 0) { this.checkCount--; Block id = sideVec.getBlockIDsafe_noChunkLoad(world); // The most likely case if (id == breatheableAirID) { checkedAdd(sideVec); nextLayer.add(sideVec); this.ambientThermalTracked.add(sideVec); } else if (id == airID) { checkedAdd(sideVec); nextLayer.add(sideVec); this.airToReplace.add(sideVec); } else if (id == breatheableAirIDBright) { checkedAdd(sideVec); nextLayer.add(sideVec); this.ambientThermalTrackedBright.add(sideVec); } else if (id == airIDBright) { checkedAdd(sideVec); nextLayer.add(sideVec); this.airToReplaceBright.add(sideVec); } else if (id == null) { // Broken through to the void or the // stratosphere (above y==255) - set // unsealed and abort this.checkCount = 0; this.sealed = false; return; } else if (id == oxygenSealerID) { TileEntityOxygenSealer sealer = this.sealersAround.get(sideVec); if (sealer != null && !this.sealers.contains(sealer)) { if (side == 0) { checkedAdd(sideVec); this.sealers.add(sealer); if (sealer.thermalControlEnabled()) { foundAmbientThermal = true; } this.checkCount += sealer.getFindSealChecks(); } //if side != 0, no checkedAdd() - allows this sealer to be checked again from other sides } } else if (this.canBlockPassAirCheck(id, sideVec, side)) { nextLayer.add(sideVec); } //If the chunk was unloaded, BlockVec3.getBlockID returns Blocks.bedrock //which is a solid block, so the loop will treat that as a sealed edge //and not iterate any further in that direction } // the if (this.isSealed) check here is unnecessary because of the returns else { Block id = sideVec.getBlockIDsafe_noChunkLoad(this.world); // id == null means the void or height y>255, both // of which are unsealed obviously if (id == null || id == airID || id == breatheableAirID || id == airIDBright || id == breatheableAirIDBright || this.canBlockPassAirCheck(id, sideVec, side)) { this.sealed = false; if (this.sealers.size() > 0) { vec.sideDoneBits = side << 6; traceLeak(vec); } return; } } } } side++; } while (side < 6); } // Is there a further layer of air/permeable blocks to test? this.currentLayer = nextLayer; nextLayer = new LinkedList<BlockVec3>(); } } /** * Again, literally the only difference from doLayer() should be these two lines: * Block id = sideVec.getBlockID_noChunkLoad(world); * * In this code, there is a map edge check on the x, z coordinates (outside map edge at 30,000,000 blocks?) * This check is skipped in the "safe" version of the same code, for higher performance * because doing this check 50000 times when looking at blocks around a sealer at spawn is obviously dumb */ private void doLayerNearMapEdge() { //Local variables are fractionally faster than statics Block breatheableAirID = GCBlocks.breatheableAir; Block airID = Blocks.air; Block breatheableAirIDBright = GCBlocks.brightBreatheableAir; Block airIDBright = GCBlocks.brightAir; Block oxygenSealerID = GCBlocks.oxygenSealer; LinkedList nextLayer = new LinkedList<BlockVec3>(); World world = this.world; int side, bits; while (this.sealed && this.currentLayer.size() > 0) { for (BlockVec3 vec : this.currentLayer) { //This is for side = 0 to 5 - but using do...while() is fractionally quicker side = 0; bits = vec.sideDoneBits; do { //Skip the side which this was entered from //This is also used to skip looking on the solid sides of partially sealable blocks if ((bits & (1 << side)) == 0) { // The sides 0 to 5 correspond with the EnumFacings // but saves a bit of time not to call EnumFacing if (!checkedContains(vec, side)) { BlockVec3 sideVec = vec.newVecSide(side); if (this.checkCount > 0) { this.checkCount--; Block id = sideVec.getBlockID_noChunkLoad(world); // The most likely case if (id == breatheableAirID) { checkedAdd(sideVec); nextLayer.add(sideVec); this.ambientThermalTracked.add(sideVec); } else if (id == airID) { checkedAdd(sideVec); nextLayer.add(sideVec); this.airToReplace.add(sideVec); } else if (id == breatheableAirIDBright) { checkedAdd(sideVec); nextLayer.add(sideVec); this.ambientThermalTrackedBright.add(sideVec); } else if (id == airIDBright) { checkedAdd(sideVec); nextLayer.add(sideVec); this.airToReplaceBright.add(sideVec); } else if (id == null) { // Broken through to the void or the // stratosphere (above y==255) - set // unsealed and abort this.checkCount = 0; this.sealed = false; return; } else if (id == oxygenSealerID) { TileEntityOxygenSealer sealer = this.sealersAround.get(sideVec); if (sealer != null && !this.sealers.contains(sealer)) { if (side == 0) { checkedAdd(sideVec); this.sealers.add(sealer); if (sealer.thermalControlEnabled()) { foundAmbientThermal = true; } this.checkCount += sealer.getFindSealChecks(); } //if side != 0, no checkedAdd() - allows this sealer to be checked again from other sides } } else if (this.canBlockPassAirCheck(id, sideVec, side)) { nextLayer.add(sideVec); } //If the chunk was unloaded, BlockVec3.getBlockID returns Blocks.bedrock //which is a solid block, so the loop will treat that as a sealed edge //and not iterate any further in that direction } // the if (this.isSealed) check here is unnecessary because of the returns else { Block id = sideVec.getBlockID_noChunkLoad(this.world); // id == null means the void or height y>255, both // of which are unsealed obviously if (id == null || id == airID || id == breatheableAirID || id == airIDBright || id == breatheableAirIDBright || this.canBlockPassAirCheck(id, sideVec, side)) { this.sealed = false; if (this.sealers.size() > 0) { vec.sideDoneBits = side << 6; traceLeak(vec); } return; } } } } side++; } while (side < 6); } // Is there a further layer of air/permeable blocks to test? this.currentLayer = nextLayer; nextLayer = new LinkedList<BlockVec3>(); } } private void checkedAdd(BlockVec3 vec) { int dx = this.head.x - vec.x; int dz = this.head.z - vec.z; if (dx < -8191 || dx > 8192) return; if (dz < -8191 || dz > 8192) return; intBucket bucket = buckets[((dx & 15) << 4) + (dz & 15)]; bucket.add(vec.y + ((dx & 0x3FF0) + ((dz & 0x3FF0) << 10) + ((vec.sideDoneBits & 0x1C0) << 18) << 4)); } /** * Currently unused - the sided implementation is used instead */ private boolean checkedContains(BlockVec3 vec) { int dx = this.head.x - vec.x; int dz = this.head.z - vec.z; if (dx < -8191 || dx > 8192) return true; if (dz < -8191 || dz > 8192) return true; intBucket bucket = buckets[((dx & 15) << 4) + (dz & 15)]; return bucket.contains(vec.y + ((dx & 0x3FF0) + ((dz & 0x3FF0) << 10) << 4)); } private boolean checkedContains(BlockVec3 vec, int side) { int y = vec.y; int dx = this.head.x - vec.x; int dz = this.head.z - vec.z; switch (side) { case 0: y--; if (y < 0) return false; break; case 1: y++; if (y > 255) return false; break; case 2: dz++; break; case 3: dz--; break; case 4: dx++; break; case 5: dx--; } if (dx < -8191 || dx > 8192) return true; if (dz < -8191 || dz > 8192) return true; intBucket bucket = buckets[((dx & 15) << 4) + (dz & 15)]; return bucket.contains(y + ((dx & 0x3FF0) + ((dz & 0x3FF0) << 10) << 4)); } private BlockVec3 checkedContainsTrace(int x, int y, int z) { int dx = this.head.x - x; int dz = this.head.z - z; if (dx < -8191 || dx > 8192) return null; if (dz < -8191 || dz > 8192) return null; intBucket bucket = buckets[((dx & 15) << 4) + (dz & 15)]; int side = bucket.getMSB4shifted(y + ((dx & 0x3FF0) + ((dz & 0x3FF0) << 10) << 4)); if (side >= 0) { BlockVec3 vec = new BlockVec3(x, y, z); vec.sideDoneBits = side; return vec; } return null; } private static void checkedInit() { for (int i = 0; i < 256; i++) { buckets[i] = new intBucket(); } } private static void checkedClear() { for (int i = 0; i < 256; i++) { buckets[i].clear(); } checkedSize = 0; } public List<BlockPos> checkedAll() { List<BlockPos> list = new LinkedList<BlockPos>(); for (int i = 0; i < 256; i++) { if (this.buckets[i].size() == 0) continue; int ddx = i >> 4; int ddz = i & 15; int[] ints = this.buckets[i].contents(); for (int j = 0; j < this.buckets[i].size(); j++) { int k = ints[j]; int y = k & 255; k >>= 4; int dx = (k & 0x3FF0) + ddx; int dz = ((k >> 10) & 0x3FF0) + ddz; if (dx > 0x2000) dx -= 0x4000; if (dz > 0x2000) dz -= 0x4000; list.add(new BlockPos(head.x + dx, y, head.z + dz)); } } return list; } private void traceLeak(BlockVec3 tracer) { ArrayList<BlockVec3> route = new ArrayList(); BlockVec3 start = this.head.clone().translate(0, 1, 0); int count = 0; int x = tracer.x; int y = tracer.y; int z = tracer.z; while (!tracer.equals(start) && count < 90) { route.add(tracer); switch(tracer.sideDoneBits >> 6) { case 1: y--; break; case 0: y++; break; case 3: z--; break; case 2: z++; break; case 5: x--; break; case 4: x++; break; } tracer = checkedContainsTrace(x, y, z); if (tracer == null) { return; } count ++; } this.leakTrace = new ArrayList(); this.leakTrace.add(start); for (int j = route.size() - 1; j >= 0; j--) { this.leakTrace.add(route.get(j)); } } private boolean canBlockPassAirCheck(Block block, BlockVec3 vec, int side) { //Check leaves first, because their isOpaqueCube() test depends on graphics settings //(See net.minecraft.block.BlockLeaves.isOpaqueCube()!) if (block instanceof BlockLeavesBase) { checkedAdd(vec); return true; } if (block.isOpaqueCube()) { checkedAdd(vec); //Gravel, wool and sponge are porous return block instanceof BlockGravel || block.getMaterial() == Material.cloth || block instanceof BlockSponge; } if (block instanceof BlockGlass || block instanceof BlockStainedGlass) { checkedAdd(vec); return false; } //Solid but non-opaque blocks, for example special glass if (OxygenPressureProtocol.nonPermeableBlocks.containsKey(block)) { ArrayList<Integer> metaList = OxygenPressureProtocol.nonPermeableBlocks.get(block); if (metaList.contains(Integer.valueOf(-1)) || metaList.contains(vec.getBlockMetadata(this.world))) { checkedAdd(vec); return false; } } if (block instanceof IPartialSealableBlock) { EnumFacing testSide = EnumFacing.getFront(side); IPartialSealableBlock blockPartial = (IPartialSealableBlock) block; BlockPos vecPos = new BlockPos(vec.x, vec.y, vec.z); if (blockPartial.isSealed(this.world, vecPos, testSide)) { // If a partial block checks as solid, allow it to be tested // again from other directions // This won't cause an endless loop, because the block won't // be included in nextLayer if it checks as solid this.checkCount--; return false; } //Find the solid sides so they don't get iterated into, when doing the next layer for (EnumFacing face : EnumFacing.values()) { if (face == testSide) { continue; } if (blockPartial.isSealed(this.world, vecPos, face)) { vec.setSideDone(face.getIndex() ^ 1); } } checkedAdd(vec); return true; } if (block instanceof BlockUnlitTorch) { this.torchesToUpdate.add(vec); checkedAdd(vec); return true; } //Half slab seals on the top side or the bottom side according to its metadata if (block instanceof BlockSlab) { boolean isTopSlab = (vec.getBlockMetadata(this.world) & 8) == 8; //Looking down onto a top slab or looking up onto a bottom slab if (side == 0 && isTopSlab || side == 1 && !isTopSlab) { //Sealed from that solid side but allow other sides still to be checked this.checkCount--; return false; } //Not sealed vec.setSideDone(isTopSlab ? 1 : 0); checkedAdd(vec); return true; } //Farmland etc only seals on the solid underside if (block instanceof BlockFarmland || block instanceof BlockEnchantmentTable || block instanceof BlockLiquid) { if (side == 1) { //Sealed from the underside but allow other sides still to be checked this.checkCount--; return false; } //Not sealed vec.setSideDone(0); checkedAdd(vec); return true; } if (block instanceof BlockPistonBase) { BlockPistonBase piston = (BlockPistonBase) block; IBlockState state = this.world.getBlockState(new BlockPos(vec.x, vec.y, vec.z)); if (((Boolean) state.getValue(BlockPistonBase.EXTENDED)).booleanValue()) { int facing = ((EnumFacing) state.getValue(BlockPistonBase.FACING)).getIndex(); if (side == facing) { this.checkCount--; return false; } vec.setSideDone(facing ^ 1); checkedAdd(vec); return true; } checkedAdd(vec); return false; } //General case - this should cover any block which correctly implements isBlockSolidOnSide //including most modded blocks - Forge microblocks in particular is covered by this. // ### Any exceptions in mods should implement the IPartialSealableBlock interface ### if (block.isSideSolid(this.world, new BlockPos(vec.x, vec.y, vec.z), EnumFacing.getFront(side ^ 1))) { //Solid on all sides if (block.getMaterial().blocksMovement() && block.isFullCube()) { checkedAdd(vec); return false; } //Sealed from this side but allow other sides still to be checked this.checkCount--; return false; } //Easy case: airblock, return without checking other sides if (block.getMaterial() == Material.air) { checkedAdd(vec); return true; } //Not solid on that side. //Look to see if there is any other side which is solid in which case a check will not be needed next time for (int i = 0; i < 6; i++) { if (i == (side ^ 1)) { continue; } if (block.isSideSolid(this.world, new BlockPos(vec.x, vec.y, vec.z), EnumFacing.getFront(i))) { vec.setSideDone(i); } } //Not solid from this side, so this is not sealed checkedAdd(vec); return true; } public static class intBucket { private int maxSize = 64; //default size private int size = 0; private int[] table = new int[maxSize]; public void add(int i) { if (this.contains(i)) return; if (size >= maxSize) { int[] newTable = new int[maxSize + maxSize]; System.arraycopy(table, 0, newTable, 0, maxSize); table = newTable; maxSize += maxSize; } table[size] = i; size++; checkedSize++; } public boolean contains(int test) { for (int i = size - 1; i >= 0; i--) { if ((table[i] & 0xFFFFFFF) == test) return true; } return false; } public int getMSB4shifted(int test) { for (int i = size - 1; i >= 0; i--) { if ((table[i] & 0xFFFFFFF) == test) { return (table[i] & 0xF0000000) >> 22; } } return -1; } public void clear() { size = 0; } public int size() { return size; } public int[] contents() { return table; } } }