From b19fd441f8e7f3e71d60b916ef005cdd119934c0 Mon Sep 17 00:00:00 2001 From: Zontreck Date: Wed, 19 Oct 2022 17:36:50 -0700 Subject: [PATCH] Fixed the healing code --- build.gradle | 14 -- gradle.properties | 1 - src/main/java/dev/zontreck/otemod/OTEMod.java | 2 +- .../zontreck/otemod/antigrief/Handler.java | 20 +- .../zontreck/otemod/antigrief/HealRunner.java | 72 ++++++ .../otemod/antigrief/HealerManager.java | 233 ++++++++++++------ .../otemod/antigrief/HealerQueue.java | 108 +++++++- .../otemod/antigrief/HealerWorker.java | 54 ++++ .../otemod/antigrief/StoredBlock.java | 78 +++++- 9 files changed, 486 insertions(+), 96 deletions(-) create mode 100644 src/main/java/dev/zontreck/otemod/antigrief/HealRunner.java create mode 100644 src/main/java/dev/zontreck/otemod/antigrief/HealerWorker.java diff --git a/build.gradle b/build.gradle index b9c4a82..62a3ad5 100644 --- a/build.gradle +++ b/build.gradle @@ -104,16 +104,6 @@ repositories { // } mavenCentral() - maven { - // location of the maven that hosts JEI files - name = "Progwml6 maven" - url = "https://dvs1.progwml6.com/files/maven/" - } - maven { - // location of a maven mirror for JEI files, as a fallback - name = "ModMaven" - url = "https://modmaven.dev" - } maven { name = "CurseMaven" url = "https://cursemaven.com" @@ -131,10 +121,6 @@ dependencies { // The userdev artifact is a special name and will get all sorts of transformations applied to it. minecraft "net.minecraftforge:forge:${mc_version}-${forge_version}" - compileOnly fg.deobf("mezz.jei:jei-${mc_version}-common-api:${jei_version}") - compileOnly fg.deobf("mezz.jei:jei-${mc_version}-forge-api:${jei_version}") - runtimeOnly fg.deobf("mezz.jei:jei-${mc_version}-forge:${jei_version}") - implementation fg.deobf("dev.zontreck:libzontreck:${libz_version}:dev") compileOnly fg.deobf("dev.zontreck:libzontreck:${libz_version}:dev") runtimeOnly fg.deobf("dev.zontreck:libzontreck:${libz_version}") diff --git a/gradle.properties b/gradle.properties index 28cb2dc..eb0f883 100644 --- a/gradle.properties +++ b/gradle.properties @@ -7,5 +7,4 @@ my_version=1.3.4.4 mc_version=1.19.2 forge_version=43.1.40 -jei_version=11.3.0.271 libz_version=1.0.1.2 \ No newline at end of file diff --git a/src/main/java/dev/zontreck/otemod/OTEMod.java b/src/main/java/dev/zontreck/otemod/OTEMod.java index 70603e5..5fae119 100644 --- a/src/main/java/dev/zontreck/otemod/OTEMod.java +++ b/src/main/java/dev/zontreck/otemod/OTEMod.java @@ -69,7 +69,7 @@ public class OTEMod public static Map PROFILES = new HashMap(); public static List TeleportRegistry = new ArrayList<>(); public static MinecraftServer THE_SERVER; - public static boolean ALIVE; + public static boolean ALIVE=false; public static boolean HEALER_WAIT=true; // Only on loading finish should this unlock public static Thread HEALER_THREAD; diff --git a/src/main/java/dev/zontreck/otemod/antigrief/Handler.java b/src/main/java/dev/zontreck/otemod/antigrief/Handler.java index e041b27..8a1e48f 100644 --- a/src/main/java/dev/zontreck/otemod/antigrief/Handler.java +++ b/src/main/java/dev/zontreck/otemod/antigrief/Handler.java @@ -3,6 +3,7 @@ package dev.zontreck.otemod.antigrief; import java.util.ArrayList; import java.util.Collection; import java.util.LinkedList; +import java.util.List; import dev.zontreck.libzontreck.vectors.Vector3; import dev.zontreck.libzontreck.vectors.WorldPosition; @@ -60,7 +61,7 @@ public class Handler if(exploder==null)return ; // TODO: Make this check config final Collection affectedBlocks = buildBlocks(level, event.getAffectedBlocks()); - final Collection toHeal = new ArrayList(affectedBlocks.size()); + Collection toHeal = new ArrayList(affectedBlocks.size()); Block tnt = Blocks.TNT; @@ -96,7 +97,22 @@ public class Handler } // Add to the healing queue - HealerQueue.ToHeal.addAll(toHeal); + List mainList = new ArrayList<>(); + mainList.addAll(toHeal); + mainList = HealerQueue.removeSame(mainList); + + HealerWorker work = new HealerWorker(mainList); + HealerQueue.ManagerInstance.registerWorker(work); + Thread tx = new Thread(work); + tx.start(); + + HealerQueue.ToHeal.addAll(mainList); + + + HealerQueue.Pass=0; + HealerQueue.ToHeal.addAll(HealerQueue.ToValidate); + HealerQueue.ToValidate = new ArrayList<>(); + HealerQueue.Shuffle(); } diff --git a/src/main/java/dev/zontreck/otemod/antigrief/HealRunner.java b/src/main/java/dev/zontreck/otemod/antigrief/HealRunner.java new file mode 100644 index 0000000..7be91b1 --- /dev/null +++ b/src/main/java/dev/zontreck/otemod/antigrief/HealRunner.java @@ -0,0 +1,72 @@ +package dev.zontreck.otemod.antigrief; + +import java.util.Random; + +import dev.zontreck.libzontreck.vectors.Vector3; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.sounds.SoundEvent; +import net.minecraft.sounds.SoundEvents; +import net.minecraft.sounds.SoundSource; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; + +public class HealRunner implements Runnable +{ + public final StoredBlock BlockToSet; + // Play a popping sound at the block position + public final SoundEvent pop = SoundEvents.ITEM_PICKUP; + + + public HealRunner(StoredBlock sb) + { + BlockToSet = sb; + } + public static void scheduleHeal(StoredBlock sb){ + sb.getWorldPosition().getActualDimension().getServer().execute(new HealRunner(sb)); + } + @Override + public void run() + { + + //BlockSnapshot bs = BlockSnapshot.create(level.dimension(), level, sb.getPos()); + + //BlockState current = level.getBlockState(sb.getPos()); + final ServerLevel level = BlockToSet.getWorldPosition().getActualDimension(); + + BlockState nState = Block.updateFromNeighbourShapes(BlockToSet.getState(), level, BlockToSet.getPos()); + level.setBlock(BlockToSet.getPos(), nState, Block.UPDATE_CLIENTS); // no update? + + + //level.setBlocksDirty(sb.getPos(), sb.getState(), level.getBlockState(sb.getPos())); + //level.markAndNotifyBlock(sb.getPos(), level.getChunkAt(sb.getPos()), sb.getState(), level.getBlockState(sb.getPos()), 2, 0); + + //level.getChunkAt(sb.getPos()).setBlockState(sb.getPos(), sb.getState(), false); + + BlockEntity be = level.getBlockEntity(BlockToSet.getPos()); + + if(be!=null){ + //be.deserializeNBT(sb.getBlockEntity()); + be.load(BlockToSet.getBlockEntity()); + be.setChanged(); + + } + + // Everything is restored, play sound + SoundSource ss = SoundSource.NEUTRAL; + Vector3 v3 = BlockToSet.getWorldPosition().Position; + Random rng = new Random(); + + level.playSound(null, v3.asBlockPos(), pop, ss, rng.nextFloat(0.75f,1.0f), rng.nextFloat(1)); + + /*for(ServerPlayer player : level.players()) + { + Vector3 playerPos = new Vector3(player.position()); + if(sb.getWorldPosition().Position.distance(playerPos) < 15) + { + // have player's client play sound (Packet?) + } + }*/ + + } +} diff --git a/src/main/java/dev/zontreck/otemod/antigrief/HealerManager.java b/src/main/java/dev/zontreck/otemod/antigrief/HealerManager.java index 23712fa..ebb23c1 100644 --- a/src/main/java/dev/zontreck/otemod/antigrief/HealerManager.java +++ b/src/main/java/dev/zontreck/otemod/antigrief/HealerManager.java @@ -2,9 +2,12 @@ package dev.zontreck.otemod.antigrief; import java.io.IOException; import java.time.Instant; +import java.util.ArrayList; +import java.util.List; import java.util.Random; import dev.zontreck.libzontreck.vectors.Vector3; +import dev.zontreck.libzontreck.vectors.WorldPosition; import dev.zontreck.otemod.OTEMod; import dev.zontreck.otemod.configs.OTEServerConfig; import net.minecraft.server.commands.SetBlockCommand; @@ -15,6 +18,7 @@ import net.minecraft.sounds.SoundEvents; import net.minecraft.sounds.SoundSource; import net.minecraft.world.entity.item.FallingBlockEntity; import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.FallingBlock; import net.minecraft.world.level.block.SandBlock; import net.minecraft.world.level.block.entity.BlockEntity; @@ -23,8 +27,18 @@ import net.minecraftforge.common.util.BlockSnapshot; public class HealerManager implements Runnable { + private List Workers = new ArrayList<>(); public HealerManager(){ + } + public void registerWorker(HealerWorker worker) + { + Workers.add(worker); + } + + public void deregisterWorker(HealerWorker worker) + { + Workers.remove(worker); } @Override public void run(){ @@ -33,34 +47,159 @@ public class HealerManager implements Runnable long lastSave = 0; final long saveInterval = (2*60); // Every 2 minutes boolean lastWait = false; + // Heal pass 1 is set all positions to bedrock + // Pass 2 is assert all solid blocks (Not air) + // Pass 3 is to set the air blocks and remove bedrock + while(OTEMod.ALIVE) { try{ - // Run the queue - // We want to restore one block per run, then halt for number of seconds in config - try { - if(!skipWait) - Thread.sleep(OTEServerConfig.HEALER_TIMER.get()); - } catch (NumberFormatException e) { - e.printStackTrace(); - } catch (InterruptedException e) { - e.printStackTrace(); - } - if(lastWait != OTEMod.HEALER_WAIT){ - OTEMod.LOGGER.info("Healer wait flag was toggled"); - } - lastWait = OTEMod.HEALER_WAIT; - if(OTEMod.HEALER_WAIT) - continue; // Wait until the saved queue has been fully imported + try{ + if(!skipWait) Thread.sleep(OTEServerConfig.HEALER_TIMER.get()); + }catch(Exception E){} - if(!OTEMod.ALIVE) - { - // Server has begun to shut down while we were sleeping - // Begin tear down - break; + if(lastWait != OTEMod.HEALER_WAIT) OTEMod.LOGGER.info("Healer Wait Flag was Toggled"); + lastWait = OTEMod.HEALER_WAIT; + if(OTEMod.HEALER_WAIT) continue; // Wait to process until import completes + + if(!OTEMod.ALIVE) break; // Begin tear down, server has shut down + + for (HealerWorker healerWorker : Workers) { + healerWorker.doTick=true; } + if(HealerQueue.ToHeal.size()==0 && HealerQueue.ToValidate.size()==0) + { + HealerQueue.Pass=0; + if(HealerQueue.dirty()) + HealerQueue.dump(); + continue; + } + + // Get the first block in the list + final StoredBlock sb = HealerQueue.locateLowestBlock(HealerQueue.ToHeal); + ServerLevel level = null; + StoredBlock below = null; + + if(sb != null) + { + level = sb.getWorldPosition().getActualDimension(); + below = HealerQueue.getExact(new WorldPosition(sb.getWorldPosition().Position.moveDown(), level)); + } + + + switch(HealerQueue.Pass) + { + case 0: + { + // Pass 1. Set all positions to bedrock + // The code will check is the block is solid. If the block below it is not solid, it will set it to air, regardless of if it is a falling block of not + + if(HealerQueue.ToHeal.size()==0) + { + // Move the validate list back into healer queue, and increment pass + OTEMod.LOGGER.info("Pass 1 completed, moving to pass 2"); + HealerQueue.Pass=1; + HealerQueue.ToHeal = HealerQueue.ToValidate; + HealerQueue.ToValidate = new ArrayList<>(); + HealerQueue.dump(); + break; // Exit this loop + } + + if(below == null){ + // This line will prevent the block below from getting set to Sculk + below = StoredBlock.getSculk(new WorldPosition(sb.getWorldPosition().Position.moveDown(), level)); // below is null so it is a unknown, accept a loss if its a falling block + } + + + if(!sb.getState().isAir() && below.getState().isAir()) + { + HealRunner.scheduleHeal(StoredBlock.getSculk(below.getWorldPosition())); + skipWait=false; + }else { + if(!sb.getState().isAir()) + { + HealRunner.scheduleHeal(StoredBlock.getSculk(sb.getWorldPosition())); + skipWait=false; + } else { + skipWait=true; + } + } + HealerQueue.ToValidate.add(sb); + HealerQueue.ToHeal.remove(sb); + break; + } + case 1: + { + // Pass 2 only sets the solid blocks + if(HealerQueue.ToHeal.size()==0) + { + OTEMod.LOGGER.info("Pass 2 completed, moving to pass 3"); + HealerQueue.Pass++; + HealerQueue.ToHeal = HealerQueue.ToValidate; + HealerQueue.ToValidate = new ArrayList<>(); + HealerQueue.dump(); + break; + } + + if(!sb.getState().isAir()) + { + skipWait=false; + HealRunner.scheduleHeal(sb); + }else{ + skipWait=true; + + HealerQueue.ToValidate.add(sb); + } + HealerQueue.ToHeal.remove(sb); + break; + } + + case 2: + { + // Pass 3 removes bedrock by setting blocks that are air + if(HealerQueue.ToHeal.size()==0) + { + OTEMod.LOGGER.info("Pass 3 has been completed. Ending restore"); + HealerQueue.Pass=0; + HealerQueue.ToHeal.clear(); + HealerQueue.ToValidate.clear(); + HealerQueue.dump(); + break; + } + HealerQueue.ToHeal.remove(sb); + + if(sb.getState().isAir()) + { + BlockState bs = sb.getWorldPosition().getActualDimension().getBlockState(sb.getPos()); + if(!bs.isAir() && !bs.is(Blocks.SCULK)) + { + skipWait=true; + continue; + } + if(!bs.isAir()){ + skipWait=false; + HealRunner.scheduleHeal(sb); + }else skipWait=true; + }else skipWait=true; + break; + } + default: + { + HealerQueue.Pass=0; + OTEMod.LOGGER.info("/!\\ ALERT /!\\\n\nWARNING: Unknown pass operation was added to the HealQueue"); + break; + } + } + + }catch(Exception e){} + } + OTEMod.ALIVE=false; + /*while(OTEMod.ALIVE) // do nothing, code is disabled here. + { + try{ + // Loop back to start if no items in queue if(HealerQueue.ToHeal.size()==0){ @@ -94,13 +233,10 @@ public class HealerManager implements Runnable continue; } - // Play a popping sound at the block position - final SoundEvent pop = SoundEvents.ITEM_PICKUP; // Get the first block in the list final StoredBlock sb = HealerQueue.locateHighestBlock(HealerQueue.ToHeal); final ServerLevel level = sb.getWorldPosition().getActualDimension(); - // Remove the block from the queue now to prevent further issues if( !HealerQueue.ToValidate.add(sb) ) { @@ -128,53 +264,6 @@ public class HealerManager implements Runnable skipCount++; } }else skipCount=0; - - - level.getServer().execute(new Runnable(){ - public void run() - { - - //BlockSnapshot bs = BlockSnapshot.create(level.dimension(), level, sb.getPos()); - - //BlockState current = level.getBlockState(sb.getPos()); - BlockState nState = Block.updateFromNeighbourShapes(sb.getState(), level, sb.getPos()); - level.setBlock(sb.getPos(), nState, Block.UPDATE_CLIENTS | Block.UPDATE_KNOWN_SHAPE); // no update? - - - //level.setBlocksDirty(sb.getPos(), sb.getState(), level.getBlockState(sb.getPos())); - //level.markAndNotifyBlock(sb.getPos(), level.getChunkAt(sb.getPos()), sb.getState(), level.getBlockState(sb.getPos()), 2, 0); - - //level.getChunkAt(sb.getPos()).setBlockState(sb.getPos(), sb.getState(), false); - - BlockEntity be = level.getBlockEntity(sb.getPos()); - - if(be!=null){ - //be.deserializeNBT(sb.getBlockEntity()); - be.load(sb.getBlockEntity()); - be.setChanged(); - - } - - // Everything is restored, play sound - SoundSource ss = SoundSource.NEUTRAL; - Vector3 v3 = sb.getWorldPosition().Position; - Random rng = new Random(); - - level.playSound(null, v3.asBlockPos(), pop, ss, rng.nextFloat(0.75f,1.0f), rng.nextFloat(1)); - - /*for(ServerPlayer player : level.players()) - { - Vector3 playerPos = new Vector3(player.position()); - if(sb.getWorldPosition().Position.distance(playerPos) < 15) - { - // have player's client play sound (Packet?) - } - }*/ - - - - } - }); @@ -197,7 +286,7 @@ public class HealerManager implements Runnable e.printStackTrace(); } - } + }*/ OTEMod.LOGGER.info("Tearing down healer jobs. Saving remaining queue, stand by..."); try { diff --git a/src/main/java/dev/zontreck/otemod/antigrief/HealerQueue.java b/src/main/java/dev/zontreck/otemod/antigrief/HealerQueue.java index 555716e..c76c6dc 100644 --- a/src/main/java/dev/zontreck/otemod/antigrief/HealerQueue.java +++ b/src/main/java/dev/zontreck/otemod/antigrief/HealerQueue.java @@ -31,10 +31,18 @@ public class HealerQueue { public static final String HealerQueueFile = "OTEHealerLastQueue.nbt"; public static final String HealerQueueDebugFile = "OTEHealerLastQueue.snbt"; - public static List ToHeal = new ArrayList(); - public static List ToValidate = new ArrayList(); - public static List FinishedBlocks = new ArrayList(); + public static List ToHeal = new ArrayList(); // Air and Solid Blocks get set to bedrock initially + public static List ToValidate = new ArrayList(); // This contains all the blocks except air + public static int Pass = 0; + + + private static List LastToHeal = new ArrayList(); + private static List LastToValidate = new ArrayList(); + private static int LastPass = 0; + + public static HealerManager ManagerInstance=null; + public static Path getPath() { @@ -68,6 +76,40 @@ public class HealerQueue { return sb; } + public static StoredBlock locateLowestBlock(List list) + { + StoredBlock sb = null; + double currentY = 300; + for (StoredBlock storedBlock : ToHeal) { + if(storedBlock.getWorldPosition().Position.y < currentY) + { + currentY = storedBlock.getWorldPosition().Position.y; + sb=storedBlock; + } + } + + return sb; + } + + public static StoredBlock getExact(WorldPosition wp) + { + for (StoredBlock storedBlock : ToHeal) { + if(storedBlock.getWorldPosition().same(wp)) + { + return storedBlock; + } + } + for (StoredBlock storedBlock : ToValidate) { + if(storedBlock.getWorldPosition().same(wp)) + { + return storedBlock; + } + } + + return null; + } + + public static boolean HasValidatePosition(BlockPos pos, ServerLevel lvl) { Vector3 realPos = new Vector3(pos); @@ -134,7 +176,8 @@ public class HealerQueue { tx.start(); // Set up the HealerManager / Runner - Thread txx = new Thread(new HealerManager()); + ManagerInstance = new HealerManager(); + Thread txx = new Thread(ManagerInstance); txx.start(); OTEMod.HEALER_THREAD = txx; @@ -157,6 +200,7 @@ public class HealerQueue { tag.put("queue", lst); tag.put("validate", lst2); + tag.putInt("pass", HealerQueue.Pass); //OTEMod.LOGGER.info("HEAL ["+HealerQueue.ToHeal.size()+"] / VALIDATE ["+HealerQueue.ToValidate.size()+"]"); @@ -165,6 +209,48 @@ public class HealerQueue { return tag; } + public static List removeSame(List other) + { + other=removeSameFrom(ToHeal, other); + other=removeSameFrom(ToValidate,other); + + return other; + } + + public static List removeSameFrom(List stored, List other) + { + + for(int i = 0;i proc; + public boolean alive=false; // These are the + public boolean doTick = false; + public Thread MyThread; + public HealerWorker(List toProcess) + { + // TODO: Make this a individualized heal worker that does the task of the current HealerManager, but with a central list of positions for de-duplication. But that a second worker would not process the same positions + // The goal is to have a worker so a second explosion elsewhere does not reset the entire thing + for (StoredBlock sBlock : toProcess) { + if(!sBlock.claimed()) // Checks if a thread has been set on this storage block yet or not. This data does not get serialized. + sBlock.setClaimed(); // We are not yet in a new thread but we know we will be soon. + } + proc = toProcess; + } + + @Override + public void run() + { + MyThread=Thread.currentThread(); + HealerQueue.ManagerInstance.registerWorker(this); + for (StoredBlock storedBlock : proc) { + storedBlock.setClaimedBy(Thread.currentThread()); + } + + while(alive){ + // Stay alive + // The tick event will be fired when appropriate + if(doTick)tick(); + + + doTick=false; + } + + OTEMod.LOGGER.info(Thread.currentThread().getName()+" has completed healing an area. Worker is now dismantling"); + + HealerQueue.ManagerInstance.deregisterWorker(this); + } + + public void tick() + { + // A tick in the healer worker tells it to repair a block + // The healer manager is responsible for dispatching ticks + } + +} diff --git a/src/main/java/dev/zontreck/otemod/antigrief/StoredBlock.java b/src/main/java/dev/zontreck/otemod/antigrief/StoredBlock.java index 532219e..943014a 100644 --- a/src/main/java/dev/zontreck/otemod/antigrief/StoredBlock.java +++ b/src/main/java/dev/zontreck/otemod/antigrief/StoredBlock.java @@ -1,5 +1,7 @@ package dev.zontreck.otemod.antigrief; +import java.time.Instant; + import dev.zontreck.libzontreck.exceptions.InvalidDeserialization; import dev.zontreck.libzontreck.vectors.Vector3; import dev.zontreck.libzontreck.vectors.WorldPosition; @@ -8,17 +10,73 @@ import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.NbtUtils; import net.minecraft.nbt.Tag; import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; -public class StoredBlock { +public class StoredBlock implements Comparable +{ + + public static final StoredBlock getBedrock(WorldPosition pos){ + StoredBlock sb = new StoredBlock(pos.Position.asBlockPos(), Blocks.BEDROCK.defaultBlockState(), pos.getActualDimension()); + + return sb; + } + public static final StoredBlock getAir(WorldPosition pos){ + StoredBlock sb = new StoredBlock(pos.Position.asBlockPos(), Blocks.AIR.defaultBlockState(), pos.getActualDimension()); + + return sb; + } + + public static final StoredBlock getSculk(WorldPosition pos){ + StoredBlock sb = new StoredBlock(pos.Position.asBlockPos(), Blocks.SCULK.defaultBlockState() ,pos.getActualDimension()); + return sb; + } + + + + public static final int UNSET = 0; + public static final int PHASE1 = 1; + public static final int PHASE2 = 2; + public static final int PHSAE3 = 4; + public CompoundTag blockData; private WorldPosition position; private BlockState state; - private CompoundTag blockEntity; + private boolean claim = false; + private long claimed_at = 0; + private Thread claimed_by; + + public void setClaimed() + { + claimed_at = Instant.now().getEpochSecond(); + claim=true; + } + + public boolean claimed() + { + if(claimed_by == null) + { + if(claim) + { + if(Instant.now().getEpochSecond() > claimed_at+30) + { + claim=false; + claimed_at = 0; // The claim timed out as no thread was set + return false; + }else return true; // Temporary lock on claim + }else return false; // Not claimed + }else return true; // Permanent process lock + } + + public void setClaimedBy(Thread tx){ + claimed_by=tx; + } + + public StoredBlock(final BlockPos pos, final BlockState toSave, final ServerLevel lvl) { position = new WorldPosition(new Vector3(pos), lvl); @@ -32,6 +90,7 @@ public class StoredBlock { } + public final BlockPos getPos() { return position.Position.asBlockPos(); @@ -100,6 +159,21 @@ public class StoredBlock { final CompoundTag tmp = tag.getCompound("entity"); blockEntity = tmp.isEmpty() ? null : tmp; } + @Override + public int compareTo(Object o) { + if(o instanceof StoredBlock) + { + StoredBlock sb = (StoredBlock)o; + if(sb.position.same(position)) + { + if(sb.state.equals(state)) + { + return 0; + }return -1; + }else return -1; + + }return -1; + } }