From 68585d124a1bc3d23dcf7f9227f14e3968f99d2e Mon Sep 17 00:00:00 2001 From: XiaoMoMi <972454774@qq.com> Date: Mon, 18 Mar 2024 05:05:56 +0800 Subject: [PATCH] region file --- .../customcrops/api/manager/WorldManager.java | 6 +- .../customcrops/api/mechanic/item/Pot.java | 2 + .../mechanic/world/AbstractWorldAdaptor.java | 14 +- .../api/mechanic/world/BlockPos.java | 2 +- .../{ChunkCoordinate.java => ChunkPos.java} | 29 +- .../api/mechanic/world/RegionPos.java | 87 ++++++ .../api/mechanic/world/SimpleLocation.java | 4 +- .../world/level/CustomCropsChunk.java | 6 +- .../world/level/CustomCropsRegion.java | 41 +++ .../world/level/CustomCropsWorld.java | 19 +- build.gradle.kts | 2 +- .../customcrops/CustomCropsPluginImpl.java | 1 - .../customcrops/manager/CommandManager.java | 48 +++- .../mechanic/action/ActionManagerImpl.java | 4 +- .../mechanic/item/ItemManagerImpl.java | 3 + .../item/custom/AbstractCustomListener.java | 1 + .../mechanic/item/impl/PotConfig.java | 13 + .../mechanic/misc/migrator/Migration.java | 22 +- .../customcrops/mechanic/world/CChunk.java | 21 +- .../customcrops/mechanic/world/CRegion.java | 83 ++++++ .../customcrops/mechanic/world/CWorld.java | 184 ++++++++---- .../mechanic/world/SerializableChunk.java | 4 + .../mechanic/world/WorldManagerImpl.java | 31 +- .../world/adaptor/BukkitWorldAdaptor.java | 268 ++++++++++++++---- .../world/adaptor/SlimeWorldAdaptor.java | 91 ++++-- plugin/src/main/resources/config.yml | 2 +- 26 files changed, 792 insertions(+), 196 deletions(-) rename api/src/main/java/net/momirealms/customcrops/api/mechanic/world/{ChunkCoordinate.java => ChunkPos.java} (74%) create mode 100644 api/src/main/java/net/momirealms/customcrops/api/mechanic/world/RegionPos.java create mode 100644 api/src/main/java/net/momirealms/customcrops/api/mechanic/world/level/CustomCropsRegion.java create mode 100644 plugin/src/main/java/net/momirealms/customcrops/mechanic/world/CRegion.java diff --git a/api/src/main/java/net/momirealms/customcrops/api/manager/WorldManager.java b/api/src/main/java/net/momirealms/customcrops/api/manager/WorldManager.java index 4358f41..7577a19 100644 --- a/api/src/main/java/net/momirealms/customcrops/api/manager/WorldManager.java +++ b/api/src/main/java/net/momirealms/customcrops/api/manager/WorldManager.java @@ -133,7 +133,9 @@ public interface WorldManager extends Reloadable { void handleChunkUnload(Chunk bukkitChunk); - void saveChunkToFile(CustomCropsChunk chunk); + void saveChunkToCachedRegion(CustomCropsChunk chunk); + + void saveRegionToFile(CustomCropsRegion region); void removeGlassAt(@NotNull SimpleLocation location); @@ -142,4 +144,6 @@ public interface WorldManager extends Reloadable { CustomCropsBlock removeAnythingAt(SimpleLocation location); AbstractWorldAdaptor getWorldAdaptor(); + + void saveInfoData(CustomCropsWorld customCropsWorld); } diff --git a/api/src/main/java/net/momirealms/customcrops/api/mechanic/item/Pot.java b/api/src/main/java/net/momirealms/customcrops/api/mechanic/item/Pot.java index f4f4007..df0513c 100644 --- a/api/src/main/java/net/momirealms/customcrops/api/mechanic/item/Pot.java +++ b/api/src/main/java/net/momirealms/customcrops/api/mechanic/item/Pot.java @@ -56,4 +56,6 @@ public interface Pot extends KeyItem { String getBlockState(boolean water, FertilizerType type); boolean isVanillaBlock(); + + boolean isWetPot(String id); } diff --git a/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/AbstractWorldAdaptor.java b/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/AbstractWorldAdaptor.java index 7159a92..8827d2d 100644 --- a/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/AbstractWorldAdaptor.java +++ b/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/AbstractWorldAdaptor.java @@ -19,12 +19,14 @@ package net.momirealms.customcrops.api.mechanic.world; import net.momirealms.customcrops.api.manager.WorldManager; import net.momirealms.customcrops.api.mechanic.world.level.CustomCropsChunk; +import net.momirealms.customcrops.api.mechanic.world.level.CustomCropsRegion; import net.momirealms.customcrops.api.mechanic.world.level.CustomCropsWorld; import org.bukkit.event.Listener; public abstract class AbstractWorldAdaptor implements Listener { - public static final int version = 1; + public static final int chunkVersion = 1; + public static final int regionVersion = 1; protected WorldManager worldManager; public AbstractWorldAdaptor(WorldManager worldManager) { @@ -35,9 +37,13 @@ public abstract class AbstractWorldAdaptor implements Listener { public abstract void init(CustomCropsWorld customCropsWorld); - public abstract void loadDynamicData(CustomCropsWorld customCropsWorld, ChunkCoordinate chunkCoordinate); + public abstract void loadChunkData(CustomCropsWorld customCropsWorld, ChunkPos chunkPos); - public abstract void unloadDynamicData(CustomCropsWorld customCropsWorld, ChunkCoordinate chunkCoordinate); + public abstract void unloadChunkData(CustomCropsWorld customCropsWorld, ChunkPos chunkPos); - public abstract void saveDynamicData(CustomCropsWorld ccWorld, CustomCropsChunk chunk); + public abstract void saveChunkToCachedRegion(CustomCropsChunk customCropsChunk); + + public abstract void saveRegion(CustomCropsRegion customCropsRegion); + + public abstract void saveInfoData(CustomCropsWorld customCropsWorld); } diff --git a/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/BlockPos.java b/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/BlockPos.java index 14f1c4b..319a2c7 100644 --- a/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/BlockPos.java +++ b/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/BlockPos.java @@ -35,7 +35,7 @@ public class BlockPos { return new BlockPos(location.getX() % 16, location.getY(), location.getZ() % 16); } - public SimpleLocation getLocation(String world, ChunkCoordinate coordinate) { + public SimpleLocation getLocation(String world, ChunkPos coordinate) { return new SimpleLocation(world, coordinate.x() * 16 + getX(), getY(), coordinate.z() * 16 + getZ()); } diff --git a/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/ChunkCoordinate.java b/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/ChunkPos.java similarity index 74% rename from api/src/main/java/net/momirealms/customcrops/api/mechanic/world/ChunkCoordinate.java rename to api/src/main/java/net/momirealms/customcrops/api/mechanic/world/ChunkPos.java index 6b402ca..511e46c 100644 --- a/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/ChunkCoordinate.java +++ b/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/ChunkPos.java @@ -20,20 +20,18 @@ package net.momirealms.customcrops.api.mechanic.world; import org.bukkit.Chunk; import org.jetbrains.annotations.NotNull; -public record ChunkCoordinate(int x, int z) { +public record ChunkPos(int x, int z) { - private static final ChunkCoordinate empty = new ChunkCoordinate(0, 0); - - public static ChunkCoordinate of(int x, int z) { - return new ChunkCoordinate(x, z); + public static ChunkPos of(int x, int z) { + return new ChunkPos(x, z); } - public static ChunkCoordinate getByString(String coordinate) { + public static ChunkPos getByString(String coordinate) { String[] split = coordinate.split(",", 2); try { int x = Integer.parseInt(split[0]); int z = Integer.parseInt(split[1]); - return new ChunkCoordinate(x, z); + return new ChunkPos(x, z); } catch (NumberFormatException e) { e.printStackTrace(); @@ -55,7 +53,7 @@ public record ChunkCoordinate(int x, int z) { if (getClass() != obj.getClass()) { return false; } - final ChunkCoordinate other = (ChunkCoordinate) obj; + final ChunkPos other = (ChunkPos) obj; if (this.x != other.x) { return false; } @@ -66,13 +64,22 @@ public record ChunkCoordinate(int x, int z) { } @NotNull - public static ChunkCoordinate getByBukkitChunk(@NotNull Chunk chunk) { - return new ChunkCoordinate(chunk.getX(), chunk.getZ()); + public RegionPos getRegionPos() { + return RegionPos.getByChunkPos(this); + } + + @NotNull + public static ChunkPos getByBukkitChunk(@NotNull Chunk chunk) { + return new ChunkPos(chunk.getX(), chunk.getZ()); + } + + public String getAsString() { + return x + "," + z; } @Override public String toString() { - return "ChunkCoordinate{" + + return "ChunkPos{" + "x=" + x + ", z=" + z + '}'; diff --git a/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/RegionPos.java b/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/RegionPos.java new file mode 100644 index 0000000..7b04763 --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/RegionPos.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customcrops.api.mechanic.world; + +import org.bukkit.Chunk; +import org.jetbrains.annotations.NotNull; + +public record RegionPos(int x, int z) { + + public static RegionPos of(int x, int z) { + return new RegionPos(x, z); + } + + public static RegionPos getByString(String coordinate) { + String[] split = coordinate.split(",", 2); + try { + int x = Integer.parseInt(split[0]); + int z = Integer.parseInt(split[1]); + return new RegionPos(x, z); + } + catch (NumberFormatException e) { + e.printStackTrace(); + return null; + } + } + + @Override + public int hashCode() { + long combined = (long) x << 32 | z; + return Long.hashCode(combined); + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final RegionPos other = (RegionPos) obj; + if (this.x != other.x) { + return false; + } + if (this.z != other.z) { + return false; + } + return true; + } + + @NotNull + public static RegionPos getByBukkitChunk(@NotNull Chunk chunk) { + int regionX = (int) Math.floor((double) chunk.getX() / 32.0); + int regionZ = (int) Math.floor((double) chunk.getZ() / 32.0); + return new RegionPos(regionX, regionZ); + } + + @NotNull + public static RegionPos getByChunkPos(@NotNull ChunkPos chunk) { + int regionX = (int) Math.floor((double) chunk.x() / 32.0); + int regionZ = (int) Math.floor((double) chunk.z() / 32.0); + return new RegionPos(regionX, regionZ); + } + + @Override + public String toString() { + return "RegionPos{" + + "x=" + x + + ", z=" + z + + '}'; + } +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/SimpleLocation.java b/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/SimpleLocation.java index a83ae58..403dfb7 100644 --- a/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/SimpleLocation.java +++ b/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/SimpleLocation.java @@ -57,8 +57,8 @@ public class SimpleLocation { return worldName; } - public ChunkCoordinate getChunkCoordinate() { - return new ChunkCoordinate(x >> 4, z >> 4); + public ChunkPos getChunkCoordinate() { + return new ChunkPos(x >> 4, z >> 4); } public SimpleLocation add(int x, int y, int z) { diff --git a/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/level/CustomCropsChunk.java b/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/level/CustomCropsChunk.java index c3062fe..1ac3812 100644 --- a/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/level/CustomCropsChunk.java +++ b/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/level/CustomCropsChunk.java @@ -21,7 +21,7 @@ import net.momirealms.customcrops.api.mechanic.item.Crop; import net.momirealms.customcrops.api.mechanic.item.Fertilizer; import net.momirealms.customcrops.api.mechanic.item.Pot; import net.momirealms.customcrops.api.mechanic.item.Sprinkler; -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.SimpleLocation; @@ -33,7 +33,9 @@ public interface CustomCropsChunk { CustomCropsWorld getCustomCropsWorld(); - ChunkCoordinate getChunkCoordinate(); + CustomCropsRegion getCustomCropsRegion(); + + ChunkPos getChunkPos(); void secondTimer(); diff --git a/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/level/CustomCropsRegion.java b/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/level/CustomCropsRegion.java new file mode 100644 index 0000000..2825419 --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/level/CustomCropsRegion.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customcrops.api.mechanic.world.level; + +import net.momirealms.customcrops.api.mechanic.world.ChunkPos; +import net.momirealms.customcrops.api.mechanic.world.RegionPos; +import org.jetbrains.annotations.Nullable; + +import java.util.Map; + +public interface CustomCropsRegion { + + CustomCropsWorld getCustomCropsWorld(); + + byte @Nullable [] getChunkBytes(ChunkPos pos); + + RegionPos getRegionPos(); + + void removeChunk(ChunkPos pos); + + void saveChunk(ChunkPos pos, byte[] data); + + Map getRegionDataToSave(); + + boolean canPrune(); +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/level/CustomCropsWorld.java b/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/level/CustomCropsWorld.java index af2f960..66d5984 100644 --- a/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/level/CustomCropsWorld.java +++ b/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/level/CustomCropsWorld.java @@ -21,8 +21,9 @@ import net.momirealms.customcrops.api.mechanic.item.Crop; import net.momirealms.customcrops.api.mechanic.item.Fertilizer; import net.momirealms.customcrops.api.mechanic.item.Pot; import net.momirealms.customcrops.api.mechanic.item.Sprinkler; -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.RegionPos; import net.momirealms.customcrops.api.mechanic.world.SimpleLocation; import net.momirealms.customcrops.api.mechanic.world.season.Season; import org.bukkit.World; @@ -33,11 +34,15 @@ import java.util.Optional; public interface CustomCropsWorld { + void save(); + void startTick(); void cancelTick(); - CustomCropsChunk removeLazyChunkAt(ChunkCoordinate chunkCoordinate); + boolean isRegionLoaded(RegionPos regionPos); + + CustomCropsChunk removeLazyChunkAt(ChunkPos chunkPos); WorldSetting getWorldSetting(); @@ -49,13 +54,17 @@ public interface CustomCropsWorld { String getWorldName(); - boolean isChunkLoaded(ChunkCoordinate chunkCoordinate); + boolean isChunkLoaded(ChunkPos chunkPos); - Optional getChunkAt(ChunkCoordinate chunkCoordinate); + Optional getLoadedChunkAt(ChunkPos chunkPos); + + Optional getLoadedRegionAt(RegionPos regionPos); + + void loadRegion(CustomCropsRegion region); void loadChunk(CustomCropsChunk chunk); - void unloadChunk(ChunkCoordinate chunkCoordinate); + void unloadChunk(ChunkPos chunkPos); void setInfoData(WorldInfoData infoData); diff --git a/build.gradle.kts b/build.gradle.kts index 7b7a748..bfd1354 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -8,7 +8,7 @@ plugins { allprojects { project.group = "net.momirealms" - project.version = "3.4.2.3" + project.version = "3.4.3.0" apply() apply(plugin = "java") diff --git a/plugin/src/main/java/net/momirealms/customcrops/CustomCropsPluginImpl.java b/plugin/src/main/java/net/momirealms/customcrops/CustomCropsPluginImpl.java index 739acb3..917ad0e 100644 --- a/plugin/src/main/java/net/momirealms/customcrops/CustomCropsPluginImpl.java +++ b/plugin/src/main/java/net/momirealms/customcrops/CustomCropsPluginImpl.java @@ -103,7 +103,6 @@ public class CustomCropsPluginImpl extends CustomCropsPlugin { this.disableNBTAPILogs(); Migration.tryUpdating(); this.reload(); - this.worldManager.init(); if (ConfigManager.metrics()) new Metrics(this, 16593); if (ConfigManager.checkUpdate()) { this.versionManager.checkUpdate().thenAccept(result -> { diff --git a/plugin/src/main/java/net/momirealms/customcrops/manager/CommandManager.java b/plugin/src/main/java/net/momirealms/customcrops/manager/CommandManager.java index 8edb501..74829ed 100644 --- a/plugin/src/main/java/net/momirealms/customcrops/manager/CommandManager.java +++ b/plugin/src/main/java/net/momirealms/customcrops/manager/CommandManager.java @@ -21,7 +21,6 @@ import dev.jorel.commandapi.*; import dev.jorel.commandapi.arguments.ArgumentSuggestions; import dev.jorel.commandapi.arguments.IntegerArgument; import dev.jorel.commandapi.arguments.StringArgument; -import dev.jorel.commandapi.arguments.WorldArgument; import net.momirealms.customcrops.api.CustomCropsPlugin; import net.momirealms.customcrops.api.common.Initable; import net.momirealms.customcrops.api.integration.SeasonInterface; @@ -33,7 +32,9 @@ import net.momirealms.customcrops.api.mechanic.world.level.CustomCropsSection; import net.momirealms.customcrops.api.mechanic.world.level.CustomCropsWorld; import net.momirealms.customcrops.api.mechanic.world.season.Season; import net.momirealms.customcrops.compatibility.season.InBuiltSeason; +import org.bukkit.Bukkit; import org.bukkit.World; +import org.bukkit.generator.WorldInfo; import java.util.Locale; import java.util.Optional; @@ -91,10 +92,15 @@ public class CommandManager implements Initable { private CommandAPICommand getForceTickCommand() { return new CommandAPICommand("force-tick") - .withArguments(new WorldArgument("world")) + .withArguments(new StringArgument("world").replaceSuggestions(ArgumentSuggestions.strings(commandSenderSuggestionInfo -> Bukkit.getWorlds().stream().map(WorldInfo::getName).toList().toArray(new String[0])))) .withArguments(new StringArgument("type").replaceSuggestions(ArgumentSuggestions.strings("sprinkler", "crop", "pot", "scarecrow", "greenhouse"))) .executes((sender, args) -> { - World world = (World) args.get("world"); + String worldName = (String) args.get("world"); + World world = Bukkit.getWorld(worldName); + if (world == null) { + plugin.getAdventure().sendMessageWithPrefix(sender, "CustomCrops is not enabled in that world"); + return; + } ItemType itemType = ItemType.valueOf(((String) args.get("type")).toUpperCase(Locale.ENGLISH)); Optional customCropsWorld = plugin.getWorldManager().getCustomCropsWorld(world); if (customCropsWorld.isEmpty()) { @@ -119,16 +125,26 @@ public class CommandManager implements Initable { return new CommandAPICommand("date") .withSubcommands( new CommandAPICommand("get") - .withArguments(new WorldArgument("world")) + .withArguments(new StringArgument("world").replaceSuggestions(ArgumentSuggestions.strings(commandSenderSuggestionInfo -> Bukkit.getWorlds().stream().map(WorldInfo::getName).toList().toArray(new String[0])))) .executes((sender, args) -> { - World world = (World) args.get("world"); + String worldName = (String) args.get("world"); + World world = Bukkit.getWorld(worldName); + if (world == null) { + plugin.getAdventure().sendMessageWithPrefix(sender, "CustomCrops is not enabled in that world"); + return; + } plugin.getAdventure().sendMessageWithPrefix(sender, String.valueOf(plugin.getIntegrationManager().getDate(world))); }), new CommandAPICommand("set") - .withArguments(new WorldArgument("world")) + .withArguments(new StringArgument("world").replaceSuggestions(ArgumentSuggestions.strings(commandSenderSuggestionInfo -> Bukkit.getWorlds().stream().map(WorldInfo::getName).toList().toArray(new String[0])))) .withArguments(new IntegerArgument("date",1)) .executes((sender, args) -> { - World world = (World) args.get("world"); + String worldName = (String) args.get("world"); + World world = Bukkit.getWorld(worldName); + if (world == null) { + plugin.getAdventure().sendMessageWithPrefix(sender, "CustomCrops is not enabled in that world"); + return; + } int date = (int) args.getOrDefault("date", 1); SeasonInterface seasonInterface = plugin.getIntegrationManager().getSeasonInterface(); if (!(seasonInterface instanceof InBuiltSeason inBuiltSeason)) { @@ -159,13 +175,18 @@ public class CommandManager implements Initable { return new CommandAPICommand("season") .withSubcommands( new CommandAPICommand("get") - .withArguments(new WorldArgument("world")) + .withArguments(new StringArgument("world").replaceSuggestions(ArgumentSuggestions.strings(commandSenderSuggestionInfo -> Bukkit.getWorlds().stream().map(WorldInfo::getName).toList().toArray(new String[0])))) .executes((sender, args) -> { - World world = (World) args.get("world"); + String worldName = (String) args.get("world"); + World world = Bukkit.getWorld(worldName); + if (world == null) { + plugin.getAdventure().sendMessageWithPrefix(sender, "CustomCrops is not enabled in that world"); + return; + } plugin.getAdventure().sendMessageWithPrefix(sender, MessageManager.seasonTranslation(plugin.getIntegrationManager().getSeason(world))); }), new CommandAPICommand("set") - .withArguments(new WorldArgument("world")) + .withArguments(new StringArgument("world").replaceSuggestions(ArgumentSuggestions.strings(commandSenderSuggestionInfo -> Bukkit.getWorlds().stream().map(WorldInfo::getName).toList().toArray(new String[0])))) .withArguments(new StringArgument("season") .replaceSuggestions(ArgumentSuggestions.stringsWithTooltips(info -> new IStringTooltip[] { @@ -177,7 +198,12 @@ public class CommandManager implements Initable { )) ) .executes((sender, args) -> { - World world = (World) args.get("world"); + String worldName = (String) args.get("world"); + World world = Bukkit.getWorld(worldName); + if (world == null) { + plugin.getAdventure().sendMessageWithPrefix(sender, "CustomCrops is not enabled in that world"); + return; + } String seasonName = (String) args.get("season"); SeasonInterface seasonInterface = plugin.getIntegrationManager().getSeasonInterface(); diff --git a/plugin/src/main/java/net/momirealms/customcrops/mechanic/action/ActionManagerImpl.java b/plugin/src/main/java/net/momirealms/customcrops/mechanic/action/ActionManagerImpl.java index 1ec8bd6..e97f141 100644 --- a/plugin/src/main/java/net/momirealms/customcrops/mechanic/action/ActionManagerImpl.java +++ b/plugin/src/main/java/net/momirealms/customcrops/mechanic/action/ActionManagerImpl.java @@ -41,7 +41,7 @@ import net.momirealms.customcrops.api.mechanic.misc.CRotation; import net.momirealms.customcrops.api.mechanic.misc.Reason; import net.momirealms.customcrops.api.mechanic.misc.Value; import net.momirealms.customcrops.api.mechanic.requirement.Requirement; -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.SimpleLocation; import net.momirealms.customcrops.api.mechanic.world.level.WorldCrop; @@ -443,7 +443,7 @@ public class ActionManagerImpl implements ActionManager { if (Math.random() > chance) return; Location location = state.getLocation(); plugin.getWorldManager().getCustomCropsWorld(location.getWorld()) - .flatMap(world -> world.getChunkAt(ChunkCoordinate.getByBukkitChunk(location.getChunk()))) + .flatMap(world -> world.getLoadedChunkAt(ChunkPos.getByBukkitChunk(location.getChunk()))) .flatMap(chunk -> chunk.getBlockAt(SimpleLocation.of(location))) .ifPresent(block -> { block.tick(1); diff --git a/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/ItemManagerImpl.java b/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/ItemManagerImpl.java index ce7dadc..5f7f147 100644 --- a/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/ItemManagerImpl.java +++ b/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/ItemManagerImpl.java @@ -2020,6 +2020,9 @@ public class ItemManagerImpl implements ItemManager { if (optionalPot.isEmpty()) { plugin.debug("Found a pot without data interacted by " + player.getName() + " at " + location); WorldPot newPot = new MemoryPot(simpleLocation, pot.getKey()); + if (pot.isWetPot(potItemID)) { + newPot.setWater(1); + } plugin.getWorldManager().addPotAt(newPot, simpleLocation); optionalPot = Optional.of(newPot); } else { diff --git a/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/custom/AbstractCustomListener.java b/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/custom/AbstractCustomListener.java index 198aa41..7d0efef 100644 --- a/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/custom/AbstractCustomListener.java +++ b/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/custom/AbstractCustomListener.java @@ -45,6 +45,7 @@ import org.bukkit.event.entity.EntityExplodeEvent; import org.bukkit.event.entity.ItemSpawnEvent; import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.event.player.PlayerItemDamageEvent; +import org.bukkit.event.world.WorldSaveEvent; import org.bukkit.inventory.EquipmentSlot; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; diff --git a/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/impl/PotConfig.java b/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/impl/PotConfig.java index cf0f48e..d2d3abe 100644 --- a/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/impl/PotConfig.java +++ b/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/impl/PotConfig.java @@ -161,4 +161,17 @@ public class PotConfig extends AbstractEventItem implements Pot { public boolean isVanillaBlock() { return isVanillaBlock; } + + @Override + public boolean isWetPot(String id) { + if (id.equals(getWetItem())) { + return true; + } + for (Pair pair : fertilizedPotMap.values()) { + if (pair.right().equals(id)) { + return true; + } + } + return false; + } } diff --git a/plugin/src/main/java/net/momirealms/customcrops/mechanic/misc/migrator/Migration.java b/plugin/src/main/java/net/momirealms/customcrops/mechanic/misc/migrator/Migration.java index c3cf397..cefe581 100644 --- a/plugin/src/main/java/net/momirealms/customcrops/mechanic/misc/migrator/Migration.java +++ b/plugin/src/main/java/net/momirealms/customcrops/mechanic/misc/migrator/Migration.java @@ -34,10 +34,26 @@ public class Migration { if (version == null) return; int versionNumber = Integer.parseInt(version); - if (!(versionNumber >= 25 && versionNumber <= 34)) { + if (versionNumber >= 25 && versionNumber <= 34) { + doV33Migration(config); return; } + if (versionNumber == 35) { + doV343Migration(); + } + } + private static void doV343Migration() { + if (CustomCropsPlugin.get().getWorldManager().getWorldAdaptor() instanceof BukkitWorldAdaptor adaptor) { + for (World world : Bukkit.getWorlds()) { + CWorld temp = new CWorld(CustomCropsPlugin.getInstance().getWorldManager(), world); + temp.setWorldSetting(WorldSetting.of(false,300,true, 1,true,2,true,2,false,false,false,28,-1,-1,-1, 0)); + adaptor.convertWorldFromV342toV343(temp, world); + } + } + } + + private static void doV33Migration(YamlConfiguration config) { // do migration if (config.contains("mechanics.season.sync-season")) { config.set("mechanics.sync-season.enable", config.getBoolean("mechanics.season.sync-season.enable")); @@ -53,7 +69,7 @@ public class Migration { } try { - config.save(configFile); + config.save(new File(CustomCropsPlugin.getInstance().getDataFolder(), "config.yml")); } catch (IOException e) { e.printStackTrace(); } @@ -87,7 +103,7 @@ public class Migration { for (World world : Bukkit.getWorlds()) { CWorld temp = new CWorld(CustomCropsPlugin.getInstance().getWorldManager(), world); temp.setWorldSetting(WorldSetting.of(false,300,true, 1,true,2,true,2,false,false,false,28,-1,-1,-1, 0)); - adaptor.convertWorld(temp, world); + adaptor.convertWorldFromV33toV34(temp, world); } } } diff --git a/plugin/src/main/java/net/momirealms/customcrops/mechanic/world/CChunk.java b/plugin/src/main/java/net/momirealms/customcrops/mechanic/world/CChunk.java index 9257d28..2555f13 100644 --- a/plugin/src/main/java/net/momirealms/customcrops/mechanic/world/CChunk.java +++ b/plugin/src/main/java/net/momirealms/customcrops/mechanic/world/CChunk.java @@ -25,8 +25,8 @@ import net.momirealms.customcrops.api.mechanic.item.Pot; import net.momirealms.customcrops.api.mechanic.item.Sprinkler; import net.momirealms.customcrops.api.mechanic.misc.CRotation; import net.momirealms.customcrops.api.mechanic.requirement.State; -import net.momirealms.customcrops.api.mechanic.world.ChunkCoordinate; import net.momirealms.customcrops.api.mechanic.world.BlockPos; +import net.momirealms.customcrops.api.mechanic.world.ChunkPos; import net.momirealms.customcrops.api.mechanic.world.CustomCropsBlock; import net.momirealms.customcrops.api.mechanic.world.SimpleLocation; import net.momirealms.customcrops.api.mechanic.world.level.*; @@ -44,7 +44,7 @@ import java.util.concurrent.ThreadLocalRandom; public class CChunk implements CustomCropsChunk { private transient CWorld cWorld; - private final ChunkCoordinate chunkCoordinate; + private final ChunkPos chunkPos; private final ConcurrentHashMap loadedSections; private final PriorityQueue queue; private final Set tickedBlocks; @@ -52,9 +52,9 @@ public class CChunk implements CustomCropsChunk { private int loadedSeconds; private int unloadedSeconds; - public CChunk(CWorld cWorld, ChunkCoordinate chunkCoordinate) { + public CChunk(CWorld cWorld, ChunkPos chunkPos) { this.cWorld = cWorld; - this.chunkCoordinate = chunkCoordinate; + this.chunkPos = chunkPos; this.loadedSections = new ConcurrentHashMap<>(64); this.queue = new PriorityQueue<>(); this.unloadedSeconds = 0; @@ -64,7 +64,7 @@ public class CChunk implements CustomCropsChunk { public CChunk( CWorld cWorld, - ChunkCoordinate chunkCoordinate, + ChunkPos chunkPos, int loadedSeconds, long lastLoadedTime, ConcurrentHashMap loadedSections, @@ -72,7 +72,7 @@ public class CChunk implements CustomCropsChunk { HashSet tickedBlocks ) { this.cWorld = cWorld; - this.chunkCoordinate = chunkCoordinate; + this.chunkPos = chunkPos; this.loadedSections = loadedSections; this.lastLoadedTime = lastLoadedTime; this.loadedSeconds = loadedSeconds; @@ -101,8 +101,13 @@ public class CChunk implements CustomCropsChunk { } @Override - public ChunkCoordinate getChunkCoordinate() { - return chunkCoordinate; + public CustomCropsRegion getCustomCropsRegion() { + return cWorld.getLoadedRegionAt(chunkPos.getRegionPos()).orElse(null); + } + + @Override + public ChunkPos getChunkPos() { + return chunkPos; } @Override diff --git a/plugin/src/main/java/net/momirealms/customcrops/mechanic/world/CRegion.java b/plugin/src/main/java/net/momirealms/customcrops/mechanic/world/CRegion.java new file mode 100644 index 0000000..5a6808e --- /dev/null +++ b/plugin/src/main/java/net/momirealms/customcrops/mechanic/world/CRegion.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customcrops.mechanic.world; + +import net.momirealms.customcrops.api.mechanic.world.ChunkPos; +import net.momirealms.customcrops.api.mechanic.world.RegionPos; +import net.momirealms.customcrops.api.mechanic.world.level.CustomCropsRegion; +import net.momirealms.customcrops.api.mechanic.world.level.CustomCropsWorld; +import org.jetbrains.annotations.Nullable; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class CRegion implements CustomCropsRegion { + + private final CWorld cWorld; + private final RegionPos regionPos; + private final ConcurrentHashMap cachedChunkBytes; + + public CRegion(CWorld cWorld, RegionPos regionPos) { + this.cWorld = cWorld; + this.regionPos = regionPos; + this.cachedChunkBytes = new ConcurrentHashMap<>(); + } + + public CRegion(CWorld cWorld, RegionPos regionPos, ConcurrentHashMap cachedChunkBytes) { + this.cWorld = cWorld; + this.regionPos = regionPos; + this.cachedChunkBytes = cachedChunkBytes; + } + + @Override + public CustomCropsWorld getCustomCropsWorld() { + return cWorld; + } + + @Nullable + @Override + public byte[] getChunkBytes(ChunkPos pos) { + return cachedChunkBytes.get(pos); + } + + @Override + public RegionPos getRegionPos() { + return regionPos; + } + + @Override + public void removeChunk(ChunkPos pos) { + cachedChunkBytes.remove(pos); + } + + @Override + public void saveChunk(ChunkPos pos, byte[] data) { + cachedChunkBytes.put(pos, data); + } + + @Override + public Map getRegionDataToSave() { + return new HashMap<>(cachedChunkBytes); + } + + @Override + public boolean canPrune() { + return cachedChunkBytes.size() == 0; + } +} diff --git a/plugin/src/main/java/net/momirealms/customcrops/mechanic/world/CWorld.java b/plugin/src/main/java/net/momirealms/customcrops/mechanic/world/CWorld.java index c3a726a..0741514 100644 --- a/plugin/src/main/java/net/momirealms/customcrops/mechanic/world/CWorld.java +++ b/plugin/src/main/java/net/momirealms/customcrops/mechanic/world/CWorld.java @@ -26,20 +26,23 @@ import net.momirealms.customcrops.api.mechanic.item.Crop; import net.momirealms.customcrops.api.mechanic.item.Fertilizer; import net.momirealms.customcrops.api.mechanic.item.Pot; import net.momirealms.customcrops.api.mechanic.item.Sprinkler; -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.RegionPos; import net.momirealms.customcrops.api.mechanic.world.SimpleLocation; import net.momirealms.customcrops.api.mechanic.world.level.*; import net.momirealms.customcrops.api.mechanic.world.season.Season; import net.momirealms.customcrops.api.scheduler.CancellableTask; import net.momirealms.customcrops.api.util.LogUtils; import net.momirealms.customcrops.utils.EventUtils; -import org.bukkit.Chunk; import org.bukkit.World; import org.jetbrains.annotations.Nullable; import java.lang.ref.WeakReference; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Map; +import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; @@ -47,21 +50,42 @@ public class CWorld implements CustomCropsWorld { private final WorldManager worldManager; private final WeakReference world; - private final ConcurrentHashMap loadedChunks; - private final ConcurrentHashMap lazyChunks; + private final ConcurrentHashMap loadedChunks; + private final ConcurrentHashMap lazyChunks; + private final ConcurrentHashMap loadedRegions; private WorldSetting setting; private WorldInfoData infoData; private final String worldName; private CancellableTask worldTask; private int currentMinecraftDay; + private int regionTimer; public CWorld(WorldManager worldManager, World world) { this.world = new WeakReference<>(world); this.worldManager = worldManager; this.loadedChunks = new ConcurrentHashMap<>(); this.lazyChunks = new ConcurrentHashMap<>(); + this.loadedRegions = new ConcurrentHashMap<>(); this.worldName = world.getName(); this.currentMinecraftDay = (int) (world.getFullTime() / 24000); + this.regionTimer = 0; + } + + @Override + public void save() { + long time1 = System.currentTimeMillis(); + worldManager.saveInfoData(this); + for (CChunk chunk : loadedChunks.values()) { + worldManager.saveChunkToCachedRegion(chunk); + } + for (CChunk chunk : lazyChunks.values()) { + worldManager.saveChunkToCachedRegion(chunk); + } + for (CRegion region : loadedRegions.values()) { + worldManager.saveRegionToFile(region); + } + long time2 = System.currentTimeMillis(); + CustomCropsPlugin.get().debug("Took " + (time2-time1) + "ms to save world " + worldName + ". Saved " + (lazyChunks.size() + loadedChunks.size()) + " chunks."); } @Override @@ -77,8 +101,8 @@ public class CWorld implements CustomCropsWorld { } private void timer() { - ArrayList> chunksToSave = new ArrayList<>(); - for (Map.Entry lazyEntry : lazyChunks.entrySet()) { + ArrayList> chunksToSave = new ArrayList<>(); + for (Map.Entry lazyEntry : lazyChunks.entrySet()) { CChunk chunk = lazyEntry.getValue(); int sec = chunk.getUnloadedSeconds() + 1; if (sec >= 30) { @@ -87,9 +111,9 @@ public class CWorld implements CustomCropsWorld { chunk.setUnloadedSeconds(sec); } } - for (Pair pair : chunksToSave) { + for (Pair pair : chunksToSave) { lazyChunks.remove(pair.left()); - worldManager.saveChunkToFile(pair.right()); + worldManager.saveChunkToCachedRegion(pair.right()); } if (setting.isAutoSeasonChange()) { this.updateSeasonAndDate(); @@ -99,6 +123,22 @@ public class CWorld implements CustomCropsWorld { chunk.secondTimer(); } } + + // timer check to unload regions + this.regionTimer++; + if (this.regionTimer >= 600) { + this.regionTimer = 0; + ArrayList removed = new ArrayList<>(); + for (Map.Entry entry : loadedRegions.entrySet()) { + if (isRegionNoLongerLoaded(entry.getKey())) { + worldManager.saveRegionToFile(entry.getValue()); + removed.add(entry.getKey()); + } + } + for (RegionPos pos : removed) { + loadedRegions.remove(pos); + } + } } private void updateSeasonAndDate() { @@ -130,8 +170,8 @@ public class CWorld implements CustomCropsWorld { } @Override - public CustomCropsChunk removeLazyChunkAt(ChunkCoordinate chunkCoordinate) { - return lazyChunks.remove(chunkCoordinate); + public CustomCropsChunk removeLazyChunkAt(ChunkPos chunkPos) { + return lazyChunks.remove(chunkPos); } @Override @@ -161,31 +201,51 @@ public class CWorld implements CustomCropsWorld { } @Override - public boolean isChunkLoaded(ChunkCoordinate chunkCoordinate) { - return loadedChunks.containsKey(chunkCoordinate); + public boolean isChunkLoaded(ChunkPos chunkPos) { + return loadedChunks.containsKey(chunkPos); } @Override - public Optional getChunkAt(ChunkCoordinate chunkCoordinate) { - return Optional.ofNullable(createOrGetChunk(chunkCoordinate)); + public boolean isRegionLoaded(RegionPos regionPos) { + return loadedRegions.containsKey(regionPos); + } + + @Override + public Optional getLoadedChunkAt(ChunkPos chunkPos) { + return Optional.ofNullable(createOrGetChunk(chunkPos)); + } + + @Override + public Optional getLoadedRegionAt(RegionPos regionPos) { + return Optional.ofNullable(loadedRegions.get(regionPos)); + } + + @Override + public void loadRegion(CustomCropsRegion region) { + RegionPos regionPos = region.getRegionPos(); + if (loadedRegions.containsKey(regionPos)) { + LogUtils.warn("Invalid operation: Loaded region is loaded again." + regionPos); + return; + } + loadedRegions.put(regionPos, (CRegion) region); } @Override public void loadChunk(CustomCropsChunk chunk) { - ChunkCoordinate chunkCoordinate = chunk.getChunkCoordinate(); - if (loadedChunks.containsKey(chunkCoordinate)) { - LogUtils.warn("Invalid operation: Loaded chunk is loaded again." + chunkCoordinate); + ChunkPos chunkPos = chunk.getChunkPos(); + if (loadedChunks.containsKey(chunkPos)) { + LogUtils.warn("Invalid operation: Loaded chunk is loaded again." + chunkPos); return; } - loadedChunks.put(chunkCoordinate, (CChunk) chunk); + loadedChunks.put(chunkPos, (CChunk) chunk); } @Override - public void unloadChunk(ChunkCoordinate chunkCoordinate) { - CChunk chunk = loadedChunks.remove(chunkCoordinate); + public void unloadChunk(ChunkPos chunkPos) { + CChunk chunk = loadedChunks.remove(chunkPos); if (chunk != null) { chunk.updateLastLoadedTime(); - lazyChunks.put(chunkCoordinate, chunk); + lazyChunks.put(chunkPos, chunk); } } @@ -256,7 +316,7 @@ public class CWorld implements CustomCropsWorld { @Override public void addWaterToSprinkler(Sprinkler sprinkler, SimpleLocation location, int amount) { - Optional chunk = getChunkAt(location.getChunkCoordinate()); + Optional chunk = getLoadedChunkAt(location.getChunkCoordinate()); if (chunk.isPresent()) { chunk.get().addWaterToSprinkler(sprinkler, location, amount); } else { @@ -266,7 +326,7 @@ public class CWorld implements CustomCropsWorld { @Override public void addFertilizerToPot(Pot pot, Fertilizer fertilizer, SimpleLocation location) { - Optional chunk = getChunkAt(location.getChunkCoordinate()); + Optional chunk = getLoadedChunkAt(location.getChunkCoordinate()); if (chunk.isPresent()) { chunk.get().addFertilizerToPot(pot, fertilizer, location); } else { @@ -276,7 +336,7 @@ public class CWorld implements CustomCropsWorld { @Override public void addWaterToPot(Pot pot, SimpleLocation location, int amount) { - Optional chunk = getChunkAt(location.getChunkCoordinate()); + Optional chunk = getLoadedChunkAt(location.getChunkCoordinate()); if (chunk.isPresent()) { chunk.get().addWaterToPot(pot, location, amount); } else { @@ -286,7 +346,7 @@ public class CWorld implements CustomCropsWorld { @Override public void addPotAt(WorldPot pot, SimpleLocation location) { - Optional chunk = getChunkAt(location.getChunkCoordinate()); + Optional chunk = getLoadedChunkAt(location.getChunkCoordinate()); if (chunk.isPresent()) { chunk.get().addPotAt(pot, location); } else { @@ -296,7 +356,7 @@ public class CWorld implements CustomCropsWorld { @Override public void addSprinklerAt(WorldSprinkler sprinkler, SimpleLocation location) { - Optional chunk = getChunkAt(location.getChunkCoordinate()); + Optional chunk = getLoadedChunkAt(location.getChunkCoordinate()); if (chunk.isPresent()) { chunk.get().addSprinklerAt(sprinkler, location); } else { @@ -306,7 +366,7 @@ public class CWorld implements CustomCropsWorld { @Override public void addCropAt(WorldCrop crop, SimpleLocation location) { - Optional chunk = getChunkAt(location.getChunkCoordinate()); + Optional chunk = getLoadedChunkAt(location.getChunkCoordinate()); if (chunk.isPresent()) { chunk.get().addCropAt(crop, location); } else { @@ -316,7 +376,7 @@ public class CWorld implements CustomCropsWorld { @Override public void addPointToCrop(Crop crop, SimpleLocation location, int points) { - Optional chunk = getChunkAt(location.getChunkCoordinate()); + Optional chunk = getLoadedChunkAt(location.getChunkCoordinate()); if (chunk.isPresent()) { chunk.get().addPointToCrop(crop, location, points); } else { @@ -326,7 +386,7 @@ public class CWorld implements CustomCropsWorld { @Override public void addGlassAt(WorldGlass glass, SimpleLocation location) { - Optional chunk = getChunkAt(location.getChunkCoordinate()); + Optional chunk = getLoadedChunkAt(location.getChunkCoordinate()); if (chunk.isPresent()) { chunk.get().addGlassAt(glass, location); } else { @@ -336,7 +396,7 @@ public class CWorld implements CustomCropsWorld { @Override public void addScarecrowAt(WorldScarecrow scarecrow, SimpleLocation location) { - Optional chunk = getChunkAt(location.getChunkCoordinate()); + Optional chunk = getLoadedChunkAt(location.getChunkCoordinate()); if (chunk.isPresent()) { chunk.get().addScarecrowAt(scarecrow, location); } else { @@ -346,7 +406,7 @@ public class CWorld implements CustomCropsWorld { @Override public void removeSprinklerAt(SimpleLocation location) { - Optional chunk = getChunkAt(location.getChunkCoordinate()); + Optional chunk = getLoadedChunkAt(location.getChunkCoordinate()); if (chunk.isPresent()) { chunk.get().removeSprinklerAt(location); } else { @@ -356,7 +416,7 @@ public class CWorld implements CustomCropsWorld { @Override public void removePotAt(SimpleLocation location) { - Optional chunk = getChunkAt(location.getChunkCoordinate()); + Optional chunk = getLoadedChunkAt(location.getChunkCoordinate()); if (chunk.isPresent()) { chunk.get().removePotAt(location); } else { @@ -366,7 +426,7 @@ public class CWorld implements CustomCropsWorld { @Override public void removeCropAt(SimpleLocation location) { - Optional chunk = getChunkAt(location.getChunkCoordinate()); + Optional chunk = getLoadedChunkAt(location.getChunkCoordinate()); if (chunk.isPresent()) { chunk.get().removeCropAt(location); } else { @@ -376,7 +436,7 @@ public class CWorld implements CustomCropsWorld { @Override public void removeGlassAt(SimpleLocation location) { - Optional chunk = getChunkAt(location.getChunkCoordinate()); + Optional chunk = getLoadedChunkAt(location.getChunkCoordinate()); if (chunk.isPresent()) { chunk.get().removeGlassAt(location); } else { @@ -386,7 +446,7 @@ public class CWorld implements CustomCropsWorld { @Override public void removeScarecrowAt(SimpleLocation location) { - Optional chunk = getChunkAt(location.getChunkCoordinate()); + Optional chunk = getLoadedChunkAt(location.getChunkCoordinate()); if (chunk.isPresent()) { chunk.get().removeScarecrowAt(location); } else { @@ -396,7 +456,7 @@ public class CWorld implements CustomCropsWorld { @Override public CustomCropsBlock removeAnythingAt(SimpleLocation location) { - Optional chunk = getChunkAt(location.getChunkCoordinate()); + Optional chunk = getLoadedChunkAt(location.getChunkCoordinate()); if (chunk.isPresent()) { return chunk.get().removeBlockAt(location); } else { @@ -406,33 +466,35 @@ public class CWorld implements CustomCropsWorld { } @Nullable - private CustomCropsChunk createOrGetChunk(ChunkCoordinate chunkCoordinate) { + private CustomCropsChunk createOrGetChunk(ChunkPos chunkPos) { World bukkitWorld = world.get(); if (bukkitWorld == null) return null; - CChunk chunk = loadedChunks.get(chunkCoordinate); + CChunk chunk = loadedChunks.get(chunkPos); if (chunk != null) { return chunk; } - if (bukkitWorld.isChunkLoaded(chunkCoordinate.x(), chunkCoordinate.z())) { - chunk = new CChunk(this, chunkCoordinate); + // is a loaded chunk, but it doesn't have customcrops data + if (bukkitWorld.isChunkLoaded(chunkPos.x(), chunkPos.z())) { + chunk = new CChunk(this, chunkPos); loadChunk(chunk); return chunk; } else { - if (bukkitWorld.isChunkGenerated(chunkCoordinate.x(), chunkCoordinate.z())) { - Chunk bukkitChunk = bukkitWorld.getChunkAt(chunkCoordinate.x(), chunkCoordinate.z()); - worldManager.handleChunkLoad(bukkitChunk); - chunk = loadedChunks.get(chunkCoordinate); - return Objects.requireNonNullElseGet(chunk, () -> new CChunk(this, chunkCoordinate)); - } else { - return null; - } + // is an unloaded chunk, but has been generated +// if (bukkitWorld.isChunkGenerated(chunkPos.x(), chunkPos.z())) { +// Chunk bukkitChunk = bukkitWorld.getChunkAt(chunkPos.x(), chunkPos.z()); +// worldManager.handleChunkLoad(bukkitChunk); +// chunk = loadedChunks.get(chunkPos); +// return Objects.requireNonNullElseGet(chunk, () -> new CChunk(this, chunkPos)); +// } else { + return null; +// } } } @Override public boolean isPotReachLimit(SimpleLocation location) { - Optional chunk = getChunkAt(location.getChunkCoordinate()); + Optional chunk = getLoadedChunkAt(location.getChunkCoordinate()); if (chunk.isEmpty()) { LogUtils.warn("Invalid operation: Querying pot amount from a not generated chunk"); return true; @@ -443,7 +505,7 @@ public class CWorld implements CustomCropsWorld { @Override public boolean isCropReachLimit(SimpleLocation location) { - Optional chunk = getChunkAt(location.getChunkCoordinate()); + Optional chunk = getLoadedChunkAt(location.getChunkCoordinate()); if (chunk.isEmpty()) { LogUtils.warn("Invalid operation: Querying crop amount from a not generated chunk"); return true; @@ -454,7 +516,7 @@ public class CWorld implements CustomCropsWorld { @Override public boolean isSprinklerReachLimit(SimpleLocation location) { - Optional chunk = getChunkAt(location.getChunkCoordinate()); + Optional chunk = getLoadedChunkAt(location.getChunkCoordinate()); if (chunk.isEmpty()) { LogUtils.warn("Invalid operation: Querying sprinkler amount from a not generated chunk"); return true; @@ -463,10 +525,18 @@ public class CWorld implements CustomCropsWorld { return chunk.get().getSprinklerAmount() >= setting.getSprinklerPerChunk(); } - public Collection getAllChunksToSave() { - ArrayList chunks = new ArrayList<>(); - chunks.addAll(lazyChunks.values()); - chunks.addAll(loadedChunks.values()); - return chunks; + private boolean isRegionNoLongerLoaded(RegionPos region) { + World w = world.get(); + if (w != null) { + for (int chunkX = region.x() * 32; chunkX < region.x() * 32 + 32; chunkX++) { + for (int chunkZ = region.z() * 32; chunkZ < region.z() * 32 + 32; chunkZ++) { + // if a chunk is unloaded, then it should not be in the loaded chunks map + if (w.isChunkLoaded(chunkX, chunkZ) || lazyChunks.containsKey(ChunkPos.of(chunkX, chunkZ))) { + return false; + } + } + } + } + return true; } } diff --git a/plugin/src/main/java/net/momirealms/customcrops/mechanic/world/SerializableChunk.java b/plugin/src/main/java/net/momirealms/customcrops/mechanic/world/SerializableChunk.java index d869d75..b18e223 100644 --- a/plugin/src/main/java/net/momirealms/customcrops/mechanic/world/SerializableChunk.java +++ b/plugin/src/main/java/net/momirealms/customcrops/mechanic/world/SerializableChunk.java @@ -66,4 +66,8 @@ public class SerializableChunk { public int[] getTicked() { return ticked; } + + public boolean canPrune() { + return sections.size() == 0; + } } 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 84d412f..955d39f 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 @@ -21,7 +21,7 @@ import net.momirealms.customcrops.api.CustomCropsPlugin; import net.momirealms.customcrops.api.manager.WorldManager; import net.momirealms.customcrops.api.mechanic.item.*; 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.SimpleLocation; import net.momirealms.customcrops.api.mechanic.world.level.*; @@ -39,6 +39,7 @@ import org.bukkit.event.HandlerList; import org.bukkit.event.Listener; import org.bukkit.event.world.ChunkLoadEvent; import org.bukkit.event.world.ChunkUnloadEvent; +import org.bukkit.event.world.WorldSaveEvent; import org.jetbrains.annotations.NotNull; import java.util.*; @@ -404,7 +405,7 @@ public class WorldManagerImpl implements WorldManager, Listener { public void removeGlassAt(@NotNull SimpleLocation location) { CWorld cWorld = loadedWorlds.get(location.getWorldName()); if (cWorld == null) { - LogUtils.warn("Unsupported operation: Removing crop from unloaded world " + location); + LogUtils.warn("Unsupported operation: Removing glass from unloaded world " + location); return; } cWorld.removeGlassAt(location); @@ -463,20 +464,20 @@ public class WorldManagerImpl implements WorldManager, Listener { return; CustomCropsWorld customCropsWorld = optional.get(); - ChunkCoordinate chunkCoordinate = ChunkCoordinate.getByBukkitChunk(bukkitChunk); + ChunkPos chunkPos = ChunkPos.getByBukkitChunk(bukkitChunk); - if (customCropsWorld.isChunkLoaded(chunkCoordinate)) { + if (customCropsWorld.isChunkLoaded(chunkPos)) { return; } // load chunks - this.worldAdaptor.loadDynamicData(customCropsWorld, chunkCoordinate); + this.worldAdaptor.loadChunkData(customCropsWorld, chunkPos); // offline grow part if (!customCropsWorld.getWorldSetting().isOfflineGrow()) return; // If chunk data not exists, return - Optional optionalChunk = customCropsWorld.getChunkAt(chunkCoordinate); + Optional optionalChunk = customCropsWorld.getLoadedChunkAt(chunkPos); if (optionalChunk.isEmpty()) { return; } @@ -493,9 +494,9 @@ public class WorldManagerImpl implements WorldManager, Listener { return; CustomCropsWorld customCropsWorld = optional.get(); - ChunkCoordinate chunkCoordinate = ChunkCoordinate.getByBukkitChunk(bukkitChunk); + ChunkPos chunkPos = ChunkPos.getByBukkitChunk(bukkitChunk); - this.worldAdaptor.unloadDynamicData(customCropsWorld, chunkCoordinate); + this.worldAdaptor.unloadChunkData(customCropsWorld, chunkPos); } @EventHandler @@ -509,12 +510,22 @@ public class WorldManagerImpl implements WorldManager, Listener { } @Override - public void saveChunkToFile(CustomCropsChunk chunk) { - this.worldAdaptor.saveDynamicData(chunk.getCustomCropsWorld(), chunk); + public void saveChunkToCachedRegion(CustomCropsChunk chunk) { + this.worldAdaptor.saveChunkToCachedRegion(chunk); + } + + @Override + public void saveRegionToFile(CustomCropsRegion region) { + this.worldAdaptor.saveRegion(region); } @Override public AbstractWorldAdaptor getWorldAdaptor() { return worldAdaptor; } + + @Override + public void saveInfoData(CustomCropsWorld customCropsWorld) { + this.worldAdaptor.saveInfoData(customCropsWorld); + } } 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 3f2200a..2ba4913 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 @@ -30,6 +30,7 @@ import net.momirealms.customcrops.api.manager.ConfigManager; import net.momirealms.customcrops.api.manager.WorldManager; import net.momirealms.customcrops.api.mechanic.world.*; import net.momirealms.customcrops.api.mechanic.world.level.CustomCropsChunk; +import net.momirealms.customcrops.api.mechanic.world.level.CustomCropsRegion; import net.momirealms.customcrops.api.mechanic.world.level.CustomCropsWorld; import net.momirealms.customcrops.api.mechanic.world.level.WorldInfoData; import net.momirealms.customcrops.api.mechanic.world.season.Season; @@ -47,6 +48,7 @@ import org.bukkit.World; import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.event.EventHandler; import org.bukkit.event.world.WorldLoadEvent; +import org.bukkit.event.world.WorldSaveEvent; import org.bukkit.event.world.WorldUnloadEvent; import org.bukkit.persistence.PersistentDataType; import org.jetbrains.annotations.Nullable; @@ -70,6 +72,15 @@ public class BukkitWorldAdaptor extends AbstractWorldAdaptor { @Override public void unload(CustomCropsWorld customCropsWorld) { + World world = customCropsWorld.getWorld(); + if (world != null) { + new File(world.getWorldFolder(), "customcrops").mkdir(); + customCropsWorld.save(); + } + } + + @Override + public void saveInfoData(CustomCropsWorld customCropsWorld) { CWorld cWorld = (CWorld) customCropsWorld; World world = cWorld.getWorld(); if (world == null) { @@ -77,15 +88,8 @@ public class BukkitWorldAdaptor extends AbstractWorldAdaptor { return; } - // save world data into psd world.getPersistentDataContainer().set(key, PersistentDataType.STRING, gson.toJson(cWorld.getInfoData())); - - new File(world.getWorldFolder(), "customcrops").mkdir(); - - for (CChunk chunk : cWorld.getAllChunksToSave()) { - saveDynamicData(cWorld, chunk); - } } @Override @@ -102,7 +106,7 @@ public class BukkitWorldAdaptor extends AbstractWorldAdaptor { // try converting legacy worlds if (ConfigManager.convertWorldOnLoad()) { - convertWorld(cWorld, world); + convertWorldFromV33toV34(cWorld, world); return; } @@ -113,7 +117,7 @@ public class BukkitWorldAdaptor extends AbstractWorldAdaptor { } @Override - public void loadDynamicData(CustomCropsWorld customCropsWorld, ChunkCoordinate chunkCoordinate) { + public void loadChunkData(CustomCropsWorld customCropsWorld, ChunkPos chunkPos) { CWorld cWorld = (CWorld) customCropsWorld; World world = cWorld.getWorld(); if (world == null) { @@ -121,35 +125,81 @@ public class BukkitWorldAdaptor extends AbstractWorldAdaptor { return; } + long time1 = System.currentTimeMillis(); // load lazy chunks firstly - CustomCropsChunk lazyChunk = customCropsWorld.removeLazyChunkAt(chunkCoordinate); + CustomCropsChunk lazyChunk = cWorld.removeLazyChunkAt(chunkPos); if (lazyChunk != null) { CChunk cChunk = (CChunk) lazyChunk; cChunk.setUnloadedSeconds(0); cWorld.loadChunk(cChunk); + long time2 = System.currentTimeMillis(); + CustomCropsPlugin.get().debug("Took " + (time2-time1) + "ms to load chunk " + chunkPos + " from lazy chunks"); return; } - // create or get chunk files - File data = getChunkDataFilePath(world, chunkCoordinate); - if (!data.exists()) + + // check if region is loaded, load if not loaded + RegionPos regionPos = RegionPos.getByChunkPos(chunkPos); + Optional optionalRegion = cWorld.getLoadedRegionAt(regionPos); + if (optionalRegion.isPresent()) { + CustomCropsRegion region = optionalRegion.get(); + byte[] bytes = region.getChunkBytes(chunkPos); + if (bytes != null) { + try { + DataInputStream dataStream = new DataInputStream(new ByteArrayInputStream(bytes)); + CChunk chunk = deserializeChunk(cWorld, dataStream); + dataStream.close(); + cWorld.loadChunk(chunk); + long time2 = System.currentTimeMillis(); + CustomCropsPlugin.get().debug("Took " + (time2-time1) + "ms to load chunk " + chunkPos + " from cached region"); + } catch (IOException e) { + LogUtils.severe("Failed to load CustomCrops data at " + chunkPos); + e.printStackTrace(); + region.removeChunk(chunkPos); + } + } return; - // load chunk from local files - long time1 = System.currentTimeMillis(); - try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(getChunkDataFilePath(world, chunkCoordinate)))) { + } + + // if region file not exist, create one + File data = getRegionDataFilePath(world, regionPos); + if (!data.exists()) { + cWorld.loadRegion(new CRegion(cWorld, regionPos)); + return; + } + + // load region from local files + try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(data))) { DataInputStream dataStream = new DataInputStream(bis); - CChunk chunk = deserialize(cWorld, dataStream); + CRegion region = deserializeRegion(cWorld, dataStream); dataStream.close(); - cWorld.loadChunk(chunk); - long time2 = System.currentTimeMillis(); - CustomCropsPlugin.get().debug("Took " + (time2-time1) + "ms to load chunk " + chunkCoordinate); + cWorld.loadRegion(region); + byte[] bytes = region.getChunkBytes(chunkPos); + if (bytes != null) { + try { + DataInputStream chunkStream = new DataInputStream(new ByteArrayInputStream(bytes)); + CChunk chunk = deserializeChunk(cWorld, chunkStream); + chunkStream.close(); + cWorld.loadChunk(chunk); + long time2 = System.currentTimeMillis(); + CustomCropsPlugin.get().debug("Took " + (time2-time1) + "ms to load chunk " + chunkPos); + } catch (IOException e) { + LogUtils.severe("Failed to load CustomCrops data at " + chunkPos + ". Deleting corrupted chunk."); + e.printStackTrace(); + region.removeChunk(chunkPos); + } + } else { + long time2 = System.currentTimeMillis(); + CustomCropsPlugin.get().debug("Took " + (time2-time1) + "ms to load region " + regionPos); + } } catch (IOException e) { - LogUtils.severe("Failed to load CustomCrops data at " + chunkCoordinate); + LogUtils.severe("Failed to load CustomCrops region data at " + chunkPos + ". Deleting corrupted region."); e.printStackTrace(); + data.delete(); } } @Override - public void unloadDynamicData(CustomCropsWorld ccWorld, ChunkCoordinate chunkCoordinate) { + public void unloadChunkData(CustomCropsWorld ccWorld, ChunkPos chunkPos) { CWorld cWorld = (CWorld) ccWorld; World world = cWorld.getWorld(); if (world == null) { @@ -157,18 +207,35 @@ public class BukkitWorldAdaptor extends AbstractWorldAdaptor { return; } - cWorld.unloadChunk(chunkCoordinate); + cWorld.unloadChunk(chunkPos); } @Override - public void saveDynamicData(CustomCropsWorld ccWorld, CustomCropsChunk chunk) { + public void saveChunkToCachedRegion(CustomCropsChunk customCropsChunk) { + CustomCropsRegion customCropsRegion = customCropsChunk.getCustomCropsRegion(); + SerializableChunk serializableChunk = toSerializableChunk((CChunk) customCropsChunk); + if (serializableChunk.canPrune()) { + customCropsRegion.removeChunk(customCropsChunk.getChunkPos()); + } else { + customCropsRegion.saveChunk(customCropsChunk.getChunkPos(), serialize(serializableChunk)); + } + } + + @Override + public void saveRegion(CustomCropsRegion customCropsRegion) { + File file = getRegionDataFilePath(customCropsRegion.getCustomCropsWorld().getWorld(), customCropsRegion.getRegionPos()); + if (customCropsRegion.canPrune()) { + file.delete(); + return; + } + long time1 = System.currentTimeMillis(); - try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(getChunkDataFilePath(ccWorld.getWorld(), chunk.getChunkCoordinate())))) { - bos.write(serialize((CChunk) chunk)); + try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file))) { + bos.write(serialize(customCropsRegion)); long time2 = System.currentTimeMillis(); - CustomCropsPlugin.get().debug("Took " + (time2-time1) + "ms to save chunk " + chunk.getChunkCoordinate()); + CustomCropsPlugin.get().debug("Took " + (time2-time1) + "ms to save region " + customCropsRegion.getRegionPos()); } catch (IOException e) { - LogUtils.severe("Failed to save CustomCrops data."); + LogUtils.severe("Failed to save CustomCrops region data." + customCropsRegion.getRegionPos()); e.printStackTrace(); } } @@ -186,15 +253,35 @@ public class BukkitWorldAdaptor extends AbstractWorldAdaptor { worldManager.unloadWorld(event.getWorld()); } - private String getChunkDataFile(ChunkCoordinate chunkCoordinate) { - return chunkCoordinate.x() + "," + chunkCoordinate.z() + ".ccd"; + @EventHandler (ignoreCancelled = true) + public void onWorldSave(WorldSaveEvent event) { + World world = event.getWorld(); + worldManager.getCustomCropsWorld(world).ifPresent(CustomCropsWorld::save); } - private File getChunkDataFilePath(World world, ChunkCoordinate chunkCoordinate) { + @Deprecated + private String getChunkDataFile(ChunkPos chunkPos) { + return chunkPos.x() + "," + chunkPos.z() + ".ccd"; + } + + private String getRegionDataFile(RegionPos regionPos) { + return "r." + regionPos.x() + "." + regionPos.z() + ".mcc"; + } + + @Deprecated + private File getChunkDataFilePath(World world, ChunkPos chunkPos) { if (worldFolder.isEmpty()) { - return new File(world.getWorldFolder(), "customcrops" + File.separator + getChunkDataFile(chunkCoordinate)); + return new File(world.getWorldFolder(), "customcrops" + File.separator + getChunkDataFile(chunkPos)); } else { - return new File(worldFolder, world.getName() + File.separator + "customcrops" + File.separator + getChunkDataFile(chunkCoordinate)); + return new File(worldFolder, world.getName() + File.separator + "customcrops" + File.separator + getChunkDataFile(chunkPos)); + } + } + + private File getRegionDataFilePath(World world, RegionPos regionPos) { + if (worldFolder.isEmpty()) { + return new File(world.getWorldFolder(), "customcrops" + File.separator + getRegionDataFile(regionPos)); + } else { + return new File(worldFolder, world.getName() + File.separator + "customcrops" + File.separator + getRegionDataFile(regionPos)); } } @@ -202,12 +289,51 @@ public class BukkitWorldAdaptor extends AbstractWorldAdaptor { this.worldFolder = folder; } - public byte[] serialize(CChunk chunk) { + public byte[] serialize(CustomCropsRegion region) { ByteArrayOutputStream outByteStream = new ByteArrayOutputStream(); DataOutputStream outStream = new DataOutputStream(outByteStream); - SerializableChunk serializableChunk = toSerializableChunk(chunk); try { - outStream.writeByte(version); + outStream.writeByte(regionVersion); + outStream.writeInt(region.getRegionPos().x()); + outStream.writeInt(region.getRegionPos().z()); + Map map = region.getRegionDataToSave(); + outStream.writeInt(map.size()); + for (Map.Entry entry : map.entrySet()) { + outStream.writeInt(entry.getKey().x()); + outStream.writeInt(entry.getKey().z()); + byte[] dataArray = entry.getValue(); + outStream.writeInt(dataArray.length); + outStream.write(dataArray); + } + } catch (IOException e) { + e.printStackTrace(); + } + return outByteStream.toByteArray(); + } + + public CRegion deserializeRegion(CWorld world, DataInputStream dataStream) throws IOException { + int regionVersion = dataStream.readByte(); + int regionX = dataStream.readInt(); + int regionZ = dataStream.readInt(); + RegionPos regionPos = RegionPos.of(regionX, regionZ); + ConcurrentHashMap map = new ConcurrentHashMap<>(); + int chunkAmount = dataStream.readInt(); + for (int i = 0; i < chunkAmount; i++) { + int chunkX = dataStream.readInt(); + int chunkZ = dataStream.readInt(); + ChunkPos chunkPos = ChunkPos.of(chunkX, chunkZ); + byte[] chunkData = new byte[dataStream.readInt()]; + dataStream.read(chunkData); + map.put(chunkPos, chunkData); + } + return new CRegion(world, regionPos, map); + } + + public byte[] serialize(SerializableChunk serializableChunk) { + ByteArrayOutputStream outByteStream = new ByteArrayOutputStream(); + DataOutputStream outStream = new DataOutputStream(outByteStream); + try { + outStream.writeByte(chunkVersion); byte[] serializedSections = serializeChunk(serializableChunk); byte[] compressed = Zstd.compress(serializedSections); outStream.writeInt(compressed.length); @@ -219,8 +345,8 @@ public class BukkitWorldAdaptor extends AbstractWorldAdaptor { return outByteStream.toByteArray(); } - public CChunk deserialize(CWorld world, DataInputStream dataStream) throws IOException { - int worldVersion = dataStream.readByte(); + public CChunk deserializeChunk(CWorld world, DataInputStream dataStream) throws IOException { + int chunkVersion = dataStream.readByte(); byte[] blockData = readCompressedBytes(dataStream); return deserializeChunk(world, blockData); } @@ -231,7 +357,7 @@ public class BukkitWorldAdaptor extends AbstractWorldAdaptor { // read coordinate int x = chunkData.readInt(); int z = chunkData.readInt(); - ChunkCoordinate coordinate = new ChunkCoordinate(x, z); + ChunkPos coordinate = new ChunkPos(x, z); // read loading info int loadedSeconds = chunkData.readInt(); long lastLoadedTime = chunkData.readLong(); @@ -362,10 +488,10 @@ public class BukkitWorldAdaptor extends AbstractWorldAdaptor { } public SerializableChunk toSerializableChunk(CChunk chunk) { - ChunkCoordinate chunkCoordinate = chunk.getChunkCoordinate(); + ChunkPos chunkPos = chunk.getChunkPos(); return new SerializableChunk( - chunkCoordinate.x(), - chunkCoordinate.z(), + chunkPos.x(), + chunkPos.z(), chunk.getLoadedSeconds(), chunk.getLastLoadedTime(), Arrays.stream(chunk.getSectionsForSerialization()).map(this::toSerializableSection).toList(), @@ -451,7 +577,7 @@ public class BukkitWorldAdaptor extends AbstractWorldAdaptor { return outByteStream.toByteArray(); } - public void convertWorld(@Nullable CWorld cWorld, World world) { + public void convertWorldFromV33toV34(@Nullable CWorld cWorld, World world) { // handle legacy files File leagcyFile = new File(world.getWorldFolder(), "customcrops" + File.separator + "data.yml"); if (leagcyFile.exists()) { @@ -477,11 +603,14 @@ public class BukkitWorldAdaptor extends AbstractWorldAdaptor { LogUtils.warn("Converting chunks for world " + world.getName() + " from 3.3 to 3.4... This might take some time."); File[] data_files = folder.listFiles(); if (data_files == null) return; + + HashMap regionHashMap = new HashMap<>(); + for (File file : data_files) { - ChunkCoordinate chunkCoordinate = ChunkCoordinate.getByString(file.getName().substring(0, file.getName().length() - 7)); + ChunkPos chunkPos = ChunkPos.getByString(file.getName().substring(0, file.getName().length() - 7)); try (FileInputStream fis = new FileInputStream(file); ObjectInputStream ois = new ObjectInputStream(fis)) { CCChunk chunk = (CCChunk) ois.readObject(); - CChunk cChunk = new CChunk(cWorld, chunkCoordinate); + CChunk cChunk = new CChunk(cWorld, chunkPos); for (net.momirealms.customcrops.api.object.world.SimpleLocation legacyLocation : chunk.getGreenhouseSet()) { SimpleLocation simpleLocation = new SimpleLocation(legacyLocation.getWorldName(), legacyLocation.getX(), legacyLocation.getY(), legacyLocation.getZ()); cChunk.addGlassAt(new MemoryGlass(simpleLocation), simpleLocation); @@ -506,19 +635,56 @@ public class BukkitWorldAdaptor extends AbstractWorldAdaptor { Fertilizer fertilizer = entry.getValue().getFertilizer(); cChunk.addPotAt(new MemoryPot(simpleLocation, entry.getValue().getKey(), entry.getValue().getWater(), fertilizer == null ? "" : fertilizer.getKey(), fertilizer == null ? 0 : fertilizer.getTimes()), simpleLocation); } - try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(getChunkDataFilePath(world, cChunk.getChunkCoordinate())))) { - bos.write(serialize(cChunk)); - } catch (IOException e) { - LogUtils.severe("Failed to save CustomCrops data."); - e.printStackTrace(); + CustomCropsRegion region = regionHashMap.get(chunkPos.getRegionPos()); + if (region == null) { + region = new CRegion(cWorld, chunkPos.getRegionPos()); + regionHashMap.put(chunkPos.getRegionPos(), region); } + region.saveChunk(chunkPos, serialize(toSerializableChunk(cChunk))); } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); LogUtils.info("Error at " + file.getAbsolutePath()); } } - LogUtils.info("Successfully converted chunks for world: " + world); + for (CustomCropsRegion region : regionHashMap.values()) { + saveRegion(region); + } + LogUtils.info("Successfully converted chunks for world: " + world.getName()); } } + + public void convertWorldFromV342toV343(@Nullable CWorld cWorld, World world) { + File folder = new File(world.getWorldFolder(), "customcrops"); + if (!folder.exists()) return; + LogUtils.warn("Converting chunks for world " + world.getName() + " from 3.4.2 to 3.4.3... This might take some time."); + File[] data_files = folder.listFiles(); + if (data_files == null) return; + HashMap regionHashMap = new HashMap<>(); + for (File file : data_files) { + String fileName = file.getName(); + if (fileName.endsWith(".ccd")) { + String chunkStr = file.getName().substring(0, fileName.length()-4); + ChunkPos chunkPos = ChunkPos.getByString(chunkStr); + try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file))) { + DataInputStream dataStream = new DataInputStream(bis); + byte[] chunkData = dataStream.readAllBytes(); + dataStream.close(); + CustomCropsRegion region = regionHashMap.get(chunkPos.getRegionPos()); + if (region == null) { + region = new CRegion(cWorld, chunkPos.getRegionPos()); + regionHashMap.put(chunkPos.getRegionPos(), region); + } + region.saveChunk(chunkPos, chunkData); + } catch (IOException e) { + e.printStackTrace(); + } + file.delete(); + } + } + for (CustomCropsRegion region : regionHashMap.values()) { + saveRegion(region); + } + LogUtils.info("Successfully converted chunks for world: " + world.getName()); + } } 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 f1476bd..fd9390f 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 @@ -23,10 +23,11 @@ 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.ChunkCoordinate; import net.momirealms.customcrops.api.mechanic.world.BlockPos; +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.CustomCropsRegion; 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; @@ -65,13 +66,13 @@ public class SlimeWorldAdaptor extends BukkitWorldAdaptor { } @Override - public void unload(CustomCropsWorld customCropsWorld) { + public void saveInfoData(CustomCropsWorld customCropsWorld) { SlimeWorld slimeWorld = slimePlugin.getWorld(customCropsWorld.getWorldName()); if (slimeWorld == null) { - super.unload(customCropsWorld); + super.saveInfoData(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."); @@ -80,9 +81,16 @@ public class SlimeWorldAdaptor extends BukkitWorldAdaptor { 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 unload(CustomCropsWorld customCropsWorld) { + SlimeWorld slimeWorld = slimePlugin.getWorld(customCropsWorld.getWorldName()); + if (slimeWorld == null) { + super.unload(customCropsWorld); + return; } + customCropsWorld.save(); } @Override @@ -108,64 +116,97 @@ public class SlimeWorldAdaptor extends BukkitWorldAdaptor { } @Override - public void loadDynamicData(CustomCropsWorld customCropsWorld, ChunkCoordinate chunkCoordinate) { + public void loadChunkData(CustomCropsWorld customCropsWorld, ChunkPos chunkPos) { SlimeWorld slimeWorld = slimePlugin.getWorld(customCropsWorld.getWorldName()); if (slimeWorld == null) { - super.loadDynamicData(customCropsWorld, chunkCoordinate); + super.loadChunkData(customCropsWorld, chunkPos); return; } + long time1 = System.currentTimeMillis(); CWorld cWorld = (CWorld) customCropsWorld; // load lazy chunks firstly - CustomCropsChunk lazyChunk = customCropsWorld.removeLazyChunkAt(chunkCoordinate); + CustomCropsChunk lazyChunk = customCropsWorld.removeLazyChunkAt(chunkPos); if (lazyChunk != null) { CChunk cChunk = (CChunk) lazyChunk; cChunk.setUnloadedSeconds(0); cWorld.loadChunk(cChunk); + long time2 = System.currentTimeMillis(); + CustomCropsPlugin.get().debug("Took " + (time2-time1) + "ms to load chunk " + chunkPos + " from lazy chunks"); 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."); + LogUtils.warn("Failed to load data for " + chunkPos + " in world " + customCropsWorld.getWorldName() + " because slime world format is incorrect."); return; } - Tag chunkTag = optionalCompoundTag.get().getValue().get(chunkCoordinate.toString()); - if (chunkTag == null) return; + Tag chunkTag = optionalCompoundTag.get().getValue().get(chunkPos.getAsString()); + if (chunkTag == null) { + return; + } Optional chunkCompoundTag = chunkTag.getAsCompoundTag(); - if (chunkCompoundTag.isEmpty()) return; + if (chunkCompoundTag.isEmpty()) { + return; + } - // load chunk from local files - long time1 = System.currentTimeMillis(); + // load chunk from slime world cWorld.loadChunk(tagToChunk(cWorld, chunkCompoundTag.get())); long time2 = System.currentTimeMillis(); - CustomCropsPlugin.get().debug("Took " + (time2-time1) + "ms to load chunk " + chunkCoordinate); + CustomCropsPlugin.get().debug("Took " + (time2-time1) + "ms to load chunk " + chunkPos); } @Override - public void saveDynamicData(CustomCropsWorld customCropsWorld, CustomCropsChunk chunk) { - SlimeWorld slimeWorld = getSlimeWorld(customCropsWorld.getWorldName()); + public void saveChunkToCachedRegion(CustomCropsChunk customCropsChunk) { + CustomCropsWorld customCropsWorld = customCropsChunk.getCustomCropsWorld(); + SlimeWorld slimeWorld = getSlimeWorld(customCropsChunk.getCustomCropsWorld().getWorldName()); if (slimeWorld == null) { - super.saveDynamicData(customCropsWorld, chunk); + super.saveChunkToCachedRegion(customCropsChunk); 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."); + LogUtils.warn("Failed to save data for " + customCropsChunk.getChunkPos() + " in world " + customCropsWorld.getWorldName() + " because slime world format is incorrect."); return; } - CustomCropsPlugin.get().getScheduler().runTaskSync(() -> optionalCompoundTag.get().getValue().put(chunkToTag((CChunk) chunk)), null); + SerializableChunk serializableChunk = toSerializableChunk((CChunk) customCropsChunk); + if (Bukkit.isPrimaryThread()) { + if (serializableChunk.canPrune()) { + optionalCompoundTag.get().getValue().remove(customCropsChunk.getChunkPos().getAsString()); + } else { + optionalCompoundTag.get().getValue().put(chunkToTag(serializableChunk)); + } + } else { + CustomCropsPlugin.get().getScheduler().runTaskSync(() -> { + if (serializableChunk.canPrune()) { + optionalCompoundTag.get().getValue().remove(customCropsChunk.getChunkPos().getAsString()); + } else { + optionalCompoundTag.get().getValue().put(chunkToTag(serializableChunk)); + } + }, null); + } + } + + @Override + public void saveRegion(CustomCropsRegion customCropsRegion) { + CustomCropsWorld customCropsWorld = customCropsRegion.getCustomCropsWorld(); + SlimeWorld slimeWorld = getSlimeWorld(customCropsWorld.getWorldName()); + if (slimeWorld == null) { + super.saveRegion(customCropsRegion); + return; + } + + // don't need to save region to slime world } private SlimeWorld getSlimeWorld(String name) { return slimePlugin.getWorld(name); } - private CompoundTag chunkToTag(CChunk chunk) { - SerializableChunk serializableChunk = toSerializableChunk(chunk); + private CompoundTag chunkToTag(SerializableChunk serializableChunk) { CompoundMap map = new CompoundMap(); map.put(new IntTag("x", serializableChunk.getX())); map.put(new IntTag("z", serializableChunk.getZ())); @@ -178,7 +219,7 @@ public class SlimeWorldAdaptor extends BukkitWorldAdaptor { 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); + return new CompoundTag(serializableChunk.getX() + "," + serializableChunk.getZ(), map); } private CChunk tagToChunk(CWorld cWorld, CompoundTag tag) { @@ -186,7 +227,7 @@ public class SlimeWorldAdaptor extends BukkitWorldAdaptor { 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); + ChunkPos coordinate = new ChunkPos(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(); diff --git a/plugin/src/main/resources/config.yml b/plugin/src/main/resources/config.yml index 3c18fc2..5af91b8 100644 --- a/plugin/src/main/resources/config.yml +++ b/plugin/src/main/resources/config.yml @@ -1,5 +1,5 @@ # Don't change -config-version: '35' +config-version: '36' # Debug debug: false