From 1d24209b687ec13bae0fa8267bda8ce8adcc6ef6 Mon Sep 17 00:00:00 2001 From: William Date: Fri, 26 Jul 2024 14:26:52 +0100 Subject: [PATCH] feat: add attribute config, don't sync potion modifiers, close #349 --- .../william278/husksync/data/BukkitData.java | 34 ++++++++----- .../william278/husksync/config/Settings.java | 51 +++++++++++++++---- 2 files changed, 60 insertions(+), 25 deletions(-) diff --git a/bukkit/src/main/java/net/william278/husksync/data/BukkitData.java b/bukkit/src/main/java/net/william278/husksync/data/BukkitData.java index e4b53c68..acd3c3e3 100644 --- a/bukkit/src/main/java/net/william278/husksync/data/BukkitData.java +++ b/bukkit/src/main/java/net/william278/husksync/data/BukkitData.java @@ -31,6 +31,7 @@ import net.william278.desertwell.util.Version; import net.william278.husksync.BukkitHuskSync; import net.william278.husksync.HuskSync; import net.william278.husksync.adapter.Adaptable; +import net.william278.husksync.config.Settings.SynchronizationSettings.AttributeSettings; import net.william278.husksync.user.BukkitUser; import org.bukkit.Bukkit; import org.bukkit.Material; @@ -51,6 +52,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Range; +import java.lang.reflect.Constructor; import java.util.*; import java.util.logging.Level; import java.util.stream.Collectors; @@ -366,7 +368,7 @@ public abstract class BukkitData implements Data { // Set player experience and level (prevent advancement awards applying twice), reset game rule if (!toAward.isEmpty() - && (player.getLevel() != expLevel || player.getExp() != expProgress)) { + && (player.getLevel() != expLevel || player.getExp() != expProgress)) { player.setLevel(expLevel); player.setExp(expProgress); } @@ -577,14 +579,14 @@ public abstract class BukkitData implements Data { @NotNull public static BukkitData.Attributes adapt(@NotNull Player player, @NotNull HuskSync plugin) { final List attributes = Lists.newArrayList(); + final AttributeSettings settings = plugin.getSettings().getSynchronization().getAttributes(); Registry.ATTRIBUTE.forEach(id -> { final AttributeInstance instance = player.getAttribute(id); if (instance == null || Double.compare(instance.getValue(), instance.getDefaultValue()) == 0 - || plugin.getSettings().getSynchronization().isIgnoredAttribute(id.getKey().toString())) { - // We don't sync unmodified or disabled attributes - return; + || settings.isIgnoredAttribute(id.getKey().toString())) { + return; // We don't sync unmodified or disabled attributes } - attributes.add(adapt(instance)); + attributes.add(adapt(instance, settings)); }); return new BukkitData.Attributes(attributes); } @@ -603,11 +605,13 @@ public abstract class BukkitData implements Data { } @NotNull - private static Attribute adapt(@NotNull AttributeInstance instance) { + private static Attribute adapt(@NotNull AttributeInstance instance, @NotNull AttributeSettings settings) { return new Attribute( instance.getAttribute().getKey().toString(), instance.getBaseValue(), - instance.getModifiers().stream().map(BukkitData.Attributes::adapt).collect(Collectors.toSet()) + instance.getModifiers().stream() + .filter(modifier -> !settings.isIgnoredModifier(modifier.getName())) + .map(BukkitData.Attributes::adapt).collect(Collectors.toSet()) ); } @@ -639,7 +643,7 @@ public abstract class BukkitData implements Data { instance.setBaseValue(attribute == null ? instance.getDefaultValue() : attribute.baseValue()); instance.getModifiers().forEach(instance::removeModifier); if (attribute != null) { - attribute.modifiers().forEach(modifier -> instance.addModifier(adapt(modifier, plugin))); + attribute.modifiers().forEach(mod -> instance.addModifier(adapt(mod, plugin))); } } @@ -648,8 +652,8 @@ public abstract class BukkitData implements Data { private static AttributeModifier adapt(@NotNull Modifier modifier, @NotNull HuskSync plugin) { final int slotId = modifier.equipmentSlot(); if (USE_KEYED_MODIFIERS == TriState.NOT_SET) { - USE_KEYED_MODIFIERS = TriState.byBoolean(plugin.getMinecraftVersion() - .compareTo(Version.fromString("1.21")) >= 0); + boolean is1_21 = plugin.getMinecraftVersion().compareTo(Version.fromString("1.21")) >= 0; + USE_KEYED_MODIFIERS = TriState.byBoolean(is1_21); } if (USE_KEYED_MODIFIERS == TriState.TRUE) { try { @@ -657,10 +661,12 @@ public abstract class BukkitData implements Data { final EquipmentSlot slot = slotId != -1 ? EquipmentSlot.values()[slotId] : null; final Class slotGroup = Class.forName(EQUIPMENT_SLOT_GROUP); final String modifierName = modifier.name() == null ? modifier.uuid().toString() : modifier.name(); - return AttributeModifier.class.getDeclaredConstructor( - NamespacedKey.class, double.class, AttributeModifier.Operation.class, slotGroup - ).newInstance( - NamespacedKey.fromString(modifierName), + final NamespacedKey modifierKey = Objects.requireNonNull(NamespacedKey.fromString(modifierName), + "Modifier key returned null"); + final Constructor constructor = AttributeModifier.class.getDeclaredConstructor( + NamespacedKey.class, double.class, AttributeModifier.Operation.class, slotGroup); + return constructor.newInstance( + modifierKey, modifier.amount(), AttributeModifier.Operation.values()[modifier.operationType()], slot == null ? slotGroup.getField(EQUIPMENT_SLOT_GROUP$ANY).get(null) diff --git a/common/src/main/java/net/william278/husksync/config/Settings.java b/common/src/main/java/net/william278/husksync/config/Settings.java index 67ab01e9..f9b316b6 100644 --- a/common/src/main/java/net/william278/husksync/config/Settings.java +++ b/common/src/main/java/net/william278/husksync/config/Settings.java @@ -64,7 +64,7 @@ public class Settings { private boolean checkForUpdates = true; @Comment("Specify a common ID for grouping servers running HuskSync. " - + "Don't modify this unless you know what you're doing!") + + "Don't modify this unless you know what you're doing!") private String clusterId = ""; @Comment("Enable development debug logging") @@ -229,7 +229,7 @@ public class Settings { private boolean enabled = false; @Comment("What items to save in death snapshots? (DROPS or ITEMS_TO_KEEP). " - + "Note that ITEMS_TO_KEEP (suggested for keepInventory servers) requires a Paper 1.19.4+ server.") + + "Note that ITEMS_TO_KEEP (suggested for keepInventory servers) requires a Paper 1.19.4+ server.") private DeathItemsMode itemsToSave = DeathItemsMode.DROPS; @Comment("Should a death snapshot still be created even if the items to save on the player's death are empty?") @@ -257,7 +257,7 @@ public class Settings { private boolean persistLockedMaps = true; @Comment("If using the DELAY sync method, how long should this server listen for Redis key data updates before " - + "pulling data from the database instead (i.e., if the user did not change servers).") + + "pulling data from the database instead (i.e., if the user did not change servers).") private int networkLatencyMilliseconds = 500; @Comment({"Which data types to synchronize.", "Docs: https://william278.net/docs/husksync/sync-features"}) @@ -267,10 +267,43 @@ public class Settings { @Comment("Commands which should be blocked before a player has finished syncing (Use * to block all commands)") private List blacklistedCommandsWhileLocked = new ArrayList<>(List.of("*")); - @Comment({"For attribute syncing, which attributes should be ignored/skipped when syncing", - "(e.g. ['minecraft:generic.max_health', 'minecraft:generic.attack_damage'])"}) - @Getter(AccessLevel.NONE) - private List ignoredAttributes = new ArrayList<>(List.of("")); + @Comment("Configuration for how to sync attributes") + private AttributeSettings attributes = new AttributeSettings(); + + @Getter + @Configuration + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class AttributeSettings { + + @Comment({"Which attributes should not be saved when syncing users. Supports wildcard matching.", + "(e.g. ['minecraft:generic.max_health', 'minecraft:generic.*'])"}) + @Getter(AccessLevel.NONE) + private List ignoredAttributes = new ArrayList<>(List.of("")); + + @Comment({"Which modifiers should not be saved when syncing users. Supports wildcard matching.", + "(e.g. ['minecraft:effect.speed', 'minecraft:effect.*'])"}) + @Getter(AccessLevel.NONE) + private List ignoredModifiers = new ArrayList<>(List.of("minecraft:effect.*")); + + private boolean matchesWildcard(@NotNull String pat, @NotNull String value) { + if (!pat.contains(":")) { + pat = "minecraft:%s".formatted(pat); + } + if (!value.contains(":")) { + value = "minecraft:%s".formatted(value); + } + return pat.contains("*") ? value.matches(pat.replace("*", ".*")) : pat.equals(value); + } + + public boolean isIgnoredAttribute(@NotNull String attribute) { + return ignoredAttributes.stream().anyMatch(wildcard -> matchesWildcard(wildcard, attribute)); + } + + public boolean isIgnoredModifier(@NotNull String modifier) { + return ignoredModifiers.stream().anyMatch(wildcard -> matchesWildcard(wildcard, modifier)); + } + + } @Comment("Event priorities for listeners (HIGHEST, NORMAL, LOWEST). Change if you encounter plugin conflicts") @Getter(AccessLevel.NONE) @@ -284,10 +317,6 @@ public class Settings { return id.isCustom() || features.getOrDefault(id.getKeyValue(), id.isEnabledByDefault()); } - public boolean isIgnoredAttribute(@NotNull String attribute) { - return ignoredAttributes.contains(attribute); - } - @NotNull public EventListener.Priority getEventPriority(@NotNull EventListener.ListenerType type) { try {