From 2d1a9a4fec9f5da464771fc9f1148d554d06f142 Mon Sep 17 00:00:00 2001 From: xGinko Date: Sun, 31 Mar 2024 17:16:15 +0200 Subject: [PATCH] new system for handling different plugin data --- .../villageroptimizer/VillagerCache.java | 1 + .../villageroptimizer/WrappedVillager.java | 325 ------------------ .../optimizevillagers/OptVillagersRadius.java | 2 +- .../UnOptVillagersRadius.java | 2 +- .../villageroptimizer/enums/Keyring.java | 6 +- .../events/VillagerOptimizeEvent.java | 2 +- .../events/VillagerUnoptimizeEvent.java | 2 +- .../gameplay/FixOptimisationAfterCure.java | 2 +- .../gameplay/LevelOptimizedProfession.java | 2 +- .../gameplay/RestockOptimizedTrades.java | 2 +- .../gameplay/UnoptimizeOnJobLoose.java | 2 +- .../modules/optimization/OptimizeByBlock.java | 2 +- .../optimization/OptimizeByNametag.java | 2 +- .../optimization/OptimizeByWorkstation.java | 2 +- .../wrapper/AVLVillagerDataHandlerImpl.java | 144 ++++++++ .../wrapper/MainVillagerDataHandlerImpl.java | 142 ++++++++ .../wrapper/VillagerDataHandler.java | 105 ++++++ .../wrapper/WrappedVillager.java | 188 ++++++++++ 18 files changed, 594 insertions(+), 339 deletions(-) delete mode 100644 src/main/java/me/xginko/villageroptimizer/WrappedVillager.java create mode 100644 src/main/java/me/xginko/villageroptimizer/wrapper/AVLVillagerDataHandlerImpl.java create mode 100644 src/main/java/me/xginko/villageroptimizer/wrapper/MainVillagerDataHandlerImpl.java create mode 100644 src/main/java/me/xginko/villageroptimizer/wrapper/VillagerDataHandler.java create mode 100644 src/main/java/me/xginko/villageroptimizer/wrapper/WrappedVillager.java diff --git a/src/main/java/me/xginko/villageroptimizer/VillagerCache.java b/src/main/java/me/xginko/villageroptimizer/VillagerCache.java index 1a11e20..41b0525 100644 --- a/src/main/java/me/xginko/villageroptimizer/VillagerCache.java +++ b/src/main/java/me/xginko/villageroptimizer/VillagerCache.java @@ -2,6 +2,7 @@ package me.xginko.villageroptimizer; import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; +import me.xginko.villageroptimizer.wrapper.WrappedVillager; import org.bukkit.entity.Villager; import org.jetbrains.annotations.NotNull; diff --git a/src/main/java/me/xginko/villageroptimizer/WrappedVillager.java b/src/main/java/me/xginko/villageroptimizer/WrappedVillager.java deleted file mode 100644 index f92d288..0000000 --- a/src/main/java/me/xginko/villageroptimizer/WrappedVillager.java +++ /dev/null @@ -1,325 +0,0 @@ -package me.xginko.villageroptimizer; - -import me.xginko.villageroptimizer.enums.Keyring; -import me.xginko.villageroptimizer.enums.OptimizationType; -import org.bukkit.Location; -import org.bukkit.Particle; -import org.bukkit.Sound; -import org.bukkit.entity.Villager; -import org.bukkit.entity.memory.MemoryKey; -import org.bukkit.inventory.MerchantRecipe; -import org.bukkit.persistence.PersistentDataContainer; -import org.bukkit.persistence.PersistentDataType; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.concurrent.TimeUnit; - -@SuppressWarnings("ALL") -public final class WrappedVillager { - - private final @NotNull Villager villager; - private final @NotNull PersistentDataContainer dataContainer; - private final boolean parseOther; - - WrappedVillager(@NotNull Villager villager) { - this.villager = villager; - this.dataContainer = villager.getPersistentDataContainer(); - this.parseOther = VillagerOptimizer.getConfiguration().support_other_plugins; - } - - /** - * @return The villager inside the wrapper. - */ - public @NotNull Villager villager() { - return villager; - } - - /** - * @return The data container inside the wrapper. - */ - public @NotNull PersistentDataContainer dataContainer() { - return dataContainer; - } - - /** - * @return True if the villager is optimized by either this plugin or a supported alternative, otherwise false. - */ - public boolean isOptimized() { - if (!parseOther) { - return isOptimized(Keyring.Spaces.VillagerOptimizer); - } - for (Keyring.Spaces pluginNamespaces : Keyring.Spaces.values()) { - if (isOptimized(pluginNamespaces)) return true; - } - return false; - } - - /** - * @return True if the villager is optimized by the supported plugin, otherwise false. - */ - public boolean isOptimized(Keyring.Spaces namespace) { - if (namespace == Keyring.Spaces.VillagerOptimizer) { - return dataContainer.has(Keyring.VillagerOptimizer.OPTIMIZATION_TYPE.getKey(), PersistentDataType.STRING); - } else { - return dataContainer.has(Keyring.AntiVillagerLag.OPTIMIZED_ANY.getKey(), PersistentDataType.STRING) - || dataContainer.has(Keyring.AntiVillagerLag.OPTIMIZED_WORKSTATION.getKey(), PersistentDataType.STRING) - || dataContainer.has(Keyring.AntiVillagerLag.OPTIMIZED_BLOCK.getKey(), PersistentDataType.STRING); - } - } - - /** - * @param cooldown_millis The configured cooldown in millis until the next optimization is allowed to occur. - * @return True if villager can be optimized again, otherwise false. - */ - public boolean canOptimize(final long cooldown_millis) { - if (parseOther) { - if ( - dataContainer.has(Keyring.AntiVillagerLag.NEXT_OPTIMIZATION_SYSTIME_SECONDS.getKey(), PersistentDataType.LONG) - && System.currentTimeMillis() <= 1000 * dataContainer.get(Keyring.AntiVillagerLag.NEXT_OPTIMIZATION_SYSTIME_SECONDS.getKey(), PersistentDataType.LONG) - ) { - return false; - } - } - return System.currentTimeMillis() > getLastOptimize() + cooldown_millis; - } - - /** - * @param type OptimizationType the villager should be set to. - */ - public void setOptimizationType(final OptimizationType type) { - VillagerOptimizer.getFoliaLib().getImpl().runAtEntityTimer(villager, setOptimization -> { - // Keep repeating task until villager is no longer trading with a player - if (villager.isTrading()) return; - - if (type == OptimizationType.NONE) { - if (isOptimized(Keyring.Spaces.VillagerOptimizer)) { - dataContainer.remove(Keyring.VillagerOptimizer.OPTIMIZATION_TYPE.getKey()); - } - if (parseOther) { - if (dataContainer.has(Keyring.AntiVillagerLag.OPTIMIZED_ANY.getKey(), PersistentDataType.STRING)) - dataContainer.remove(Keyring.AntiVillagerLag.OPTIMIZED_ANY.getKey()); - if (dataContainer.has(Keyring.AntiVillagerLag.OPTIMIZED_WORKSTATION.getKey(), PersistentDataType.STRING)) - dataContainer.remove(Keyring.AntiVillagerLag.OPTIMIZED_WORKSTATION.getKey()); - if (dataContainer.has(Keyring.AntiVillagerLag.OPTIMIZED_BLOCK.getKey(), PersistentDataType.STRING)) - dataContainer.remove(Keyring.AntiVillagerLag.OPTIMIZED_BLOCK.getKey()); - } - villager.setAware(true); - villager.setAI(true); - } else { - dataContainer.set(Keyring.VillagerOptimizer.OPTIMIZATION_TYPE.getKey(), PersistentDataType.STRING, type.name()); - villager.setAware(false); - } - - // End repeating task once logic is finished - setOptimization.cancel(); - }, 0L, 1L, TimeUnit.SECONDS); - } - - /** - * @return The current OptimizationType of the villager. - */ - public @NotNull OptimizationType getOptimizationType() { - if (!parseOther) { - return getOptimizationType(Keyring.Spaces.VillagerOptimizer); - } - OptimizationType optimizationType = getOptimizationType(Keyring.Spaces.VillagerOptimizer); - if (optimizationType != OptimizationType.NONE) { - return optimizationType; - } - return getOptimizationType(Keyring.Spaces.AntiVillagerLag); - } - - public @NotNull OptimizationType getOptimizationType(Keyring.Spaces namespaces) { - if (namespaces == Keyring.Spaces.VillagerOptimizer) { - if (!isOptimized(Keyring.Spaces.VillagerOptimizer)) { - return OptimizationType.valueOf(dataContainer.get(Keyring.VillagerOptimizer.OPTIMIZATION_TYPE.getKey(), PersistentDataType.STRING)); - } else { - return OptimizationType.NONE; - } - } - if (namespaces == Keyring.Spaces.AntiVillagerLag) { - if (dataContainer.has(Keyring.AntiVillagerLag.OPTIMIZED_BLOCK.getKey(), PersistentDataType.STRING)) { - return OptimizationType.BLOCK; - } - if (dataContainer.has(Keyring.AntiVillagerLag.OPTIMIZED_WORKSTATION.getKey(), PersistentDataType.STRING)) { - return OptimizationType.WORKSTATION; - } - if (dataContainer.has(Keyring.AntiVillagerLag.OPTIMIZED_ANY.getKey(), PersistentDataType.STRING)) { - return OptimizationType.COMMAND; // Best we can do - } - return OptimizationType.NONE; - } - return OptimizationType.NONE; - } - - /** - * Saves the system time in millis when the villager was last optimized. - */ - public void saveOptimizeTime() { - dataContainer.set(Keyring.VillagerOptimizer.LAST_OPTIMIZE.getKey(), PersistentDataType.LONG, System.currentTimeMillis()); - } - - /** - * @return The system time in millis when the villager was last optimized, 0L if the villager was never optimized. - */ - public long getLastOptimize() { - if (dataContainer.has(Keyring.VillagerOptimizer.LAST_OPTIMIZE.getKey(), PersistentDataType.LONG)) { - return dataContainer.get(Keyring.VillagerOptimizer.LAST_OPTIMIZE.getKey(), PersistentDataType.LONG); - } - return 0L; - } - - /** - * Here for convenience so the remaining millis since the last stored optimize time - * can be easily calculated. - * This enables new configured cooldowns to instantly apply instead of them being persistent. - * - * @param cooldown_millis The configured cooldown in milliseconds you want to check against. - * @return The time left in millis until the villager can be optimized again. - */ - public long getOptimizeCooldownMillis(final long cooldown_millis) { - long remainingMillis = 0L; - - if (parseOther) { - if (dataContainer.has(Keyring.AntiVillagerLag.NEXT_OPTIMIZATION_SYSTIME_SECONDS.getKey(), PersistentDataType.LONG)) { - remainingMillis = System.currentTimeMillis() - dataContainer.get(Keyring.AntiVillagerLag.NEXT_OPTIMIZATION_SYSTIME_SECONDS.getKey(), PersistentDataType.LONG); - } - } - - if (remainingMillis > 0) return remainingMillis; - - if (dataContainer.has(Keyring.VillagerOptimizer.LAST_OPTIMIZE.getKey(), PersistentDataType.LONG)) { - return System.currentTimeMillis() - (dataContainer.get(Keyring.VillagerOptimizer.LAST_OPTIMIZE.getKey(), PersistentDataType.LONG) + cooldown_millis); - } - - return cooldown_millis; - } - - /** - * Here for convenience so the remaining millis since the last stored restock time - * can be easily calculated. - * - * @param cooldown_millis The configured cooldown in milliseconds you want to check against. - * @return True if the villager has been loaded long enough. - */ - public boolean canRestock(final long cooldown_millis) { - return getLastRestock() + cooldown_millis <= System.currentTimeMillis(); - } - - /** - * Restock all trading recipes. - */ - public void restock() { - for (MerchantRecipe recipe : villager.getRecipes()) { - recipe.setUses(0); - } - } - - /** - * Saves the time of the in-game world when the entity was last restocked. - */ - public void saveRestockTime() { - dataContainer.set(Keyring.VillagerOptimizer.LAST_RESTOCK.getKey(), PersistentDataType.LONG, System.currentTimeMillis()); - } - - /** - * @return The time of the in-game world when the entity was last restocked. - */ - public long getLastRestock() { - long lastRestock = 0L; - if (dataContainer.has(Keyring.VillagerOptimizer.LAST_RESTOCK.getKey(), PersistentDataType.LONG)) { - lastRestock = dataContainer.get(Keyring.VillagerOptimizer.LAST_RESTOCK.getKey(), PersistentDataType.LONG); - } - if (parseOther) { - if (dataContainer.has(Keyring.AntiVillagerLag.LAST_RESTOCK_WORLDFULLTIME.getKey(), PersistentDataType.LONG)) { - long lastAVLRestock = dataContainer.get(Keyring.AntiVillagerLag.LAST_RESTOCK_WORLDFULLTIME.getKey(), PersistentDataType.LONG); - if (lastRestock < lastAVLRestock) { - lastRestock = lastAVLRestock; - } - } - } - return lastRestock; - } - - public long getRestockCooldownMillis(final long cooldown_millis) { - if (dataContainer.has(Keyring.VillagerOptimizer.LAST_RESTOCK.getKey(), PersistentDataType.LONG)) - return System.currentTimeMillis() - (dataContainer.get(Keyring.VillagerOptimizer.LAST_RESTOCK.getKey(), PersistentDataType.LONG) + cooldown_millis); - return cooldown_millis; - } - - /** - * @return The level between 1-5 calculated from the villagers experience. - */ - public int calculateLevel() { - // https://minecraft.fandom.com/wiki/Trading#Mechanics - int vilEXP = villager.getVillagerExperience(); - if (vilEXP >= 250) return 5; - if (vilEXP >= 150) return 4; - if (vilEXP >= 70) return 3; - if (vilEXP >= 10) return 2; - return 1; - } - - /** - * @return true if the villager can loose his acquired profession by having their workstation destroyed. - */ - public boolean canLooseProfession() { - // A villager with a level of 1 and no trading experience is liable to lose its profession. - return villager.getVillagerLevel() <= 1 && villager.getVillagerExperience() <= 0; - } - - /** - * @param cooldown_millis The configured cooldown in milliseconds you want to check against. - * @return Whether the villager can be leveled up or not with the checked milliseconds - */ - public boolean canLevelUp(final long cooldown_millis) { - if (System.currentTimeMillis() < getLastLevelUpTime() + cooldown_millis) { - return false; - } - - if (parseOther) { - return !dataContainer.has(Keyring.AntiVillagerLag.NEXT_LEVELUP_SYSTIME_SECONDS.getKey(), PersistentDataType.LONG) - || System.currentTimeMillis() > dataContainer.get(Keyring.AntiVillagerLag.NEXT_LEVELUP_SYSTIME_SECONDS.getKey(), PersistentDataType.LONG) * 1000; - } - - return true; - } - - /** - * Saves the time of the in-game world when the entity was last leveled up. - */ - public void saveLastLevelUp() { - dataContainer.set(Keyring.VillagerOptimizer.LAST_LEVELUP.getKey(), PersistentDataType.LONG, System.currentTimeMillis()); - } - - /** - * Here for convenience so the remaining millis since the last stored level-up time - * can be easily calculated. - * - * @return The time of the in-game world when the entity was last leveled up. - */ - public long getLastLevelUpTime() { - if (dataContainer.has(Keyring.VillagerOptimizer.LAST_LEVELUP.getKey(), PersistentDataType.LONG)) - return dataContainer.get(Keyring.VillagerOptimizer.LAST_LEVELUP.getKey(), PersistentDataType.LONG); - return 0L; - } - - public long getLevelCooldownMillis(final long cooldown_millis) { - if (dataContainer.has(Keyring.VillagerOptimizer.LAST_LEVELUP.getKey(), PersistentDataType.LONG)) - return System.currentTimeMillis() - (dataContainer.get(Keyring.VillagerOptimizer.LAST_LEVELUP.getKey(), PersistentDataType.LONG) + cooldown_millis); - return cooldown_millis; - } - - public void sayNo() { - try { - villager.shakeHead(); - } catch (NoSuchMethodError e) { - villager.getWorld().playSound(villager.getEyeLocation(), Sound.ENTITY_VILLAGER_NO, 1.0F, 1.0F); - } - } - - public @Nullable Location getJobSite() { - return villager.getMemory(MemoryKey.JOB_SITE); - } -} \ No newline at end of file diff --git a/src/main/java/me/xginko/villageroptimizer/commands/optimizevillagers/OptVillagersRadius.java b/src/main/java/me/xginko/villageroptimizer/commands/optimizevillagers/OptVillagersRadius.java index 49f0d4c..b65f5f6 100644 --- a/src/main/java/me/xginko/villageroptimizer/commands/optimizevillagers/OptVillagersRadius.java +++ b/src/main/java/me/xginko/villageroptimizer/commands/optimizevillagers/OptVillagersRadius.java @@ -2,7 +2,7 @@ package me.xginko.villageroptimizer.commands.optimizevillagers; import me.xginko.villageroptimizer.VillagerCache; import me.xginko.villageroptimizer.VillagerOptimizer; -import me.xginko.villageroptimizer.WrappedVillager; +import me.xginko.villageroptimizer.wrapper.WrappedVillager; import me.xginko.villageroptimizer.commands.VillagerOptimizerCommand; import me.xginko.villageroptimizer.config.Config; import me.xginko.villageroptimizer.enums.OptimizationType; diff --git a/src/main/java/me/xginko/villageroptimizer/commands/unoptimizevillagers/UnOptVillagersRadius.java b/src/main/java/me/xginko/villageroptimizer/commands/unoptimizevillagers/UnOptVillagersRadius.java index 0e805c6..145ec04 100644 --- a/src/main/java/me/xginko/villageroptimizer/commands/unoptimizevillagers/UnOptVillagersRadius.java +++ b/src/main/java/me/xginko/villageroptimizer/commands/unoptimizevillagers/UnOptVillagersRadius.java @@ -2,7 +2,7 @@ package me.xginko.villageroptimizer.commands.unoptimizevillagers; import me.xginko.villageroptimizer.VillagerCache; import me.xginko.villageroptimizer.VillagerOptimizer; -import me.xginko.villageroptimizer.WrappedVillager; +import me.xginko.villageroptimizer.wrapper.WrappedVillager; import me.xginko.villageroptimizer.commands.VillagerOptimizerCommand; import me.xginko.villageroptimizer.enums.OptimizationType; import me.xginko.villageroptimizer.enums.Permissions; diff --git a/src/main/java/me/xginko/villageroptimizer/enums/Keyring.java b/src/main/java/me/xginko/villageroptimizer/enums/Keyring.java index cfd1e7d..3d60433 100644 --- a/src/main/java/me/xginko/villageroptimizer/enums/Keyring.java +++ b/src/main/java/me/xginko/villageroptimizer/enums/Keyring.java @@ -50,9 +50,9 @@ public final class Keyring { public enum VillagerOptimizer implements Keyed { OPTIMIZATION_TYPE("optimization-type"), - LAST_OPTIMIZE("last-optimize"), - LAST_LEVELUP("last-levelup"), - LAST_RESTOCK("last-restock"); + LAST_OPTIMIZE_SYSTIME_MILLIS("last-optimize"), + LAST_LEVELUP_SYSTIME_MILLIS("last-levelup"), + LAST_RESTOCK_SYSTIME_MILLIS("last-restock"); private final @NotNull NamespacedKey key; diff --git a/src/main/java/me/xginko/villageroptimizer/events/VillagerOptimizeEvent.java b/src/main/java/me/xginko/villageroptimizer/events/VillagerOptimizeEvent.java index 3b3e6a3..caf1de1 100644 --- a/src/main/java/me/xginko/villageroptimizer/events/VillagerOptimizeEvent.java +++ b/src/main/java/me/xginko/villageroptimizer/events/VillagerOptimizeEvent.java @@ -1,6 +1,6 @@ package me.xginko.villageroptimizer.events; -import me.xginko.villageroptimizer.WrappedVillager; +import me.xginko.villageroptimizer.wrapper.WrappedVillager; import me.xginko.villageroptimizer.enums.OptimizationType; import org.bukkit.entity.Player; import org.bukkit.event.Cancellable; diff --git a/src/main/java/me/xginko/villageroptimizer/events/VillagerUnoptimizeEvent.java b/src/main/java/me/xginko/villageroptimizer/events/VillagerUnoptimizeEvent.java index 4506953..6eb6f8d 100644 --- a/src/main/java/me/xginko/villageroptimizer/events/VillagerUnoptimizeEvent.java +++ b/src/main/java/me/xginko/villageroptimizer/events/VillagerUnoptimizeEvent.java @@ -1,6 +1,6 @@ package me.xginko.villageroptimizer.events; -import me.xginko.villageroptimizer.WrappedVillager; +import me.xginko.villageroptimizer.wrapper.WrappedVillager; import me.xginko.villageroptimizer.enums.OptimizationType; import org.bukkit.entity.Player; import org.bukkit.event.Cancellable; diff --git a/src/main/java/me/xginko/villageroptimizer/modules/gameplay/FixOptimisationAfterCure.java b/src/main/java/me/xginko/villageroptimizer/modules/gameplay/FixOptimisationAfterCure.java index ff4c04a..8868b39 100644 --- a/src/main/java/me/xginko/villageroptimizer/modules/gameplay/FixOptimisationAfterCure.java +++ b/src/main/java/me/xginko/villageroptimizer/modules/gameplay/FixOptimisationAfterCure.java @@ -1,7 +1,7 @@ package me.xginko.villageroptimizer.modules.gameplay; import me.xginko.villageroptimizer.VillagerOptimizer; -import me.xginko.villageroptimizer.WrappedVillager; +import me.xginko.villageroptimizer.wrapper.WrappedVillager; import me.xginko.villageroptimizer.modules.VillagerOptimizerModule; import org.bukkit.entity.EntityType; import org.bukkit.entity.Villager; diff --git a/src/main/java/me/xginko/villageroptimizer/modules/gameplay/LevelOptimizedProfession.java b/src/main/java/me/xginko/villageroptimizer/modules/gameplay/LevelOptimizedProfession.java index b0d5f8d..85ce7e2 100644 --- a/src/main/java/me/xginko/villageroptimizer/modules/gameplay/LevelOptimizedProfession.java +++ b/src/main/java/me/xginko/villageroptimizer/modules/gameplay/LevelOptimizedProfession.java @@ -4,7 +4,7 @@ import com.tcoded.folialib.impl.ServerImplementation; import me.xginko.villageroptimizer.VillagerOptimizer; import me.xginko.villageroptimizer.VillagerCache; import me.xginko.villageroptimizer.config.Config; -import me.xginko.villageroptimizer.WrappedVillager; +import me.xginko.villageroptimizer.wrapper.WrappedVillager; import me.xginko.villageroptimizer.modules.VillagerOptimizerModule; import me.xginko.villageroptimizer.utils.GenericUtil; import me.xginko.villageroptimizer.utils.KyoriUtil; diff --git a/src/main/java/me/xginko/villageroptimizer/modules/gameplay/RestockOptimizedTrades.java b/src/main/java/me/xginko/villageroptimizer/modules/gameplay/RestockOptimizedTrades.java index b741ca3..50672f3 100644 --- a/src/main/java/me/xginko/villageroptimizer/modules/gameplay/RestockOptimizedTrades.java +++ b/src/main/java/me/xginko/villageroptimizer/modules/gameplay/RestockOptimizedTrades.java @@ -2,7 +2,7 @@ package me.xginko.villageroptimizer.modules.gameplay; import me.xginko.villageroptimizer.VillagerCache; import me.xginko.villageroptimizer.VillagerOptimizer; -import me.xginko.villageroptimizer.WrappedVillager; +import me.xginko.villageroptimizer.wrapper.WrappedVillager; import me.xginko.villageroptimizer.config.Config; import me.xginko.villageroptimizer.enums.Permissions; import me.xginko.villageroptimizer.modules.VillagerOptimizerModule; diff --git a/src/main/java/me/xginko/villageroptimizer/modules/gameplay/UnoptimizeOnJobLoose.java b/src/main/java/me/xginko/villageroptimizer/modules/gameplay/UnoptimizeOnJobLoose.java index ecdc979..ccca153 100644 --- a/src/main/java/me/xginko/villageroptimizer/modules/gameplay/UnoptimizeOnJobLoose.java +++ b/src/main/java/me/xginko/villageroptimizer/modules/gameplay/UnoptimizeOnJobLoose.java @@ -2,7 +2,7 @@ package me.xginko.villageroptimizer.modules.gameplay; import me.xginko.villageroptimizer.VillagerCache; import me.xginko.villageroptimizer.VillagerOptimizer; -import me.xginko.villageroptimizer.WrappedVillager; +import me.xginko.villageroptimizer.wrapper.WrappedVillager; import me.xginko.villageroptimizer.enums.OptimizationType; import me.xginko.villageroptimizer.modules.VillagerOptimizerModule; import org.bukkit.event.EventHandler; diff --git a/src/main/java/me/xginko/villageroptimizer/modules/optimization/OptimizeByBlock.java b/src/main/java/me/xginko/villageroptimizer/modules/optimization/OptimizeByBlock.java index 8b05346..6bd6ac2 100644 --- a/src/main/java/me/xginko/villageroptimizer/modules/optimization/OptimizeByBlock.java +++ b/src/main/java/me/xginko/villageroptimizer/modules/optimization/OptimizeByBlock.java @@ -2,7 +2,7 @@ package me.xginko.villageroptimizer.modules.optimization; import me.xginko.villageroptimizer.VillagerCache; import me.xginko.villageroptimizer.VillagerOptimizer; -import me.xginko.villageroptimizer.WrappedVillager; +import me.xginko.villageroptimizer.wrapper.WrappedVillager; import me.xginko.villageroptimizer.config.Config; import me.xginko.villageroptimizer.enums.OptimizationType; import me.xginko.villageroptimizer.enums.Permissions; diff --git a/src/main/java/me/xginko/villageroptimizer/modules/optimization/OptimizeByNametag.java b/src/main/java/me/xginko/villageroptimizer/modules/optimization/OptimizeByNametag.java index 5401562..9612f2f 100644 --- a/src/main/java/me/xginko/villageroptimizer/modules/optimization/OptimizeByNametag.java +++ b/src/main/java/me/xginko/villageroptimizer/modules/optimization/OptimizeByNametag.java @@ -2,7 +2,7 @@ package me.xginko.villageroptimizer.modules.optimization; import me.xginko.villageroptimizer.VillagerCache; import me.xginko.villageroptimizer.VillagerOptimizer; -import me.xginko.villageroptimizer.WrappedVillager; +import me.xginko.villageroptimizer.wrapper.WrappedVillager; import me.xginko.villageroptimizer.config.Config; import me.xginko.villageroptimizer.enums.OptimizationType; import me.xginko.villageroptimizer.enums.Permissions; diff --git a/src/main/java/me/xginko/villageroptimizer/modules/optimization/OptimizeByWorkstation.java b/src/main/java/me/xginko/villageroptimizer/modules/optimization/OptimizeByWorkstation.java index 6be7899..e366ab2 100644 --- a/src/main/java/me/xginko/villageroptimizer/modules/optimization/OptimizeByWorkstation.java +++ b/src/main/java/me/xginko/villageroptimizer/modules/optimization/OptimizeByWorkstation.java @@ -3,7 +3,7 @@ package me.xginko.villageroptimizer.modules.optimization; import com.tcoded.folialib.impl.ServerImplementation; import me.xginko.villageroptimizer.VillagerCache; import me.xginko.villageroptimizer.VillagerOptimizer; -import me.xginko.villageroptimizer.WrappedVillager; +import me.xginko.villageroptimizer.wrapper.WrappedVillager; import me.xginko.villageroptimizer.config.Config; import me.xginko.villageroptimizer.enums.OptimizationType; import me.xginko.villageroptimizer.enums.Permissions; diff --git a/src/main/java/me/xginko/villageroptimizer/wrapper/AVLVillagerDataHandlerImpl.java b/src/main/java/me/xginko/villageroptimizer/wrapper/AVLVillagerDataHandlerImpl.java new file mode 100644 index 0000000..74700e2 --- /dev/null +++ b/src/main/java/me/xginko/villageroptimizer/wrapper/AVLVillagerDataHandlerImpl.java @@ -0,0 +1,144 @@ +package me.xginko.villageroptimizer.wrapper; + +import me.xginko.villageroptimizer.VillagerOptimizer; +import me.xginko.villageroptimizer.enums.Keyring; +import me.xginko.villageroptimizer.enums.OptimizationType; +import org.bukkit.entity.Villager; +import org.bukkit.persistence.PersistentDataContainer; +import org.bukkit.persistence.PersistentDataType; +import org.jetbrains.annotations.NotNull; + +import java.util.concurrent.TimeUnit; + +public class AVLVillagerDataHandlerImpl implements VillagerDataHandler { + + private final @NotNull Villager villager; + private final @NotNull PersistentDataContainer dataContainer; + + AVLVillagerDataHandlerImpl(@NotNull Villager villager) { + this.villager = villager; + this.dataContainer = villager.getPersistentDataContainer(); + } + + @Override + public boolean isMain() { + return false; + } + + @Override + public boolean isOptimized() { + return dataContainer.has(Keyring.AntiVillagerLag.OPTIMIZED_ANY.getKey(), PersistentDataType.STRING) + || dataContainer.has(Keyring.AntiVillagerLag.OPTIMIZED_WORKSTATION.getKey(), PersistentDataType.STRING) + || dataContainer.has(Keyring.AntiVillagerLag.OPTIMIZED_BLOCK.getKey(), PersistentDataType.STRING); + } + + @Override + public boolean canOptimize(final long cooldown_millis) { + return dataContainer.has(Keyring.AntiVillagerLag.NEXT_OPTIMIZATION_SYSTIME_SECONDS.getKey(), PersistentDataType.LONG) + && System.currentTimeMillis() > TimeUnit.SECONDS.toMillis(dataContainer.get(Keyring.AntiVillagerLag.NEXT_OPTIMIZATION_SYSTIME_SECONDS.getKey(), PersistentDataType.LONG)); + } + + @Override + public void setOptimizationType(final OptimizationType type) { + VillagerOptimizer.getFoliaLib().getImpl().runAtEntityTimer(villager, setOptimization -> { + // Keep repeating task until villager is no longer trading with a player + if (villager.isTrading()) return; + + if (type == OptimizationType.NONE) { + if (dataContainer.has(Keyring.AntiVillagerLag.OPTIMIZED_ANY.getKey(), PersistentDataType.STRING)) + dataContainer.remove(Keyring.AntiVillagerLag.OPTIMIZED_ANY.getKey()); + if (dataContainer.has(Keyring.AntiVillagerLag.OPTIMIZED_WORKSTATION.getKey(), PersistentDataType.STRING)) + dataContainer.remove(Keyring.AntiVillagerLag.OPTIMIZED_WORKSTATION.getKey()); + if (dataContainer.has(Keyring.AntiVillagerLag.OPTIMIZED_BLOCK.getKey(), PersistentDataType.STRING)) + dataContainer.remove(Keyring.AntiVillagerLag.OPTIMIZED_BLOCK.getKey()); + + villager.setAware(true); + villager.setAI(true); + } else { + switch (type) { + case BLOCK: + dataContainer.set(Keyring.AntiVillagerLag.OPTIMIZED_BLOCK.getKey(), PersistentDataType.STRING, Keyring.AntiVillagerLag.OPTIMIZED_BLOCK.getKey().toString()); + break; + case WORKSTATION: + dataContainer.set(Keyring.AntiVillagerLag.OPTIMIZED_WORKSTATION.getKey(), PersistentDataType.STRING, Keyring.AntiVillagerLag.OPTIMIZED_WORKSTATION.getKey().toString()); + break; + case COMMAND: + case NAMETAG: + dataContainer.set(Keyring.AntiVillagerLag.OPTIMIZED_ANY.getKey(), PersistentDataType.STRING, "AVL"); + break; + } + + villager.setAware(false); + } + + // End repeating task once logic is finished + setOptimization.cancel(); + }, 0L, 1L, TimeUnit.SECONDS); + } + + @Override + public @NotNull OptimizationType getOptimizationType() { + if (dataContainer.has(Keyring.AntiVillagerLag.OPTIMIZED_BLOCK.getKey(), PersistentDataType.STRING)) { + return OptimizationType.BLOCK; + } + if (dataContainer.has(Keyring.AntiVillagerLag.OPTIMIZED_WORKSTATION.getKey(), PersistentDataType.STRING)) { + return OptimizationType.WORKSTATION; + } + if (dataContainer.has(Keyring.AntiVillagerLag.OPTIMIZED_ANY.getKey(), PersistentDataType.STRING)) { + return OptimizationType.COMMAND; // Best we can do + } + return OptimizationType.NONE; + } + + @Override + public void saveOptimizeTime() { + dataContainer.set(Keyring.AntiVillagerLag.NEXT_OPTIMIZATION_SYSTIME_SECONDS.getKey(), PersistentDataType.LONG, + TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()) + 30); + } + + @Override + public long getOptimizeCooldownMillis(final long cooldown_millis) { + if (dataContainer.has(Keyring.AntiVillagerLag.NEXT_OPTIMIZATION_SYSTIME_SECONDS.getKey(), PersistentDataType.LONG)) { + return Math.max(cooldown_millis, System.currentTimeMillis() - TimeUnit.SECONDS.toMillis(dataContainer.get(Keyring.AntiVillagerLag.NEXT_OPTIMIZATION_SYSTIME_SECONDS.getKey(), PersistentDataType.LONG))); + } + return cooldown_millis; + } + + @Override + public boolean canRestock(final long cooldown_millis) { + if (dataContainer.has(Keyring.AntiVillagerLag.LAST_RESTOCK_WORLDFULLTIME.getKey(), PersistentDataType.LONG)) { + return villager.getWorld().getFullTime() > dataContainer.get(Keyring.AntiVillagerLag.LAST_RESTOCK_WORLDFULLTIME.getKey(), PersistentDataType.LONG); + } + return true; + } + + @Override + public void saveRestockTime() { + dataContainer.set(Keyring.AntiVillagerLag.LAST_RESTOCK_WORLDFULLTIME.getKey(), PersistentDataType.LONG, villager.getWorld().getFullTime()); + } + + @Override + public long getRestockCooldownMillis(final long cooldown_millis) { + if (dataContainer.has(Keyring.AntiVillagerLag.LAST_RESTOCK_WORLDFULLTIME.getKey(), PersistentDataType.LONG)) + return (villager.getWorld().getFullTime() - dataContainer.get(Keyring.AntiVillagerLag.LAST_RESTOCK_WORLDFULLTIME.getKey(), PersistentDataType.LONG)) * 50L; + return cooldown_millis; + } + + @Override + public boolean canLevelUp(final long cooldown_millis) { + return !dataContainer.has(Keyring.AntiVillagerLag.NEXT_LEVELUP_SYSTIME_SECONDS.getKey(), PersistentDataType.LONG) + || System.currentTimeMillis() > TimeUnit.SECONDS.toMillis(dataContainer.get(Keyring.AntiVillagerLag.NEXT_LEVELUP_SYSTIME_SECONDS.getKey(), PersistentDataType.LONG)); + } + + @Override + public void saveLastLevelUp() { + dataContainer.set(Keyring.AntiVillagerLag.NEXT_LEVELUP_SYSTIME_SECONDS.getKey(), PersistentDataType.LONG, TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()) + 30); + } + + @Override + public long getLevelCooldownMillis(final long cooldown_millis) { + if (dataContainer.has(Keyring.AntiVillagerLag.NEXT_LEVELUP_SYSTIME_SECONDS.getKey(), PersistentDataType.LONG)) + return System.currentTimeMillis() - TimeUnit.SECONDS.toMillis(dataContainer.get(Keyring.AntiVillagerLag.NEXT_LEVELUP_SYSTIME_SECONDS.getKey(), PersistentDataType.LONG)); + return cooldown_millis; + } +} \ No newline at end of file diff --git a/src/main/java/me/xginko/villageroptimizer/wrapper/MainVillagerDataHandlerImpl.java b/src/main/java/me/xginko/villageroptimizer/wrapper/MainVillagerDataHandlerImpl.java new file mode 100644 index 0000000..1855139 --- /dev/null +++ b/src/main/java/me/xginko/villageroptimizer/wrapper/MainVillagerDataHandlerImpl.java @@ -0,0 +1,142 @@ +package me.xginko.villageroptimizer.wrapper; + +import me.xginko.villageroptimizer.VillagerOptimizer; +import me.xginko.villageroptimizer.enums.Keyring; +import me.xginko.villageroptimizer.enums.OptimizationType; +import org.bukkit.entity.Villager; +import org.bukkit.persistence.PersistentDataContainer; +import org.bukkit.persistence.PersistentDataType; +import org.jetbrains.annotations.NotNull; + +import java.util.concurrent.TimeUnit; + +public final class MainVillagerDataHandlerImpl implements VillagerDataHandler { + + private final @NotNull Villager villager; + private final @NotNull PersistentDataContainer dataContainer; + + MainVillagerDataHandlerImpl(@NotNull Villager villager) { + this.villager = villager; + this.dataContainer = villager.getPersistentDataContainer(); + } + + @Override + public boolean isMain() { + return true; + } + + @Override + public boolean isOptimized() { + return dataContainer.has(Keyring.VillagerOptimizer.OPTIMIZATION_TYPE.getKey(), PersistentDataType.STRING); + } + + @Override + public boolean canOptimize(final long cooldown_millis) { + return System.currentTimeMillis() > getLastOptimize() + cooldown_millis; + } + + @Override + public void setOptimizationType(final OptimizationType type) { + VillagerOptimizer.getFoliaLib().getImpl().runAtEntityTimer(villager, setOptimization -> { + // Keep repeating task until villager is no longer trading with a player + if (villager.isTrading()) return; + + if (type == OptimizationType.NONE) { + if (isOptimized()) { + dataContainer.remove(Keyring.VillagerOptimizer.OPTIMIZATION_TYPE.getKey()); + } + villager.setAware(true); + villager.setAI(true); + } else { + dataContainer.set(Keyring.VillagerOptimizer.OPTIMIZATION_TYPE.getKey(), PersistentDataType.STRING, type.name()); + villager.setAware(false); + } + + // End repeating task once logic is finished + setOptimization.cancel(); + }, 0L, 1L, TimeUnit.SECONDS); + } + + @Override + public @NotNull OptimizationType getOptimizationType() { + if (!isOptimized()) { + return OptimizationType.valueOf(dataContainer.get(Keyring.VillagerOptimizer.OPTIMIZATION_TYPE.getKey(), PersistentDataType.STRING)); + } else { + return OptimizationType.NONE; + } + } + + @Override + public void saveOptimizeTime() { + dataContainer.set(Keyring.VillagerOptimizer.LAST_OPTIMIZE_SYSTIME_MILLIS.getKey(), PersistentDataType.LONG, System.currentTimeMillis()); + } + + /** + * @return The system time in millis when the villager was last optimized, 0L if the villager was never optimized. + */ + public long getLastOptimize() { + if (dataContainer.has(Keyring.VillagerOptimizer.LAST_OPTIMIZE_SYSTIME_MILLIS.getKey(), PersistentDataType.LONG)) { + return dataContainer.get(Keyring.VillagerOptimizer.LAST_OPTIMIZE_SYSTIME_MILLIS.getKey(), PersistentDataType.LONG); + } + return 0L; + } + + @Override + public long getOptimizeCooldownMillis(final long cooldown_millis) { + return Math.max(System.currentTimeMillis() - getLastOptimize(), cooldown_millis); + } + + @Override + public boolean canRestock(final long cooldown_millis) { + return getLastRestock() + cooldown_millis <= System.currentTimeMillis(); + } + + @Override + public void saveRestockTime() { + dataContainer.set(Keyring.VillagerOptimizer.LAST_RESTOCK_SYSTIME_MILLIS.getKey(), PersistentDataType.LONG, System.currentTimeMillis()); + } + + /** + * @return The time when the entity was last restocked. + */ + public long getLastRestock() { + long lastRestock = 0L; + if (dataContainer.has(Keyring.VillagerOptimizer.LAST_RESTOCK_SYSTIME_MILLIS.getKey(), PersistentDataType.LONG)) { + lastRestock = dataContainer.get(Keyring.VillagerOptimizer.LAST_RESTOCK_SYSTIME_MILLIS.getKey(), PersistentDataType.LONG); + } + return lastRestock; + } + + @Override + public long getRestockCooldownMillis(final long cooldown_millis) { + if (dataContainer.has(Keyring.VillagerOptimizer.LAST_RESTOCK_SYSTIME_MILLIS.getKey(), PersistentDataType.LONG)) + return System.currentTimeMillis() - (dataContainer.get(Keyring.VillagerOptimizer.LAST_RESTOCK_SYSTIME_MILLIS.getKey(), PersistentDataType.LONG) + cooldown_millis); + return cooldown_millis; + } + + @Override + public boolean canLevelUp(final long cooldown_millis) { + return System.currentTimeMillis() >= getLastLevelUpTime() + cooldown_millis; + } + + @Override + public void saveLastLevelUp() { + dataContainer.set(Keyring.VillagerOptimizer.LAST_LEVELUP_SYSTIME_MILLIS.getKey(), PersistentDataType.LONG, System.currentTimeMillis()); + } + + /** + * @return The systime in millis when the entity was last leveled up. + */ + public long getLastLevelUpTime() { + if (dataContainer.has(Keyring.VillagerOptimizer.LAST_LEVELUP_SYSTIME_MILLIS.getKey(), PersistentDataType.LONG)) + return dataContainer.get(Keyring.VillagerOptimizer.LAST_LEVELUP_SYSTIME_MILLIS.getKey(), PersistentDataType.LONG); + return 0L; + } + + @Override + public long getLevelCooldownMillis(final long cooldown_millis) { + if (dataContainer.has(Keyring.VillagerOptimizer.LAST_LEVELUP_SYSTIME_MILLIS.getKey(), PersistentDataType.LONG)) + return System.currentTimeMillis() - (dataContainer.get(Keyring.VillagerOptimizer.LAST_LEVELUP_SYSTIME_MILLIS.getKey(), PersistentDataType.LONG) + cooldown_millis); + return cooldown_millis; + } +} \ No newline at end of file diff --git a/src/main/java/me/xginko/villageroptimizer/wrapper/VillagerDataHandler.java b/src/main/java/me/xginko/villageroptimizer/wrapper/VillagerDataHandler.java new file mode 100644 index 0000000..afd50b4 --- /dev/null +++ b/src/main/java/me/xginko/villageroptimizer/wrapper/VillagerDataHandler.java @@ -0,0 +1,105 @@ +package me.xginko.villageroptimizer.wrapper; + +import me.xginko.villageroptimizer.VillagerOptimizer; +import me.xginko.villageroptimizer.enums.OptimizationType; +import org.bukkit.entity.Villager; +import org.jetbrains.annotations.NotNull; + +public interface VillagerDataHandler { + + static VillagerDataHandler[] forVillager(Villager villager) { + if (VillagerOptimizer.getConfiguration().support_other_plugins) { + return new VillagerDataHandler[]{ + new MainVillagerDataHandlerImpl(villager), + new AVLVillagerDataHandlerImpl(villager) + }; + } else { + return new VillagerDataHandler[]{ new MainVillagerDataHandlerImpl(villager) }; + } + } + + /** + * @return True if the DataHandle is this plugin's implementation. + */ + boolean isMain(); + + /** + * @return True if the villager is optimized by plugin, otherwise false. + */ + boolean isOptimized(); + + /** + * @param cooldown_millis The configured cooldown in millis until the next optimization is allowed to occur. + * @return True if villager can be optimized again, otherwise false. + */ + boolean canOptimize(final long cooldown_millis); + + /** + * @param type OptimizationType the villager should be set to. + */ + void setOptimizationType(final OptimizationType type); + + /** + * @return The current OptimizationType of the villager. + */ + @NotNull OptimizationType getOptimizationType(); + + /** + * Saves the system time when the villager was last optimized. + */ + void saveOptimizeTime(); + + /** + * For convenience so the remaining millis since the last stored optimize time + * can be easily calculated. + * This enables new configured cooldowns to instantly apply instead of them being persistent. + * + * @param cooldown_millis The configured cooldown in milliseconds you want to check against. + * @return The time left in millis until the villager can be optimized again. + */ + long getOptimizeCooldownMillis(final long cooldown_millis); + + /** + * For convenience so the remaining millis since the last stored restock time + * can be easily calculated. + * + * @param cooldown_millis The configured cooldown in milliseconds you want to check against. + * @return True if the villager has been loaded long enough. + */ + boolean canRestock(final long cooldown_millis); + + /** + * Saves the time of when the entity was last restocked. + */ + void saveRestockTime(); + + /** + * For convenience so the remaining millis since the last stored restock time + * can be easily calculated. + * This enables new configured cooldowns to instantly apply instead of them being persistent. + * + * @param cooldown_millis The configured cooldown in milliseconds you want to check against. + * @return The time left in millis until the villager can be restocked again. + */ + long getRestockCooldownMillis(final long cooldown_millis); + + /** + * @param cooldown_millis The configured cooldown in milliseconds you want to check against. + * @return Whether the villager can be leveled up or not with the checked milliseconds + */ + boolean canLevelUp(final long cooldown_millis); + + /** + * Saves the time of the in-game world when the entity was last leveled up. + */ + void saveLastLevelUp(); + + /** + * Here for convenience so the remaining millis since the last stored level-up time + * can be easily calculated. + * + * @return The time of the in-game world when the entity was last leveled up. + */ + long getLevelCooldownMillis(final long cooldown_millis); + +} diff --git a/src/main/java/me/xginko/villageroptimizer/wrapper/WrappedVillager.java b/src/main/java/me/xginko/villageroptimizer/wrapper/WrappedVillager.java new file mode 100644 index 0000000..4ac3c9b --- /dev/null +++ b/src/main/java/me/xginko/villageroptimizer/wrapper/WrappedVillager.java @@ -0,0 +1,188 @@ +package me.xginko.villageroptimizer.wrapper; + +import me.xginko.villageroptimizer.enums.OptimizationType; +import org.bukkit.Location; +import org.bukkit.Sound; +import org.bukkit.entity.Villager; +import org.bukkit.entity.memory.MemoryKey; +import org.bukkit.inventory.MerchantRecipe; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class WrappedVillager implements VillagerDataHandler { + + private final @NotNull Villager villager; + private final @NotNull VillagerDataHandler[] dataHandlers; + + public WrappedVillager(@NotNull Villager villager) { + this.villager = villager; + this.dataHandlers = VillagerDataHandler.forVillager(villager); + } + + /** + * @return The villager inside the wrapper. + */ + public @NotNull Villager villager() { + return villager; + } + + /** + * Restock all trading recipes. + */ + public void restock() { + for (MerchantRecipe recipe : villager.getRecipes()) { + recipe.setUses(0); + } + } + + /** + * @return The level between 1-5 calculated from the villagers experience. + */ + public int calculateLevel() { + // https://minecraft.fandom.com/wiki/Trading#Mechanics + int vilEXP = villager.getVillagerExperience(); + if (vilEXP >= 250) return 5; + if (vilEXP >= 150) return 4; + if (vilEXP >= 70) return 3; + if (vilEXP >= 10) return 2; + return 1; + } + + /** + * @return true if the villager can loose his acquired profession by having their workstation destroyed. + */ + public boolean canLooseProfession() { + // A villager with a level of 1 and no trading experience is liable to lose its profession. + return villager.getVillagerLevel() <= 1 && villager.getVillagerExperience() <= 0; + } + + public void sayNo() { + try { + villager.shakeHead(); + } catch (NoSuchMethodError e) { + villager.getWorld().playSound(villager.getEyeLocation(), Sound.ENTITY_VILLAGER_NO, 1.0F, 1.0F); + } + } + + public @Nullable Location getJobSite() { + return villager.getMemory(MemoryKey.JOB_SITE); + } + + @Override + public boolean isMain() { + return false; + } + + @Override + public boolean isOptimized() { + for (VillagerDataHandler handler : dataHandlers) { + if (handler.isOptimized()) { + return true; + } + } + return false; + } + + @Override + public boolean canOptimize(long cooldown_millis) { + for (VillagerDataHandler handler : dataHandlers) { + if (handler.canOptimize(cooldown_millis)) { + return true; + } + } + return false; + } + + @Override + public void setOptimizationType(OptimizationType type) { + for (VillagerDataHandler handler : dataHandlers) { + handler.setOptimizationType(type); + } + } + + @Override + public @NotNull OptimizationType getOptimizationType() { + OptimizationType result = OptimizationType.NONE; + for (VillagerDataHandler handler : dataHandlers) { + OptimizationType type = handler.getOptimizationType(); + if (type != OptimizationType.NONE) { + if (handler.isMain()) { + return type; + } else { + result = type; + } + } + } + return result; + } + + @Override + public void saveOptimizeTime() { + for (VillagerDataHandler handler : dataHandlers) { + handler.saveOptimizeTime(); + } + } + + @Override + public long getOptimizeCooldownMillis(long cooldown_millis) { + long cooldown = cooldown_millis; + for (VillagerDataHandler handler : dataHandlers) { + cooldown = Math.max(cooldown, handler.getOptimizeCooldownMillis(cooldown_millis)); + } + return cooldown; + } + + @Override + public boolean canRestock(long cooldown_millis) { + boolean can_restock = true; + for (VillagerDataHandler handler : dataHandlers) { + if (!handler.canRestock(cooldown_millis)) { + can_restock = false; + } + } + return can_restock; + } + + @Override + public void saveRestockTime() { + for (VillagerDataHandler handler : dataHandlers) { + handler.saveRestockTime(); + } + } + + @Override + public long getRestockCooldownMillis(long cooldown_millis) { + long cooldown = 0L; + for (VillagerDataHandler handler : dataHandlers) { + cooldown = Math.max(cooldown, handler.getRestockCooldownMillis(cooldown_millis)); + } + return cooldown; + } + + @Override + public boolean canLevelUp(long cooldown_millis) { + boolean can_level_up = true; + for (VillagerDataHandler handler : dataHandlers) { + if (!handler.canLevelUp(cooldown_millis)) { + can_level_up = false; + } + } + return can_level_up; + } + + @Override + public void saveLastLevelUp() { + for (VillagerDataHandler handler : dataHandlers) { + handler.saveLastLevelUp(); + } + } + + @Override + public long getLevelCooldownMillis(long cooldown_millis) { + long cooldown = 0L; + for (VillagerDataHandler handler : dataHandlers) { + cooldown = Math.max(cooldown_millis, handler.getLevelCooldownMillis(cooldown_millis)); + } + return cooldown; + } +} \ No newline at end of file