diff --git a/build.gradle b/build.gradle index 7c4e0d1..90574cc 100644 --- a/build.gradle +++ b/build.gradle @@ -4,7 +4,7 @@ plugins { } group = 'net.momirealms' -version = '3.1.0-hotfix2' +version = '3.1.1' repositories { mavenCentral() diff --git a/src/main/java/net/momirealms/customcrops/api/object/basic/ConfigManager.java b/src/main/java/net/momirealms/customcrops/api/object/basic/ConfigManager.java index 368a77f..336d8e6 100644 --- a/src/main/java/net/momirealms/customcrops/api/object/basic/ConfigManager.java +++ b/src/main/java/net/momirealms/customcrops/api/object/basic/ConfigManager.java @@ -65,6 +65,7 @@ public class ConfigManager extends Function { public static int intervalWork; public static boolean disableMoistureMechanic; public static boolean preventTrampling; + public static boolean onlyInLoadedChunks; private final HashMap cropPerWorld; private final CustomCrops plugin; @@ -72,6 +73,8 @@ public class ConfigManager extends Function { public ConfigManager(CustomCrops plugin) { this.plugin = plugin; this.cropPerWorld = new HashMap<>(); + YamlConfiguration config = ConfigUtils.getConfig("config.yml"); + onlyInLoadedChunks = config.getBoolean("mechanics.only-work-in-loaded-chunks", false); } @Override diff --git a/src/main/java/net/momirealms/customcrops/api/object/condition/Weather.java b/src/main/java/net/momirealms/customcrops/api/object/condition/Weather.java new file mode 100644 index 0000000..e6f2851 --- /dev/null +++ b/src/main/java/net/momirealms/customcrops/api/object/condition/Weather.java @@ -0,0 +1,29 @@ +package net.momirealms.customcrops.api.object.condition; + +import net.momirealms.customcrops.api.object.world.SimpleLocation; +import org.bukkit.World; + +public class Weather implements Condition { + + private final String[] weathers; + + public Weather(String[] weathers) { + this.weathers = weathers; + } + + @Override + public boolean isMet(SimpleLocation simpleLocation) { + World world = simpleLocation.getBukkitWorld(); + if (world == null) return false; + String currentWeather; + if (world.isThundering()) currentWeather = "thunder"; + else if (world.isClearWeather()) currentWeather = "clear"; + else currentWeather = "rain"; + for (String weather : weathers) { + if (weather.equals(currentWeather)) { + return true; + } + } + return false; + } +} diff --git a/src/main/java/net/momirealms/customcrops/api/object/requirement/WeatherImpl.java b/src/main/java/net/momirealms/customcrops/api/object/requirement/WeatherImpl.java index 4daff49..d507d6d 100644 --- a/src/main/java/net/momirealms/customcrops/api/object/requirement/WeatherImpl.java +++ b/src/main/java/net/momirealms/customcrops/api/object/requirement/WeatherImpl.java @@ -24,9 +24,9 @@ import java.util.List; public class WeatherImpl extends AbstractRequirement implements Requirement { - private final List weathers; + private final String[] weathers; - public WeatherImpl(@Nullable String[] msg, List weathers) { + public WeatherImpl(@Nullable String[] msg, String[] weathers) { super(msg); this.weathers = weathers; } @@ -39,7 +39,7 @@ public class WeatherImpl extends AbstractRequirement implements Requirement { else if (world.isClearWeather()) currentWeather = "clear"; else currentWeather = "rain"; for (String weather : weathers) { - if (weather.equalsIgnoreCase(currentWeather)) { + if (weather.equals(currentWeather)) { return true; } } diff --git a/src/main/java/net/momirealms/customcrops/api/object/world/CCChunk.java b/src/main/java/net/momirealms/customcrops/api/object/world/CCChunk.java index 0aae527..b9d4d5d 100644 --- a/src/main/java/net/momirealms/customcrops/api/object/world/CCChunk.java +++ b/src/main/java/net/momirealms/customcrops/api/object/world/CCChunk.java @@ -180,22 +180,23 @@ public class CCChunk implements Serializable { public void scheduleGrowTask(CCWorld ccWorld) { Random randomGenerator = ThreadLocalRandom.current(); + int delay = ConfigManager.pointGainInterval * 1000; for (SimpleLocation simpleLocation : growingCropMap.keySet()) { - ccWorld.pushCropTask(simpleLocation, randomGenerator.nextInt(ConfigManager.pointGainInterval)); + ccWorld.pushCropTask(simpleLocation, randomGenerator.nextInt(delay)); } } - public void scheduleSprinklerTask(CCWorld ccWorld) { + public void scheduleSprinklerTask(CCWorld ccWorld, int startDelay) { Random randomGenerator = ThreadLocalRandom.current(); - int delay = Math.min(30, ConfigManager.pointGainInterval); + int delay = (Math.min(30, ConfigManager.pointGainInterval) + startDelay) * 1000; for (SimpleLocation simpleLocation : sprinklerMap.keySet()) { ccWorld.pushSprinklerTask(simpleLocation, randomGenerator.nextInt(delay)); } } - public void scheduleConsumeTask(CCWorld ccWorld) { + public void scheduleConsumeTask(CCWorld ccWorld, int startDelay) { Random randomGenerator = ThreadLocalRandom.current(); - int delay = Math.min(30, ConfigManager.pointGainInterval); + int delay = (Math.min(30, ConfigManager.pointGainInterval) + startDelay) * 1000; for (SimpleLocation simpleLocation : potMap.keySet()) { ccWorld.pushConsumeTask(simpleLocation, randomGenerator.nextInt(delay)); } diff --git a/src/main/java/net/momirealms/customcrops/api/object/world/CCWorld.java b/src/main/java/net/momirealms/customcrops/api/object/world/CCWorld.java index 95a03c4..eef89d8 100644 --- a/src/main/java/net/momirealms/customcrops/api/object/world/CCWorld.java +++ b/src/main/java/net/momirealms/customcrops/api/object/world/CCWorld.java @@ -48,7 +48,6 @@ import org.bukkit.block.Block; import org.bukkit.block.data.type.Farmland; import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.entity.Player; -import org.bukkit.event.block.*; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -74,6 +73,7 @@ public class CCWorld extends Function { private int workCounter; private int consumeCounter; private final HashSet plantToday; + private File chunks_folder; public CCWorld(World world) { this.world = new WeakReference<>(world); @@ -90,11 +90,25 @@ public class CCWorld extends Function { @Override @SuppressWarnings("ResultOfMethodCallIgnored") public void init() { - File chunks_folder = ConfigUtils.getChunkFolder(worldName); + loadDateData(); + if (!ConfigManager.onlyInLoadedChunks) { + loadAllChunkData(); + } + } + + @Override + public void disable() { + closePool(); + saveDateData(); + saveAllChunkData(); + CustomCrops.getInstance().getSeasonManager().unloadSeasonData(worldName); + } + + public void loadAllChunkData() { + chunks_folder = ConfigUtils.getChunkFolder(worldName); if (!chunks_folder.exists()) chunks_folder.mkdirs(); File[] data_files = chunks_folder.listFiles(); if (data_files == null) return; - List outdated = new ArrayList<>(); for (File file : data_files) { ChunkCoordinate chunkCoordinate = ChunkCoordinate.getByString(file.getName().substring(0, file.getName().length() - 7)); @@ -111,11 +125,12 @@ public class CCWorld extends Function { outdated.add(file); } } - for (File file : outdated) { file.delete(); } + } + public void loadDateData() { YamlConfiguration dataFile; if (ConfigManager.worldFolderPath.equals("")) { dataFile = ConfigUtils.readData(new File(CustomCrops.getInstance().getDataFolder().getParentFile().getParentFile(), worldName + File.separator + "customcrops" + File.separator + "data.yml")); @@ -134,18 +149,8 @@ public class CCWorld extends Function { this.current_day = dataFile.getLong("day", 0); } - @Override - public void disable() { - closePool(); - saveCrop(); - saveDate(); - CustomCrops.getInstance().getSeasonManager().unloadSeasonData(worldName); - } - @SuppressWarnings("ResultOfMethodCallIgnored") - public void saveCrop() { - File chunks_folder = ConfigUtils.getChunkFolder(worldName); - if (!chunks_folder.exists()) chunks_folder.mkdirs(); + public void saveAllChunkData() { for (Map.Entry entry : chunkMap.entrySet()) { ChunkCoordinate chunkCoordinate = entry.getKey(); CCChunk chunk = entry.getValue(); @@ -163,7 +168,7 @@ public class CCWorld extends Function { } } - public void saveDate() { + public void saveDateData() { YamlConfiguration dataFile = new YamlConfiguration(); if (ConfigManager.enableSeason && !ConfigManager.rsHook) { SeasonData seasonData = CustomCrops.getInstance().getSeasonManager().getSeasonData(worldName); @@ -191,28 +196,29 @@ public class CCWorld extends Function { this.scheduleTask(); } + public void unload() { + if (this.timerTask != null) { + this.timerTask.cancel(false); + this.timerTask = null; + } + } + private void scheduleTask() { if (this.timerTask == null) { this.timerTask = CustomCrops.getInstance().getScheduler().runTaskTimerAsync(() -> { - World current = world.get(); if (current != null) { - if (ConfigManager.debug) { Log.info("Queue size: " + schedule.getQueue().size() + " Completed: " + schedule.getCompletedTaskCount()); } - long day = current.getFullTime() / 24000; long time = current.getTime(); - this.tryDayCycleTask(time, day); - this.tryScheduleGrow(); + this.timerTask(); } else { - AdventureUtils.consoleMessage("[CustomCrops] World: " + worldName + " unloaded unexpectedly. Shutdown the schedule."); this.schedule.shutdown(); - } }, 1000, 1000L); } @@ -230,13 +236,13 @@ public class CCWorld extends Function { if (cacheTimer <= 0) { if (ConfigManager.debug) Log.info("== Save cache =="); cacheTimer = ConfigManager.cacheSaveInterval; - schedule.execute(this::saveDate); - schedule.execute(this::saveCrop); + schedule.execute(this::saveDateData); + schedule.execute(this::saveAllChunkData); } } } - private void tryScheduleGrow() { + private void timerTask() { pointTimer--; if (pointTimer <= 0) { pointTimer = ConfigManager.pointGainInterval; @@ -252,7 +258,6 @@ public class CCWorld extends Function { schedule.getQueue().clear(); if (ConfigManager.debug) Log.info("== Clear queue =="); } - for (CCChunk chunk : chunkMap.values()) { chunk.scheduleGrowTask(this); } @@ -262,37 +267,70 @@ public class CCWorld extends Function { if (consumeCounter <= 0) { consumeCounter = ConfigManager.intervalConsume; if (ConfigManager.debug) Log.info("== Consume time =="); - scheduleConsumeTask(); + scheduleConsumeTask(0); } if (workCounter <= 0) { workCounter = ConfigManager.intervalWork; if (ConfigManager.debug) Log.info("== Work time =="); - scheduleSprinklerWork(); + scheduleSprinklerWork(Math.min(30, ConfigManager.pointGainInterval)); } } } - public void unload() { - if (this.timerTask != null) { - this.timerTask.cancel(false); - this.timerTask = null; - } - } - private void closePool() { this.schedule.shutdown(); } + @SuppressWarnings("ResultOfMethodCallIgnored") + public void unloadChunk(ChunkCoordinate chunkCoordinate) { + CCChunk chunk = chunkMap.remove(chunkCoordinate); + if (chunk != null) { + File file = new File(chunks_folder, chunkCoordinate.getFileName() + ".ccdata"); + if (chunk.isUseless() && file.exists()) { + file.delete(); + return; + } + try (FileOutputStream fos = new FileOutputStream(file); ObjectOutputStream oos = new ObjectOutputStream(fos)) { + oos.writeObject(chunk); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + @SuppressWarnings("ResultOfMethodCallIgnored") + public void loadChunk(ChunkCoordinate chunkCoordinate) { + File file = new File(chunks_folder, chunkCoordinate.getFileName() + ".ccdata"); + if (file.exists()) { + boolean delete = false; + try (FileInputStream fis = new FileInputStream(file); ObjectInputStream ois = new ObjectInputStream(fis)) { + CCChunk chunk = (CCChunk) ois.readObject(); + if (chunk.isUseless()) { + delete = true; + } else { + chunkMap.put(chunkCoordinate, chunk); + } + } catch (IOException | ClassNotFoundException e) { + e.printStackTrace(); + Log.info("Error at " + file.getAbsolutePath()); + } finally { + if (delete) { + file.delete(); + } + } + } + } + public void pushCropTask(SimpleLocation simpleLocation, int delay) { - schedule.schedule(new CropCheckTask(simpleLocation), delay, TimeUnit.SECONDS); + schedule.schedule(new CropCheckTask(simpleLocation), delay, TimeUnit.MILLISECONDS); } public void pushSprinklerTask(SimpleLocation simpleLocation, int delay) { - schedule.schedule(new SprinklerCheckTask(simpleLocation), delay, TimeUnit.SECONDS); + schedule.schedule(new SprinklerCheckTask(simpleLocation), delay, TimeUnit.MILLISECONDS); } public void pushConsumeTask(SimpleLocation simpleLocation, int delay) { - schedule.schedule(new ConsumeCheckTask(simpleLocation), delay, TimeUnit.SECONDS); + schedule.schedule(new ConsumeCheckTask(simpleLocation), delay, TimeUnit.MILLISECONDS); } public class ConsumeCheckTask implements Runnable { @@ -487,22 +525,22 @@ public class CCWorld extends Function { Location location = simpleLocation.getBukkitLocation(); String finalNextModel = nextModel; if (finalNextModel == null || location == null) return; + CompletableFuture asyncGetChunk = location.getWorld().getChunkAtAsync(location.getBlockX() >> 4, location.getBlockZ() >> 4); if (itemMode == ItemMode.ITEM_FRAME || itemMode == ItemMode.ITEM_DISPLAY) { CompletableFuture loadEntities = asyncGetChunk.thenApply((chunk) -> { chunk.getEntities(); return chunk.isEntitiesLoaded(); }); - loadEntities.whenComplete((result, throwable) -> { - CustomCrops.getInstance().getScheduler().callSyncMethod(() -> { - if (CustomCrops.getInstance().getPlatformInterface().removeCustomItem(location, itemMode)) { - CustomCrops.getInstance().getPlatformInterface().placeCustomItem(location, finalNextModel, itemMode); - } else { - removeCropData(simpleLocation); - } - return null; - }); - }); + loadEntities.whenComplete((result, throwable) -> + CustomCrops.getInstance().getScheduler().callSyncMethod(() -> { + if (CustomCrops.getInstance().getPlatformInterface().removeCustomItem(location, itemMode)) { + CustomCrops.getInstance().getPlatformInterface().placeCustomItem(location, finalNextModel, itemMode); + } else { + removeCropData(simpleLocation); + } + return null; + })); } else { asyncGetChunk.whenComplete((result, throwable) -> @@ -517,10 +555,6 @@ public class CCWorld extends Function { } } - public String getWorldName() { - return worldName; - } - public World getWorld() { return world.get(); } @@ -553,7 +587,7 @@ public class CCWorld extends Function { if (plantToday.contains(simpleLocation)) { return; } - pushCropTask(simpleLocation, ThreadLocalRandom.current().nextInt(ConfigManager.pointGainInterval)); + pushCropTask(simpleLocation, ThreadLocalRandom.current().nextInt(ConfigManager.pointGainInterval * 1000)); plantToday.add(simpleLocation); } @@ -680,19 +714,19 @@ public class CCWorld extends Function { return newChunk; } - public void scheduleSprinklerWork() { + public void scheduleSprinklerWork(int delay) { schedule.execute(() -> { for (CCChunk chunk : chunkMap.values()) { - chunk.scheduleSprinklerTask(this); + chunk.scheduleSprinklerTask(this, delay); } }); } - public void scheduleConsumeTask() { - schedule.schedule(() -> { + public void scheduleConsumeTask(int delay) { + schedule.execute(() -> { for (CCChunk chunk : chunkMap.values()) { - chunk.scheduleConsumeTask(this); + chunk.scheduleConsumeTask(this, delay); } - }, 0, TimeUnit.SECONDS); + }); } } \ No newline at end of file diff --git a/src/main/java/net/momirealms/customcrops/api/object/world/WorldDataManager.java b/src/main/java/net/momirealms/customcrops/api/object/world/WorldDataManager.java index 5601915..feeebc3 100644 --- a/src/main/java/net/momirealms/customcrops/api/object/world/WorldDataManager.java +++ b/src/main/java/net/momirealms/customcrops/api/object/world/WorldDataManager.java @@ -26,6 +26,7 @@ import net.momirealms.customcrops.api.object.pot.Pot; import net.momirealms.customcrops.api.object.sprinkler.Sprinkler; import net.momirealms.customcrops.api.object.sprinkler.SprinklerConfig; import org.bukkit.Bukkit; +import org.bukkit.Chunk; import org.bukkit.World; import org.bukkit.event.HandlerList; import org.jetbrains.annotations.Nullable; @@ -247,4 +248,18 @@ public class WorldDataManager extends Function { public CCWorld getWorld(String world) { return worldMap.get(world); } + + public void loadChunk(Chunk chunk, World world) { + CCWorld ccWorld = worldMap.get(world.getName()); + if (ccWorld != null) { + ccWorld.loadChunk(new ChunkCoordinate(chunk.getX(), chunk.getZ())); + } + } + + public void unloadChunk(Chunk chunk, World world) { + CCWorld ccWorld = worldMap.get(world.getName()); + if (ccWorld != null) { + ccWorld.unloadChunk(new ChunkCoordinate(chunk.getX(), chunk.getZ())); + } + } } diff --git a/src/main/java/net/momirealms/customcrops/api/object/world/WorldListener.java b/src/main/java/net/momirealms/customcrops/api/object/world/WorldListener.java index aea2602..388fee7 100644 --- a/src/main/java/net/momirealms/customcrops/api/object/world/WorldListener.java +++ b/src/main/java/net/momirealms/customcrops/api/object/world/WorldListener.java @@ -17,8 +17,11 @@ package net.momirealms.customcrops.api.object.world; +import net.momirealms.customcrops.api.object.basic.ConfigManager; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; +import org.bukkit.event.world.ChunkLoadEvent; +import org.bukkit.event.world.ChunkUnloadEvent; import org.bukkit.event.world.WorldLoadEvent; import org.bukkit.event.world.WorldUnloadEvent; @@ -31,12 +34,24 @@ public class WorldListener implements Listener { } @EventHandler - public void onChunkLoad(WorldLoadEvent event) { + public void onWorldLoad(WorldLoadEvent event) { worldManager.loadWorld(event.getWorld()); } @EventHandler - public void onChunkUnload(WorldUnloadEvent event) { + public void onWorldUnload(WorldUnloadEvent event) { worldManager.unloadWorld(event.getWorld()); } + + @EventHandler + public void onChunkLoad(ChunkLoadEvent event) { + if (!ConfigManager.onlyInLoadedChunks || event.isNewChunk()) return; + worldManager.loadChunk(event.getChunk(), event.getWorld()); + } + + @EventHandler + public void onChunkUnload(ChunkUnloadEvent event) { + if (!ConfigManager.onlyInLoadedChunks) return; + worldManager.unloadChunk(event.getChunk(), event.getWorld()); + } } diff --git a/src/main/java/net/momirealms/customcrops/api/util/ConfigUtils.java b/src/main/java/net/momirealms/customcrops/api/util/ConfigUtils.java index b10aa4e..8004f30 100644 --- a/src/main/java/net/momirealms/customcrops/api/util/ConfigUtils.java +++ b/src/main/java/net/momirealms/customcrops/api/util/ConfigUtils.java @@ -186,6 +186,7 @@ public class ConfigUtils { } case "crow_attack" -> conditions.add(new CrowAttack(map2.getDouble("value.chance"), map2.getString("value.fly-model"), map2.getString("value.stand-model"))); case "random" -> conditions.add(new Random(map2.getDouble("value"))); + case "weather" -> conditions.add(new Weather(map2.getStringList("value").toArray(new String[0]))); } } } @@ -205,7 +206,7 @@ public class ConfigUtils { String[] msg = innerSec.getStringList("message").size() == 0 ? (innerSec.getString("message") == null ? null : new String[]{innerSec.getString("message")}) : innerSec.getStringList("message").toArray(new String[0]); switch (type) { case "biome" -> requirements.add(new BiomeImpl(msg, new HashSet<>(innerSec.getStringList("value")))); - case "weather" -> requirements.add(new WeatherImpl(msg, innerSec.getStringList("value"))); + case "weather" -> requirements.add(new WeatherImpl(msg, innerSec.getStringList("value").toArray(new String[0]))); case "ypos" -> requirements.add(new YPosImpl(msg, innerSec.getStringList("value"))); case "season" -> { if (!ConfigManager.enableSeason) continue; diff --git a/src/main/java/net/momirealms/customcrops/command/subcmd/force/ConsumeTaskCommand.java b/src/main/java/net/momirealms/customcrops/command/subcmd/force/ConsumeTaskCommand.java index 7064ce6..d948626 100644 --- a/src/main/java/net/momirealms/customcrops/command/subcmd/force/ConsumeTaskCommand.java +++ b/src/main/java/net/momirealms/customcrops/command/subcmd/force/ConsumeTaskCommand.java @@ -50,7 +50,7 @@ public class ConsumeTaskCommand extends AbstractSubCommand { CustomCrops.getInstance().getScheduler().runTaskAsync(() -> { CCWorld ccworld = CustomCrops.getInstance().getWorldDataManager().getWorld(args.get(0)); if (ccworld != null) { - ccworld.scheduleConsumeTask(); + ccworld.scheduleConsumeTask(0); } }); return true; diff --git a/src/main/java/net/momirealms/customcrops/command/subcmd/force/SprinklerWorkCommand.java b/src/main/java/net/momirealms/customcrops/command/subcmd/force/SprinklerWorkCommand.java index c55233d..813fc46 100644 --- a/src/main/java/net/momirealms/customcrops/command/subcmd/force/SprinklerWorkCommand.java +++ b/src/main/java/net/momirealms/customcrops/command/subcmd/force/SprinklerWorkCommand.java @@ -50,7 +50,7 @@ public class SprinklerWorkCommand extends AbstractSubCommand { CustomCrops.getInstance().getScheduler().runTaskAsync(() -> { CCWorld ccworld = CustomCrops.getInstance().getWorldDataManager().getWorld(args.get(0)); if (ccworld != null) { - ccworld.scheduleSprinklerWork(); + ccworld.scheduleSprinklerWork(0); } }); return true; diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 2379fe0..c522459 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -1,5 +1,5 @@ # Don't change -config-version: '29' +config-version: '30' # BStats metrics: true # Language: english / spanish / chinese / turkish @@ -68,6 +68,10 @@ optimization: - world:64 mechanics: + # Does the system only work in loaded chunks (Requires you to stop the server before changing this setting) + # 插件是否只在加载中的区块工作 (需要关闭服务器再设置此项) + only-work-in-loaded-chunks: false + # 17/2/1 = 85%/10%/5% # 2/2/1 = 40%/40%/20% default-quality-ratio: 17/2/1