diff --git a/src/main/java/dev/zontreck/otemod/OTEMod.java b/src/main/java/dev/zontreck/otemod/OTEMod.java index c0d4bb9..b122684 100644 --- a/src/main/java/dev/zontreck/otemod/OTEMod.java +++ b/src/main/java/dev/zontreck/otemod/OTEMod.java @@ -38,6 +38,8 @@ import net.minecraftforge.registries.ForgeRegistries; import org.slf4j.Logger; import dev.zontreck.libzontreck.chat.ChatColor; +import dev.zontreck.otemod.antigrief.HealerManager; +import dev.zontreck.otemod.antigrief.HealerQueue; import dev.zontreck.otemod.blocks.ModBlocks; import dev.zontreck.otemod.chat.ChatServerOverride; import dev.zontreck.otemod.commands.CommandRegistry; @@ -101,6 +103,7 @@ public class OTEMod MinecraftForge.EVENT_BUS.register(new ChatServerOverride()); MinecraftForge.EVENT_BUS.register(new CommandRegistry()); MinecraftForge.EVENT_BUS.register(new VaultWatcher()); + MinecraftForge.EVENT_BUS.register(new dev.zontreck.otemod.antigrief.Handler()); MenuInitializer.CONTAINERS.register(bus); ModBlocks.register(bus); @@ -150,6 +153,9 @@ public class OTEMod try { OTEMod.DB = new Database(this); OTEMod.ALIVE=true; + HealerQueue.Initialize(); // Set up the queue + + // Validate that the database has been established and that tables exist Connection con = OTEMod.DB.getConnection(); con.setAutoCommit(true); diff --git a/src/main/java/dev/zontreck/otemod/antigrief/Handler.java b/src/main/java/dev/zontreck/otemod/antigrief/Handler.java index f6a8904..982c123 100644 --- a/src/main/java/dev/zontreck/otemod/antigrief/Handler.java +++ b/src/main/java/dev/zontreck/otemod/antigrief/Handler.java @@ -1,12 +1,16 @@ package dev.zontreck.otemod.antigrief; +import java.util.ArrayList; import java.util.Collection; +import java.util.LinkedList; +import dev.zontreck.otemod.configs.OTEServerConfig; import net.minecraft.core.BlockPos; import net.minecraft.nbt.CompoundTag; import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.entity.Entity; +import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.OnlyIn; @@ -19,7 +23,7 @@ public class Handler private static final String EXPLOSION_HEAL_TAG = "OTEEH"; - @OnlyIn(Dist.DEDICATED_SERVER) + /*@OnlyIn(Dist.DEDICATED_SERVER) @SubscribeEvent public void onChunkLoad(final ChunkDataEvent.Load event) { @@ -31,7 +35,7 @@ public class Handler // This would re-queue the healer } } - } + }*/ @OnlyIn(Dist.DEDICATED_SERVER) @SubscribeEvent @@ -43,16 +47,58 @@ public class Handler if(exploder==null)return ; // TODO: Make this check config - final Collection affectedBlocks = buildBlocks(level, event.getAffectedBlocks()); + final Collection affectedBlocks = buildBlocks(level, event.getAffectedBlocks()); + final Collection toHeal = new ArrayList(affectedBlocks.size()); + + for(final StoredBlock data : affectedBlocks) + { + // Check an exclusions list + if(OTEServerConfig.EXCLUDE_DIMENSIONS.get().contains(data.getWorldPosition().Dimension)) + toHeal.add(data); + } + + // Process Block Entities + + for(final StoredBlock sb : toHeal) + { + if(sb.getState().hasBlockEntity()) + { + BlockEntity be = level.getBlockEntity(sb.getPos()); + if(be != null){ + sb.setBlockEntity(be); + } + } + } + + // Remove the existing blocks from the world to prevent item duplication + // Begin + for(StoredBlock data : toHeal) + { + if(data.getBlockEntity()!=null) + data.getWorldPosition().getActualDimension().removeBlockEntity(data.getPos()); + + data.getWorldPosition().getActualDimension().removeBlock(data.getPos(), false); // Is false to not drop item? + } + + // Add to the healing queue + HealerQueue.ToHeal.addAll(toHeal); + HealerQueue.Shuffle(); + } - private Collection buildBlocks(ServerLevel level, Collection positions) + private Collection buildBlocks(ServerLevel level, Collection positions) { + Collection healables = new LinkedList(); for(final BlockPos pos : positions) { final BlockState state = level.getBlockState(pos); + StoredBlock sb = new StoredBlock(pos, state, level); + if(state !=null) + healables.add(sb); } + + return healables; } } diff --git a/src/main/java/dev/zontreck/otemod/antigrief/HealerManager.java b/src/main/java/dev/zontreck/otemod/antigrief/HealerManager.java new file mode 100644 index 0000000..05a03b4 --- /dev/null +++ b/src/main/java/dev/zontreck/otemod/antigrief/HealerManager.java @@ -0,0 +1,70 @@ +package dev.zontreck.otemod.antigrief; + +import java.io.IOException; + +import dev.zontreck.otemod.OTEMod; +import dev.zontreck.otemod.configs.OTEServerConfig; +import dev.zontreck.otemod.containers.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.entity.BlockEntity; + +public class HealerManager implements Runnable +{ + public HealerManager(){ + + } + @Override + public void run(){ + while(OTEMod.ALIVE) + { + // Run the queue + // We want to restore one block per run, then halt for number of seconds in config + try { + Thread.sleep(Long.parseLong(String.valueOf(OTEServerConfig.HEALER_TIMER.get()*1000))); + } catch (NumberFormatException e) { + e.printStackTrace(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + if(!OTEMod.ALIVE) + { + // Server has begun to shut down while we were sleeping + // Begin tear down + break; + } + + // Loop back to start if no items in queue + if(HealerQueue.ToHeal.size()==0)continue; + // Play a popping sound at the block position + SoundEvent pop = SoundEvents.ITEM_PICKUP; + // Get the first block in the list + StoredBlock sb = HealerQueue.ToHeal.get(0); + // Remove the block from the queue now to prevent further issues + HealerQueue.ToHeal.remove(sb); + ServerLevel level = sb.getWorldPosition().getActualDimension(); + + level.setBlock(sb.getPos(), sb.getState(), 0); + BlockEntity be = level.getBlockEntity(sb.getPos()); + be.deserializeNBT(sb.getBlockEntity()); + + // Everything is restored, play sound + SoundSource ss = SoundSource.BLOCKS; + Vector3 v3 = sb.getWorldPosition().Position; + level.playSound(null, v3.x, v3.y, v3.z, pop, ss, 0, 0); + + } + + OTEMod.LOGGER.info("Tearing down healer jobs. Saving remaining queue, stand by..."); + try { + HealerQueue.dump(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + OTEMod.LOGGER.info("Finished tearing down Healer - Good bye"); + } +} diff --git a/src/main/java/dev/zontreck/otemod/antigrief/HealerQueue.java b/src/main/java/dev/zontreck/otemod/antigrief/HealerQueue.java new file mode 100644 index 0000000..6f3d38f --- /dev/null +++ b/src/main/java/dev/zontreck/otemod/antigrief/HealerQueue.java @@ -0,0 +1,159 @@ +package dev.zontreck.otemod.antigrief; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import com.mojang.brigadier.exceptions.CommandSyntaxException; + +import dev.zontreck.otemod.configs.OTEServerConfig; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.nbt.NbtIo; +import net.minecraft.nbt.NbtUtils; +import net.minecraft.nbt.Tag; +import net.minecraftforge.fml.loading.FMLConfig; +import net.minecraftforge.fml.loading.FMLPaths; + +public class HealerQueue { + // Healer Queue's data source is a NBT File in the config folder + public static final String HealerQueueFile = "OTEHealerLastQueue.nbt"; + public static final String HealerQueueDebugFile = "OTEHealerLastQueue.snbt"; + + public static List ToHeal = new ArrayList(); + + public static Path getPath() + { + + Path configDir = FMLPaths.GAMEDIR.get().resolve(FMLConfig.defaultConfigPath()); + if(OTEServerConfig.DEBUG_HEALER.get()) + { + Path configFile = configDir.resolve(HealerQueue.HealerQueueDebugFile); + return configFile; + + }else { + Path configFile = configDir.resolve(HealerQueue.HealerQueueFile); + return configFile; + } + } + + public static void Initialize() + { + if(OTEServerConfig.DEBUG_HEALER.get()) + { + // Load the sNBT file + Path configFile = getPath(); + File x = configFile.toFile(); + String FinalStr = ""; + try { + BufferedReader br = new BufferedReader(new FileReader(x)); + while(br.ready()) + { + FinalStr += br.readLine(); + FinalStr += "\n"; + } + br.close(); + + } catch (FileNotFoundException e) { + e.printStackTrace(); + } catch(IOException e) + { + e.printStackTrace(); + } + + try { + HealerQueue.deserialize(NbtUtils.snbtToStructure(FinalStr)); + } catch (CommandSyntaxException e) { + e.printStackTrace(); + } + } else { + // Load from normal NBT + Path configFile = getPath(); + File x = configFile.toFile(); + // Load binary + try { + CompoundTag tag = NbtIo.readCompressed(x); + HealerQueue.deserialize(tag); + } catch (IOException e) { + e.printStackTrace(); + } + } + + // Set up the HealerManager / Runner + Thread tx = new Thread(new HealerManager()); + tx.start(); + } + + public static CompoundTag serialize() + { + CompoundTag tag = new CompoundTag(); + // Save entire list + ListTag lst = new ListTag(); + for(final StoredBlock block : HealerQueue.ToHeal) + { + lst.add(block.serialize()); + } + + tag.put("queue", lst); + + // OK + + return tag; + } + + public static void deserialize(CompoundTag tag) + { + // Begin parsing + if(tag.contains("queue")) + { + HealerQueue.ToHeal.clear(); + // Read the list + + ListTag items = tag.getList("queue", Tag.TAG_COMPOUND); + + for(int i=0;i> 4; + Vector3 pos = position.Position; + return pos.rounded().x >> 4; } - public final int getChunkZ() + public final long getChunkZ() { - return pos.getZ() >> 4; + Vector3 pos = position.Position; + return pos.rounded().z >> 4; + } + + public void setBlockEntity(BlockEntity entity) + { + CompoundTag tag = entity.serializeNBT(); + this.blockEntity=tag; + } + + public final CompoundTag getBlockEntity(){ + return blockEntity; + } + + public static boolean hasBlockEntity(final CompoundTag tag){ + return tag.contains("entity", Tag.TAG_COMPOUND); } public CompoundTag serialize() { final CompoundTag tag = new CompoundTag(); - - tag.put("pos", ) + tag.put("pos", position.serialize()); + tag.put("state", NbtUtils.writeBlockState(state)); + + if(blockEntity != null) tag.put("entity", blockEntity); + + return tag; + } + + + public void deserialize(final CompoundTag tag) + { + try { + position = new WorldPosition(tag.getCompound("pos"), false); + } catch (InvalidDeserialization e) { + e.printStackTrace(); + } + + state = NbtUtils.readBlockState(tag.getCompound("state")); + + final CompoundTag tmp = tag.getCompound("entity"); + blockEntity = tmp.isEmpty() ? null : tmp; } diff --git a/src/main/java/dev/zontreck/otemod/configs/OTEServerConfig.java b/src/main/java/dev/zontreck/otemod/configs/OTEServerConfig.java index d13dd8e..c7625a8 100644 --- a/src/main/java/dev/zontreck/otemod/configs/OTEServerConfig.java +++ b/src/main/java/dev/zontreck/otemod/configs/OTEServerConfig.java @@ -22,9 +22,15 @@ public class OTEServerConfig { public static final ForgeConfigSpec.ConfigValue DATABASE; public static final ForgeConfigSpec.ConfigValue ITEM_DESPAWN_TIMER; public static final ForgeConfigSpec.ConfigValue RTP_COOLDOWN; + public static final ForgeConfigSpec.ConfigValue HEALER_TIMER; + public static final ForgeConfigSpec.ConfigValue> EXCLUDE_DIMENSIONS; + public static final ForgeConfigSpec.BooleanValue DEBUG_HEALER; static { List defaults = new ArrayList(); + List defaultExcludeDimensions = new ArrayList(); + defaultExcludeDimensions.add("minecraft:the_nether"); // Excluded to make mining for Ancient Debris easier + defaultExcludeDimensions.add("minecraft:the_end"); // Excluded due to End Crystals BUILDER.push("OTE"); INITIAL_ITEMS_TO_GIVE_ON_FIRST_JOIN = BUILDER.comment("What items, identified by modid:item, to give to a brand new user on the server").define("New Player Gear", defaults); @@ -39,6 +45,13 @@ public class OTEServerConfig { RTP_COOLDOWN = BUILDER.comment("How many seconds between RTP uses? This can be quite laggy on the server due to the potential that new chunks are getting generated").define("rtp.cooldown", 30); // Default of 30 should be enough + BUILDER.pop(); + BUILDER.push("ANTIGRIEF").comment("AntiGrief Explosion Healing Events"); + HEALER_TIMER = BUILDER.comment("Time between healing events (In Seconds)").define("timer", 5); // Should this be lower? + EXCLUDE_DIMENSIONS = BUILDER.comment("What dimensions to exclude? (Namespace:Dimension) in lowercase").define("exclude_dims", defaultExcludeDimensions); + DEBUG_HEALER = BUILDER.comment("Whether or not to debug the healer engine. (Saves as SNBT instead of NBT)").define("debug", false); + + BUILDER.pop(); SPEC=BUILDER.build(); diff --git a/src/main/java/dev/zontreck/otemod/containers/NonAbsVector3.java b/src/main/java/dev/zontreck/otemod/containers/NonAbsVector3.java new file mode 100644 index 0000000..7f8c9ed --- /dev/null +++ b/src/main/java/dev/zontreck/otemod/containers/NonAbsVector3.java @@ -0,0 +1,18 @@ +package dev.zontreck.otemod.containers; + +/* +* This is a non-serializable instanced Vector that is meant to slam positions down as a integer +*/ +public class NonAbsVector3 +{ + public long x; + public long y; + public long z; + + public NonAbsVector3(Vector3 origin) + { + x = Math.round(origin.x); + y = Math.round(origin.y); + z = Math.round(origin.z); + } +} diff --git a/src/main/java/dev/zontreck/otemod/containers/Vector3.java b/src/main/java/dev/zontreck/otemod/containers/Vector3.java index c7daa00..80a4e30 100644 --- a/src/main/java/dev/zontreck/otemod/containers/Vector3.java +++ b/src/main/java/dev/zontreck/otemod/containers/Vector3.java @@ -2,6 +2,7 @@ package dev.zontreck.otemod.containers; import dev.zontreck.otemod.exceptions.InvalidDeserialization; import net.minecraft.core.BlockPos; +import net.minecraft.nbt.CompoundTag; import net.minecraft.world.phys.Vec3; public class Vector3 @@ -95,4 +96,30 @@ public class Vector3 { return "<"+String.valueOf(x)+", "+String.valueOf(y)+", "+String.valueOf(z)+">"; } + + public NonAbsVector3 rounded() + { + NonAbsVector3 cl = new NonAbsVector3(this); + return cl; + } + + public CompoundTag serialize() + { + CompoundTag tag = new CompoundTag(); + tag.putDouble("x", x); + tag.putDouble("y", y); + tag.putDouble("z", z); + + return tag; + } + + public Vector3(CompoundTag tag) { + this.deserialize(tag); + } + public void deserialize(CompoundTag tag) + { + x=tag.getDouble("x"); + y=tag.getDouble("y"); + z=tag.getDouble("z"); + } } diff --git a/src/main/java/dev/zontreck/otemod/containers/WorldPosition.java b/src/main/java/dev/zontreck/otemod/containers/WorldPosition.java index 556441c..1999d8f 100644 --- a/src/main/java/dev/zontreck/otemod/containers/WorldPosition.java +++ b/src/main/java/dev/zontreck/otemod/containers/WorldPosition.java @@ -13,10 +13,16 @@ public class WorldPosition public Vector3 Position; public String Dimension; - public WorldPosition(CompoundTag tag) throws InvalidDeserialization + public WorldPosition(CompoundTag tag, boolean pretty) throws InvalidDeserialization { - Position = new Vector3(tag.getString("Position")); - Dimension = tag.getString("Dimension"); + if(pretty){ + + Position = new Vector3(tag.getString("Position")); + Dimension = tag.getString("Dimension"); + }else { + Position = new Vector3(tag.getCompound("pos")); + Dimension = tag.getString("Dimension"); + } } @@ -38,7 +44,7 @@ public class WorldPosition return NbtUtils.structureToSnbt(serialize()); } - public CompoundTag serialize() + public CompoundTag serializePretty() { CompoundTag tag = new CompoundTag(); @@ -48,6 +54,15 @@ public class WorldPosition return tag; } + public CompoundTag serialize() + { + CompoundTag tag = new CompoundTag(); + tag.put("pos", Position.serialize()); + tag.putString("Dimension", Dimension); + + return tag; + } + public ServerLevel getActualDimension() diff --git a/src/main/java/dev/zontreck/otemod/database/TeleportDestination.java b/src/main/java/dev/zontreck/otemod/database/TeleportDestination.java index 67c9701..5a8873e 100644 --- a/src/main/java/dev/zontreck/otemod/database/TeleportDestination.java +++ b/src/main/java/dev/zontreck/otemod/database/TeleportDestination.java @@ -17,7 +17,7 @@ public class TeleportDestination extends WorldPosition public TeleportDestination(CompoundTag tag) throws InvalidDeserialization { - super(tag); + super(tag, true); Rotation = new Vector2(tag.getString("Rotation")); } public TeleportDestination(Vector3 pos, Vector2 rot, String dim) @@ -42,7 +42,7 @@ public class TeleportDestination extends WorldPosition public CompoundTag serialize(){ - CompoundTag tag = super.serialize(); + CompoundTag tag = super.serializePretty(); tag.putString("Rotation", Rotation.toString()); return tag;