diff --git a/pom.xml b/pom.xml index 5661c2c..c3385e2 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ me.xginko.VillagerOptimizer VillagerOptimizer - 1.2.0 + 1.3.0 jar VillagerOptimizer diff --git a/src/main/java/me/xginko/villageroptimizer/WrappedVillager.java b/src/main/java/me/xginko/villageroptimizer/WrappedVillager.java index 1a55225..365887a 100644 --- a/src/main/java/me/xginko/villageroptimizer/WrappedVillager.java +++ b/src/main/java/me/xginko/villageroptimizer/WrappedVillager.java @@ -14,10 +14,12 @@ 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; } /** @@ -35,10 +37,28 @@ public final class WrappedVillager { } /** - * @return True if the villager is optimized by this plugin, otherwise false. + * @return True if the villager is optimized by either this plugin or a supported alternative, otherwise false. */ public boolean isOptimized() { - return dataContainer.has(Keys.OPTIMIZATION_TYPE.key(), PersistentDataType.STRING); + if (!parseOther) { + return isOptimized(Keys.Origin.VillagerOptimizer); + } + for (Keys.Origin pluginOrigin : Keys.Origin.values()) { + if (isOptimized(pluginOrigin)) return true; + } + return false; + } + + /** + * @return True if the villager is optimized by the supported plugin, otherwise false. + */ + public boolean isOptimized(Keys.Origin origin) { + return switch (origin) { + case VillagerOptimizer -> dataContainer.has(Keys.Own.OPTIMIZATION_TYPE.key(), PersistentDataType.STRING); + case AntiVillagerLag -> dataContainer.has(Keys.AntiVillagerLag.OPTIMIZED_ANY.key(), PersistentDataType.STRING) + || dataContainer.has(Keys.AntiVillagerLag.OPTIMIZED_WORKSTATION.key(), PersistentDataType.STRING) + || dataContainer.has(Keys.AntiVillagerLag.OPTIMIZED_BLOCK.key(), PersistentDataType.STRING); + }; } /** @@ -46,7 +66,15 @@ public final class WrappedVillager { * @return True if villager can be optimized again, otherwise false. */ public boolean canOptimize(final long cooldown_millis) { - return getLastOptimize() + cooldown_millis <= System.currentTimeMillis(); + if (parseOther) { + if ( + dataContainer.has(Keys.AntiVillagerLag.NEXT_OPTIMIZATION_SYSTIME_SECONDS.key(), PersistentDataType.LONG) + && System.currentTimeMillis() <= 1000 * dataContainer.get(Keys.AntiVillagerLag.NEXT_OPTIMIZATION_SYSTIME_SECONDS.key(), PersistentDataType.LONG) + ) { + return false; + } + } + return System.currentTimeMillis() > getLastOptimize() + cooldown_millis; } /** @@ -54,13 +82,15 @@ public final class WrappedVillager { */ public void setOptimization(OptimizationType type) { if (type.equals(OptimizationType.NONE) && isOptimized()) { - dataContainer.remove(Keys.OPTIMIZATION_TYPE.key()); + if (!parseOther || isOptimized(Keys.Origin.VillagerOptimizer)) { + dataContainer.remove(Keys.Own.OPTIMIZATION_TYPE.key()); + } VillagerOptimizer.getScheduler().runAtEntity(villager, enableAI -> { villager.setAware(true); villager.setAI(true); }); } else { - dataContainer.set(Keys.OPTIMIZATION_TYPE.key(), PersistentDataType.STRING, type.name()); + dataContainer.set(Keys.Own.OPTIMIZATION_TYPE.key(), PersistentDataType.STRING, type.name()); VillagerOptimizer.getScheduler().runAtEntity(villager, disableAI -> villager.setAware(false)); } } @@ -69,21 +99,54 @@ public final class WrappedVillager { * @return The current OptimizationType of the villager. */ public @NotNull OptimizationType getOptimizationType() { - return isOptimized() ? OptimizationType.valueOf(dataContainer.get(Keys.OPTIMIZATION_TYPE.key(), PersistentDataType.STRING)) : OptimizationType.NONE; + if (!parseOther) { + return getOptimizationType(Keys.Origin.VillagerOptimizer); + } + OptimizationType optimizationType = getOptimizationType(Keys.Origin.VillagerOptimizer); + if (optimizationType != OptimizationType.NONE) { + return optimizationType; + } + return getOptimizationType(Keys.Origin.AntiVillagerLag); + } + + public @NotNull OptimizationType getOptimizationType(Keys.Origin origin) { + return switch (origin) { + case VillagerOptimizer -> { + if (isOptimized(Keys.Origin.VillagerOptimizer)) { + yield OptimizationType.valueOf(dataContainer.get(Keys.Own.OPTIMIZATION_TYPE.key(), PersistentDataType.STRING)); + } + yield OptimizationType.NONE; + } + case AntiVillagerLag -> { + if (dataContainer.has(Keys.AntiVillagerLag.OPTIMIZED_BLOCK.key(), PersistentDataType.STRING)) { + yield OptimizationType.BLOCK; + } + if (dataContainer.has(Keys.AntiVillagerLag.OPTIMIZED_WORKSTATION.key(), PersistentDataType.STRING)) { + yield OptimizationType.WORKSTATION; + } + if (dataContainer.has(Keys.AntiVillagerLag.OPTIMIZED_ANY.key(), PersistentDataType.STRING)) { + yield OptimizationType.COMMAND; // Best we can do + } + yield OptimizationType.NONE; + } + }; } /** * Saves the system time in millis when the villager was last optimized. */ public void saveOptimizeTime() { - dataContainer.set(Keys.LAST_OPTIMIZE.key(), PersistentDataType.LONG, System.currentTimeMillis()); + dataContainer.set(Keys.Own.LAST_OPTIMIZE.key(), 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() { - return dataContainer.has(Keys.LAST_OPTIMIZE.key(), PersistentDataType.LONG) ? dataContainer.get(Keys.LAST_OPTIMIZE.key(), PersistentDataType.LONG) : 0L; + if (dataContainer.has(Keys.Own.LAST_OPTIMIZE.key(), PersistentDataType.LONG)) { + return dataContainer.get(Keys.Own.LAST_OPTIMIZE.key(), PersistentDataType.LONG); + } + return 0L; } /** @@ -95,7 +158,21 @@ public final class WrappedVillager { * @return The time left in millis until the villager can be optimized again. */ public long getOptimizeCooldownMillis(final long cooldown_millis) { - return dataContainer.has(Keys.LAST_OPTIMIZE.key(), PersistentDataType.LONG) ? (System.currentTimeMillis() - (dataContainer.get(Keys.LAST_OPTIMIZE.key(), PersistentDataType.LONG) + cooldown_millis)) : cooldown_millis; + long remainingMillis = 0L; + + if (parseOther) { + if (dataContainer.has(Keys.AntiVillagerLag.NEXT_OPTIMIZATION_SYSTIME_SECONDS.key(), PersistentDataType.LONG)) { + remainingMillis = System.currentTimeMillis() - dataContainer.get(Keys.AntiVillagerLag.NEXT_OPTIMIZATION_SYSTIME_SECONDS.key(), PersistentDataType.LONG); + } + } + + if (remainingMillis > 0) return remainingMillis; + + if (dataContainer.has(Keys.Own.LAST_OPTIMIZE.key(), PersistentDataType.LONG)) { + return System.currentTimeMillis() - (dataContainer.get(Keys.Own.LAST_OPTIMIZE.key(), PersistentDataType.LONG) + cooldown_millis); + } + + return cooldown_millis; } /** @@ -120,18 +197,30 @@ public final class WrappedVillager { * Saves the time of the in-game world when the entity was last restocked. */ public void saveRestockTime() { - dataContainer.set(Keys.LAST_RESTOCK.key(), PersistentDataType.LONG, villager.getWorld().getFullTime()); + dataContainer.set(Keys.Own.LAST_RESTOCK.key(), PersistentDataType.LONG, villager.getWorld().getFullTime()); } /** * @return The time of the in-game world when the entity was last restocked. */ public long getLastRestock() { - return dataContainer.has(Keys.LAST_RESTOCK.key(), PersistentDataType.LONG) ? dataContainer.get(Keys.LAST_RESTOCK.key(), PersistentDataType.LONG) : 0L; + long lastRestock = 0L; + if (dataContainer.has(Keys.Own.LAST_RESTOCK.key(), PersistentDataType.LONG)) { + lastRestock = dataContainer.get(Keys.Own.LAST_RESTOCK.key(), PersistentDataType.LONG); + } + if (parseOther) { + if (dataContainer.has(Keys.AntiVillagerLag.LAST_RESTOCK_WORLDFULLTIME.key(), PersistentDataType.LONG)) { + long lastAVLRestock = dataContainer.get(Keys.AntiVillagerLag.LAST_RESTOCK_WORLDFULLTIME.key(), PersistentDataType.LONG); + if (lastRestock < lastAVLRestock) { + lastRestock = lastAVLRestock; + } + } + } + return lastRestock; } public long getRestockCooldownMillis(final long cooldown_millis) { - return dataContainer.has(Keys.LAST_RESTOCK.key(), PersistentDataType.LONG) ? (villager.getWorld().getFullTime() - (dataContainer.get(Keys.LAST_RESTOCK.key(), PersistentDataType.LONG) + cooldown_millis)) : cooldown_millis; + return dataContainer.has(Keys.Own.LAST_RESTOCK.key(), PersistentDataType.LONG) ? (villager.getWorld().getFullTime() - (dataContainer.get(Keys.Own.LAST_RESTOCK.key(), PersistentDataType.LONG) + cooldown_millis)) : cooldown_millis; } /** @@ -152,14 +241,23 @@ public final class WrappedVillager { * @return Whether the villager can be leveled up or not with the checked milliseconds */ public boolean canLevelUp(final long cooldown_millis) { - return getLastLevelUpTime() + cooldown_millis <= villager.getWorld().getFullTime(); + if (villager.getWorld().getFullTime() < getLastLevelUpTime() + cooldown_millis) { + return false; + } + + if (parseOther) { + return !dataContainer.has(Keys.AntiVillagerLag.NEXT_LEVELUP_SYSTIME_SECONDS.key(), PersistentDataType.LONG) + || System.currentTimeMillis() > dataContainer.get(Keys.AntiVillagerLag.NEXT_LEVELUP_SYSTIME_SECONDS.key(), 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(Keys.LAST_LEVELUP.key(), PersistentDataType.LONG, villager.getWorld().getFullTime()); + dataContainer.set(Keys.Own.LAST_LEVELUP.key(), PersistentDataType.LONG, villager.getWorld().getFullTime()); } /** @@ -169,22 +267,22 @@ public final class WrappedVillager { * @return The time of the in-game world when the entity was last leveled up. */ public long getLastLevelUpTime() { - return dataContainer.has(Keys.LAST_LEVELUP.key(), PersistentDataType.LONG) ? dataContainer.get(Keys.LAST_LEVELUP.key(), PersistentDataType.LONG) : 0L; + return dataContainer.has(Keys.Own.LAST_LEVELUP.key(), PersistentDataType.LONG) ? dataContainer.get(Keys.Own.LAST_LEVELUP.key(), PersistentDataType.LONG) : 0L; } public long getLevelCooldownMillis(final long cooldown_millis) { - return dataContainer.has(Keys.LAST_LEVELUP.key(), PersistentDataType.LONG) ? (villager.getWorld().getFullTime() - (dataContainer.get(Keys.LAST_LEVELUP.key(), PersistentDataType.LONG) + cooldown_millis)) : cooldown_millis; + return dataContainer.has(Keys.Own.LAST_LEVELUP.key(), PersistentDataType.LONG) ? (villager.getWorld().getFullTime() - (dataContainer.get(Keys.Own.LAST_LEVELUP.key(), PersistentDataType.LONG) + cooldown_millis)) : cooldown_millis; } public void memorizeName(final Component customName) { - dataContainer.set(Keys.LAST_OPTIMIZE_NAME.key(), PersistentDataType.STRING, MiniMessage.miniMessage().serialize(customName)); + dataContainer.set(Keys.Own.LAST_OPTIMIZE_NAME.key(), PersistentDataType.STRING, MiniMessage.miniMessage().serialize(customName)); } public @Nullable Component getMemorizedName() { - return dataContainer.has(Keys.LAST_OPTIMIZE_NAME.key(), PersistentDataType.STRING) ? MiniMessage.miniMessage().deserialize(dataContainer.get(Keys.LAST_OPTIMIZE_NAME.key(), PersistentDataType.STRING)) : null; + return dataContainer.has(Keys.Own.LAST_OPTIMIZE_NAME.key(), PersistentDataType.STRING) ? MiniMessage.miniMessage().deserialize(dataContainer.get(Keys.Own.LAST_OPTIMIZE_NAME.key(), PersistentDataType.STRING)) : null; } public void forgetName() { - dataContainer.remove(Keys.LAST_OPTIMIZE_NAME.key()); + dataContainer.remove(Keys.Own.LAST_OPTIMIZE_NAME.key()); } } \ No newline at end of file diff --git a/src/main/java/me/xginko/villageroptimizer/config/Config.java b/src/main/java/me/xginko/villageroptimizer/config/Config.java index 68ded85..c62ae2d 100644 --- a/src/main/java/me/xginko/villageroptimizer/config/Config.java +++ b/src/main/java/me/xginko/villageroptimizer/config/Config.java @@ -1,20 +1,18 @@ package me.xginko.villageroptimizer.config; import io.github.thatsmusic99.configurationmaster.api.ConfigFile; -import io.github.thatsmusic99.configurationmaster.api.ConfigSection; import me.xginko.villageroptimizer.VillagerOptimizer; import org.jetbrains.annotations.NotNull; import java.io.File; import java.util.List; import java.util.Locale; -import java.util.Map; public class Config { private final @NotNull ConfigFile config; public final @NotNull Locale default_lang; - public final boolean auto_lang; + public final boolean auto_lang, support_other_plugins; public final long cache_keep_time_seconds; public Config() throws Exception { @@ -35,6 +33,9 @@ public class Config { "If set to true, will display messages based on client language"); this.cache_keep_time_seconds = getInt("general.cache-keep-time-seconds", 30, "The amount of time in seconds a villager will be kept in the plugin's cache."); + this.support_other_plugins = getBoolean("general.support-avl-villagers", true, """ + Enable if you have previously used AntiVillagerLag (https://www.spigotmc.org/resources/antivillagerlag.102949/).\s + Tries to read pre-existing info like optimization state so players don't need to reoptimize their villagers."""); } public void saveConfig() { diff --git a/src/main/java/me/xginko/villageroptimizer/enums/Keys.java b/src/main/java/me/xginko/villageroptimizer/enums/Keys.java index 85b05e6..e7e2436 100644 --- a/src/main/java/me/xginko/villageroptimizer/enums/Keys.java +++ b/src/main/java/me/xginko/villageroptimizer/enums/Keys.java @@ -2,19 +2,62 @@ package me.xginko.villageroptimizer.enums; import me.xginko.villageroptimizer.VillagerOptimizer; import org.bukkit.NamespacedKey; +import org.bukkit.persistence.PersistentDataContainer; +import org.bukkit.plugin.Plugin; -public enum Keys { - OPTIMIZATION_TYPE(VillagerOptimizer.getKey("optimization-type")), - LAST_OPTIMIZE(VillagerOptimizer.getKey("last-optimize")), - LAST_LEVELUP(VillagerOptimizer.getKey("last-levelup")), - LAST_RESTOCK(VillagerOptimizer.getKey("last-restock")), - LAST_OPTIMIZE_NAME(VillagerOptimizer.getKey("last-optimize-name")); +import java.util.Locale; - private final NamespacedKey key; - Keys(NamespacedKey key) { - this.key = key; +public class Keys { + public enum Origin { + VillagerOptimizer, + AntiVillagerLag; } - public NamespacedKey key() { - return key; + + public enum Own { + OPTIMIZATION_TYPE(VillagerOptimizer.getKey("optimization-type")), + LAST_OPTIMIZE(VillagerOptimizer.getKey("last-optimize")), + LAST_LEVELUP(VillagerOptimizer.getKey("last-levelup")), + LAST_RESTOCK(VillagerOptimizer.getKey("last-restock")), + LAST_OPTIMIZE_NAME(VillagerOptimizer.getKey("last-optimize-name")); + + private final NamespacedKey key; + Own(NamespacedKey key) { + this.key = key; + } + public NamespacedKey key() { + return key; + } + } + + public enum AntiVillagerLag { + NEXT_OPTIMIZATION_SYSTIME_SECONDS("cooldown"), // Returns LONG -> System.currentTimeMillis() / 1000 + cooldown seconds + LAST_RESTOCK_WORLDFULLTIME("time"), // Returns LONG -> villager.getWorld().getFullTime() + NEXT_LEVELUP_SYSTIME_SECONDS("levelCooldown"), // Returns LONG -> System.currentTimeMillis() / 1000 + cooldown seconds + OPTIMIZED_ANY("Marker"), // Returns STRING -> "AVL" + OPTIMIZED_BLOCK("disabledByBlock"), // Returns STRING -> key().toString() + OPTIMIZED_WORKSTATION("disabledByWorkstation"); // Returns STRING -> key().toString() + + private final NamespacedKey key; + + AntiVillagerLag(String avlKey) { + this.key = getKey(avlKey); + } + + public NamespacedKey key() { + return key; + } + + /** + * Returns a NamespacedKey as if it was created by AntiVillagerLag. + * This is possible because they are created using {@link NamespacedKey#NamespacedKey(Plugin, String)}, + * meaning the Namespace is always the return of {@link Plugin#getName()} && {@link String#toLowerCase()} + * using {@link Locale#ROOT} + * + * @return a {@link NamespacedKey} that can be used to test for and read data stored by AntiVillagerLag + * from a {@link PersistentDataContainer} + */ + public static NamespacedKey getKey(String key) { + return new NamespacedKey("AntiVillagerLag".toLowerCase(Locale.ROOT), key); + } } } 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 2cf3453..7b4d9a5 100644 --- a/src/main/java/me/xginko/villageroptimizer/modules/optimization/OptimizeByBlock.java +++ b/src/main/java/me/xginko/villageroptimizer/modules/optimization/OptimizeByBlock.java @@ -4,8 +4,8 @@ import me.xginko.villageroptimizer.VillagerCache; import me.xginko.villageroptimizer.VillagerOptimizer; import me.xginko.villageroptimizer.WrappedVillager; import me.xginko.villageroptimizer.config.Config; -import me.xginko.villageroptimizer.enums.permissions.Bypass; import me.xginko.villageroptimizer.enums.OptimizationType; +import me.xginko.villageroptimizer.enums.permissions.Bypass; import me.xginko.villageroptimizer.enums.permissions.Optimize; import me.xginko.villageroptimizer.events.VillagerOptimizeEvent; import me.xginko.villageroptimizer.events.VillagerUnoptimizeEvent;