From 1c64f84ec94f9a221184278f95c240bea5f99dd4 Mon Sep 17 00:00:00 2001 From: XiaoMoMi <972454774@qq.com> Date: Mon, 11 Mar 2024 04:59:46 +0800 Subject: [PATCH] slime world support --- build.gradle.kts | 2 +- .../customcrops/manager/HologramManager.java | 5 +- .../mechanic/world/WorldManagerImpl.java | 4 + .../world/adaptor/BukkitWorldAdaptor.java | 2 +- .../world/adaptor/SlimeWorldAdaptor.java | 217 ++++++++++++++++-- plugin/src/main/resources/plugin.yml | 4 +- 6 files changed, 210 insertions(+), 24 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index d334847..4149a06 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -8,7 +8,7 @@ plugins { allprojects { project.group = "net.momirealms" - project.version = "3.4.0.3" + project.version = "3.4.0.4" apply() apply(plugin = "java") diff --git a/plugin/src/main/java/net/momirealms/customcrops/manager/HologramManager.java b/plugin/src/main/java/net/momirealms/customcrops/manager/HologramManager.java index ba3a833..e5a4d1a 100644 --- a/plugin/src/main/java/net/momirealms/customcrops/manager/HologramManager.java +++ b/plugin/src/main/java/net/momirealms/customcrops/manager/HologramManager.java @@ -146,12 +146,12 @@ public class HologramManager implements Listener, Reloadable { this.tuples = tupleList.toArray(new Tuple[0]); if (VersionManager.isHigherThan1_20_R2()) { PacketManager.getInstance().send(player, - FakeEntityUtils.getSpawnPacket(random, location.clone().add(0,1.1,0), EntityType.TEXT_DISPLAY), + FakeEntityUtils.getSpawnPacket(random, location.clone().add(0,1.25,0), EntityType.TEXT_DISPLAY), FakeEntityUtils.get1_20_2TextDisplayMetaPacket(random, component) ); } else if (VersionManager.isHigherThan1_19_R3()) { PacketManager.getInstance().send(player, - FakeEntityUtils.getSpawnPacket(random, location.clone().add(0,1.1,0), EntityType.TEXT_DISPLAY), + FakeEntityUtils.getSpawnPacket(random, location.clone().add(0,1.25,0), EntityType.TEXT_DISPLAY), FakeEntityUtils.get1_19_4TextDisplayMetaPacket(random, component) ); } else { @@ -168,7 +168,6 @@ public class HologramManager implements Listener, Reloadable { } else { PacketManager.getInstance().send(player, FakeEntityUtils.getVanishArmorStandMetaPacket(entity_id, component)); } - } } diff --git a/plugin/src/main/java/net/momirealms/customcrops/mechanic/world/WorldManagerImpl.java b/plugin/src/main/java/net/momirealms/customcrops/mechanic/world/WorldManagerImpl.java index 1623b79..84d412f 100644 --- a/plugin/src/main/java/net/momirealms/customcrops/mechanic/world/WorldManagerImpl.java +++ b/plugin/src/main/java/net/momirealms/customcrops/mechanic/world/WorldManagerImpl.java @@ -461,11 +461,14 @@ public class WorldManagerImpl implements WorldManager, Listener { Optional optional = getCustomCropsWorld(bukkitChunk.getWorld()); if (optional.isEmpty()) return; + CustomCropsWorld customCropsWorld = optional.get(); ChunkCoordinate chunkCoordinate = ChunkCoordinate.getByBukkitChunk(bukkitChunk); + if (customCropsWorld.isChunkLoaded(chunkCoordinate)) { return; } + // load chunks this.worldAdaptor.loadDynamicData(customCropsWorld, chunkCoordinate); @@ -491,6 +494,7 @@ public class WorldManagerImpl implements WorldManager, Listener { CustomCropsWorld customCropsWorld = optional.get(); ChunkCoordinate chunkCoordinate = ChunkCoordinate.getByBukkitChunk(bukkitChunk); + this.worldAdaptor.unloadDynamicData(customCropsWorld, chunkCoordinate); } diff --git a/plugin/src/main/java/net/momirealms/customcrops/mechanic/world/adaptor/BukkitWorldAdaptor.java b/plugin/src/main/java/net/momirealms/customcrops/mechanic/world/adaptor/BukkitWorldAdaptor.java index 0e8cecd..bfcb8b6 100644 --- a/plugin/src/main/java/net/momirealms/customcrops/mechanic/world/adaptor/BukkitWorldAdaptor.java +++ b/plugin/src/main/java/net/momirealms/customcrops/mechanic/world/adaptor/BukkitWorldAdaptor.java @@ -59,7 +59,7 @@ import java.util.concurrent.ConcurrentHashMap; public class BukkitWorldAdaptor extends AbstractWorldAdaptor { private static final NamespacedKey key = new NamespacedKey(CustomCropsPlugin.get(), "data"); - private final Gson gson; + protected final Gson gson; private String worldFolder; public BukkitWorldAdaptor(WorldManager worldManager) { diff --git a/plugin/src/main/java/net/momirealms/customcrops/mechanic/world/adaptor/SlimeWorldAdaptor.java b/plugin/src/main/java/net/momirealms/customcrops/mechanic/world/adaptor/SlimeWorldAdaptor.java index 910c97f..d562555 100644 --- a/plugin/src/main/java/net/momirealms/customcrops/mechanic/world/adaptor/SlimeWorldAdaptor.java +++ b/plugin/src/main/java/net/momirealms/customcrops/mechanic/world/adaptor/SlimeWorldAdaptor.java @@ -17,19 +17,35 @@ package net.momirealms.customcrops.mechanic.world.adaptor; +import com.flowpowered.nbt.*; import com.infernalsuite.aswm.api.SlimePlugin; import com.infernalsuite.aswm.api.events.LoadSlimeWorldEvent; +import com.infernalsuite.aswm.api.world.SlimeWorld; +import net.momirealms.customcrops.api.CustomCropsPlugin; import net.momirealms.customcrops.api.manager.WorldManager; import net.momirealms.customcrops.api.mechanic.world.AbstractWorldAdaptor; import net.momirealms.customcrops.api.mechanic.world.ChunkCoordinate; +import net.momirealms.customcrops.api.mechanic.world.ChunkPos; +import net.momirealms.customcrops.api.mechanic.world.CustomCropsBlock; import net.momirealms.customcrops.api.mechanic.world.level.CustomCropsChunk; import net.momirealms.customcrops.api.mechanic.world.level.CustomCropsWorld; +import net.momirealms.customcrops.api.mechanic.world.level.WorldInfoData; +import net.momirealms.customcrops.api.util.LogUtils; +import net.momirealms.customcrops.mechanic.world.*; +import net.momirealms.customcrops.mechanic.world.block.*; +import net.momirealms.customcrops.scheduler.task.TickTask; import org.bukkit.Bukkit; +import org.bukkit.World; import org.bukkit.event.EventHandler; import org.bukkit.event.world.WorldLoadEvent; import org.bukkit.event.world.WorldUnloadEvent; +import org.bukkit.persistence.PersistentDataType; -public class SlimeWorldAdaptor extends AbstractWorldAdaptor { +import java.io.*; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +public class SlimeWorldAdaptor extends BukkitWorldAdaptor { private final SlimePlugin slimePlugin; @@ -40,46 +56,211 @@ public class SlimeWorldAdaptor extends AbstractWorldAdaptor { @EventHandler (ignoreCancelled = true) public void onSlimeWorldLoad(LoadSlimeWorldEvent event) { - - } - - @EventHandler(ignoreCancelled = true) - public void onWorldLoad(WorldLoadEvent event) { - if (worldManager.isMechanicEnabled(event.getWorld())) { - worldManager.loadWorld(event.getWorld()); + World world = Bukkit.getWorld(event.getSlimeWorld().getName()); + if (world == null) { + LogUtils.warn("Failed to load slime world because the bukkit world not loaded"); + return; + } + if (worldManager.isMechanicEnabled(world)) { + worldManager.loadWorld(world); } - } - - @EventHandler (ignoreCancelled = true) - public void onWorldUnload(WorldUnloadEvent event) { } @Override public void unload(CustomCropsWorld customCropsWorld) { + SlimeWorld slimeWorld = slimePlugin.getWorld(customCropsWorld.getWorldName()); + if (slimeWorld == null) { + super.unload(customCropsWorld); + return; + } + CWorld cWorld = (CWorld) customCropsWorld; + Optional optionalCompoundTag = slimeWorld.getExtraData().getAsCompoundTag("customcrops"); + if (optionalCompoundTag.isEmpty()) { + LogUtils.warn("Failed to unload data for world " + customCropsWorld.getWorldName() + " because slime world format is incorrect."); + return; + } + CompoundMap ccDataMap = optionalCompoundTag.get().getValue(); + ccDataMap.put(new StringTag("world-info", gson.toJson(customCropsWorld.getInfoData()))); + for (CChunk chunk : cWorld.getAllChunksToSave()) { + ccDataMap.put(chunkToTag(chunk)); + } } @Override public void init(CustomCropsWorld customCropsWorld) { + SlimeWorld slimeWorld = slimePlugin.getWorld(customCropsWorld.getWorldName()); + if (slimeWorld == null) { + super.init(customCropsWorld); + return; + } + Optional optionalCompoundTag = slimeWorld.getExtraData().getAsCompoundTag("customcrops"); + CompoundMap ccDataMap; + if (optionalCompoundTag.isEmpty()) { + ccDataMap = new CompoundMap(); + slimeWorld.getExtraData().getValue().put(new CompoundTag("customcrops", ccDataMap)); + } else { + ccDataMap = optionalCompoundTag.get().getValue(); + } + + String json = Optional.ofNullable(ccDataMap.get("world-info")).map(tag -> tag.getAsStringTag().get().getValue()).orElse(null); + WorldInfoData data = (json == null || json.equals("null")) ? WorldInfoData.empty() : gson.fromJson(json, WorldInfoData.class); + customCropsWorld.setInfoData(data); } @Override public void loadDynamicData(CustomCropsWorld customCropsWorld, ChunkCoordinate chunkCoordinate) { + SlimeWorld slimeWorld = slimePlugin.getWorld(customCropsWorld.getWorldName()); + if (slimeWorld == null) { + super.loadDynamicData(customCropsWorld, chunkCoordinate); + return; + } + CWorld cWorld = (CWorld) customCropsWorld; + // load lazy chunks firstly + CustomCropsChunk lazyChunk = customCropsWorld.removeLazyChunkAt(chunkCoordinate); + if (lazyChunk != null) { + CChunk cChunk = (CChunk) lazyChunk; + cChunk.setUnloadedSeconds(0); + cWorld.loadChunk(cChunk); + return; + } + + Optional optionalCompoundTag = slimeWorld.getExtraData().getAsCompoundTag("customcrops"); + if (optionalCompoundTag.isEmpty()) { + LogUtils.warn("Failed to load data for " + chunkCoordinate + " in world " + customCropsWorld.getWorldName() + " because slime world format is incorrect."); + return; + } + + Tag chunkTag = optionalCompoundTag.get().getValue().get(chunkCoordinate.toString()); + if (chunkTag == null) return; + Optional chunkCompoundTag = chunkTag.getAsCompoundTag(); + if (chunkCompoundTag.isEmpty()) return; + + // load chunk from local files + long time1 = System.currentTimeMillis(); + cWorld.loadChunk(tagToChunk(cWorld, chunkCompoundTag.get())); + long time2 = System.currentTimeMillis(); + CustomCropsPlugin.get().debug("Took " + (time2-time1) + "ms to load chunk " + chunkCoordinate); } @Override - public void unloadDynamicData(CustomCropsWorld customCropsWorld, ChunkCoordinate chunkCoordinate) { + public void saveDynamicData(CustomCropsWorld customCropsWorld, CustomCropsChunk chunk) { + SlimeWorld slimeWorld = getSlimeWorld(customCropsWorld.getWorldName()); + if (slimeWorld == null) { + super.saveDynamicData(customCropsWorld, chunk); + return; + } + Optional optionalCompoundTag = slimeWorld.getExtraData().getAsCompoundTag("customcrops"); + if (optionalCompoundTag.isEmpty()) { + LogUtils.warn("Failed to save data for " + chunk + " in world " + customCropsWorld.getWorldName() + " because slime world format is incorrect."); + return; + } + + CustomCropsPlugin.get().getScheduler().runTaskSync(() -> optionalCompoundTag.get().getValue().put(chunkToTag((CChunk) chunk)), null); } - @Override - public void saveDynamicData(CustomCropsWorld ccWorld, CustomCropsChunk chunk) { - + private SlimeWorld getSlimeWorld(String name) { + return slimePlugin.getWorld(name); } - private boolean isSlimeWorld(String name) { - return slimePlugin.getWorld(name) != null; + private CompoundTag chunkToTag(CChunk chunk) { + SerializableChunk serializableChunk = toSerializableChunk(chunk); + CompoundMap map = new CompoundMap(); + map.put(new IntTag("x", serializableChunk.getX())); + map.put(new IntTag("z", serializableChunk.getZ())); + map.put(new IntTag("loadedSeconds", serializableChunk.getLoadedSeconds())); + map.put(new LongTag("lastLoadedTime", serializableChunk.getLastLoadedTime())); + map.put(new IntArrayTag("queued", serializableChunk.getTicked())); + map.put(new IntArrayTag("ticked", serializableChunk.getTicked())); + CompoundMap sectionMap = new CompoundMap(); + for (SerializableSection section : serializableChunk.getSections()) { + sectionMap.put(new ListTag<>(String.valueOf(section.getSectionID()), TagType.TAG_COMPOUND, section.getBlocks())); + } + map.put(new CompoundTag("sections", sectionMap)); + return new CompoundTag(chunk.getChunkCoordinate().toString(), map); + } + + private CChunk tagToChunk(CWorld cWorld, CompoundTag tag) { + String world = cWorld.getWorldName(); + CompoundMap map = tag.getValue(); + int x = map.get("x").getAsIntTag().get().getValue(); + int z = map.get("z").getAsIntTag().get().getValue(); + ChunkCoordinate coordinate = new ChunkCoordinate(x, z); + int loadedSeconds = map.get("loadedSeconds").getAsIntTag().get().getValue(); + long lastLoadedTime = map.get("lastLoadedTime").getAsLongTag().get().getValue(); + int[] queued = map.get("queued").getAsIntArrayTag().get().getValue(); + int[] ticked = map.get("ticked").getAsIntArrayTag().get().getValue(); + + PriorityQueue queue = new PriorityQueue<>(Math.max(11, queued.length / 2)); + for (int i = 0, size = queued.length / 2; i < size; i++) { + ChunkPos pos = new ChunkPos(queued[2*i+1]); + queue.add(new TickTask(queued[2*i], pos)); + } + + HashSet tickedSet = new HashSet<>(Math.max(11, ticked.length)); + for (int tick : ticked) { + tickedSet.add(new ChunkPos(tick)); + } + + ConcurrentHashMap sectionMap = new ConcurrentHashMap<>(); + CompoundMap sectionCompoundMap = map.get("sections").getAsCompoundTag().get().getValue(); + for (Map.Entry> entry : sectionCompoundMap.entrySet()) { + if (entry.getValue() instanceof ListTag listTag) { + int id = Integer.parseInt(entry.getKey()); + ConcurrentHashMap blockMap = new ConcurrentHashMap<>(); + ListTag blocks = (ListTag) listTag; + for (CompoundTag blockTag : blocks.getValue()) { + CompoundMap block = blockTag.getValue(); + String type = (String) block.get("type").getValue(); + CompoundMap data = (CompoundMap) block.get("data").getValue(); + switch (type) { + case "CROP" -> { + for (int pos : (int[]) block.get("pos").getValue()) { + ChunkPos chunkPos = new ChunkPos(pos); + blockMap.put(chunkPos, new MemoryCrop(chunkPos.getLocation(world, coordinate), new CompoundMap(data))); + } + } + case "POT" -> { + for (int pos : (int[]) block.get("pos").getValue()) { + ChunkPos chunkPos = new ChunkPos(pos); + blockMap.put(chunkPos, new MemoryPot(chunkPos.getLocation(world, coordinate), new CompoundMap(data))); + } + } + case "SPRINKLER" -> { + for (int pos : (int[]) block.get("pos").getValue()) { + ChunkPos chunkPos = new ChunkPos(pos); + blockMap.put(chunkPos, new MemorySprinkler(chunkPos.getLocation(world, coordinate), new CompoundMap(data))); + } + } + case "SCARECROW" -> { + for (int pos : (int[]) block.get("pos").getValue()) { + ChunkPos chunkPos = new ChunkPos(pos); + blockMap.put(chunkPos, new MemoryScarecrow(chunkPos.getLocation(world, coordinate), new CompoundMap(data))); + } + } + case "GLASS" -> { + for (int pos : (int[]) block.get("pos").getValue()) { + ChunkPos chunkPos = new ChunkPos(pos); + blockMap.put(chunkPos, new MemoryGlass(chunkPos.getLocation(world, coordinate), new CompoundMap(data))); + } + } + } + } + sectionMap.put(id, new CSection(id, blockMap)); + } + } + + return new CChunk( + cWorld, + coordinate, + loadedSeconds, + lastLoadedTime, + sectionMap, + queue, + tickedSet + ); } } diff --git a/plugin/src/main/resources/plugin.yml b/plugin/src/main/resources/plugin.yml index a3c51a8..fa7cae2 100644 --- a/plugin/src/main/resources/plugin.yml +++ b/plugin/src/main/resources/plugin.yml @@ -4,6 +4,7 @@ main: net.momirealms.customcrops.CustomCropsPluginImpl api-version: 1.17 load: POSTWORLD authors: [ XiaoMoMi ] +folia-supported: true depend: - ProtocolLib softdepend: @@ -22,4 +23,5 @@ softdepend: - EcoSkills - Jobs - RealisticSeasons - - AdvancedSeasons \ No newline at end of file + - AdvancedSeasons + - SlimeWorldManager \ No newline at end of file