diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 0060a83..05dc59d 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -14,14 +14,14 @@ jobs: steps: - uses: actions/checkout@v6.0.2 - - name: Set up JDK 21 + - name: Set up JDK 25 uses: actions/setup-java@v5.2.0 with: - java-version: "21" + java-version: "25" distribution: "microsoft" - name: Setup Gradle - uses: gradle/actions/setup-gradle@v6 + uses: gradle/actions/setup-gradle@v6.1.0 with: gradle-version: current - name: Build with Gradle Wrapper diff --git a/README.md b/README.md index 9f6fb0c..52e7e45 100644 --- a/README.md +++ b/README.md @@ -49,18 +49,18 @@ worlds: ## Build -Requires Java 21+. +Requires Java 25+. ```bash ./gradlew build ``` -Jar will be in `build/libs/AntiDupingMechanics-1.1.0.jar`. +Jar will be in `build/libs/AntiDupingMechanics-1.2.0.jar`. ## Notes -Bundle behavior in Minecraft can vary across versions/clients. This plugin blocks the common insert methods: +Bundle behaviour in Minecraft can vary across versions/clients. This plugin blocks the common insert methods: - click/drag items onto a bundle in inventories - certain right-click interactions involving item entities (where applicable) -If you find a specific interaction path that still inserts into bundles on your server, note the exact steps and we can tighten the checks. +If you find a specific interaction path that still inserts into bundles on your server, note the exact steps, and we can tighten the checks. diff --git a/build.gradle b/build.gradle index c229623..2eca015 100644 --- a/build.gradle +++ b/build.gradle @@ -1,13 +1,16 @@ +import org.apache.tools.ant.filters.ReplaceTokens + plugins { id 'java' + id("io.github.greymagic27.McTestServer") version "1.0.0" } group = 'com.mrfloris' -version = '1.1.0' +version = '26.1.2' java { toolchain { - languageVersion = JavaLanguageVersion.of(21) + languageVersion = JavaLanguageVersion.of(25) } } @@ -17,11 +20,22 @@ repositories { } dependencies { - compileOnly("io.papermc.paper:paper-api:1.21.11-R0.1-SNAPSHOT") + compileOnly("io.papermc.paper:paper-api:${version}.build.+") implementation("org.jspecify:jspecify:1.0.0") } +processResources { + from(sourceSets.main.resources.srcDirs) { + filter ReplaceTokens, tokens: [version: version] + duplicatesStrategy = DuplicatesStrategy.WARN + } +} + tasks.withType(JavaCompile).configureEach { options.encoding = 'UTF-8' options.compilerArgs += ['-Xlint:deprecation'] } + +tasks.jar { + archiveVersion.set("${version}") +} diff --git a/src/main/java/com/mrfloris/antiduping/AntiDupingCommand.java b/src/main/java/com/mrfloris/antiduping/AntiDupingCommand.java index 223eb85..cb7d2a9 100644 --- a/src/main/java/com/mrfloris/antiduping/AntiDupingCommand.java +++ b/src/main/java/com/mrfloris/antiduping/AntiDupingCommand.java @@ -1,8 +1,8 @@ package com.mrfloris.antiduping; +import com.mrfloris.antiduping.util.ColorUtil; import java.util.ArrayList; import java.util.List; -import org.bukkit.ChatColor; import org.bukkit.command.Command; import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandSender; @@ -19,37 +19,32 @@ public AntiDupingCommand(AntiDupingMechanics plugin, AntiDupingListener listener this.listener = listener; } - private String color(String s) { - return ChatColor.translateAlternateColorCodes('&', s); - } - @Override public boolean onCommand(@NonNull CommandSender sender, @NonNull Command command, @NonNull String label, @NonNull String @NonNull [] args) { if (!sender.hasPermission("antiduping.admin")) { - sender.sendMessage(color("&cNo permission.")); + sender.sendMessage(ColorUtil.color("&cNo permission.")); return true; } if (args.length == 0) { - sender.sendMessage(color("&7Usage: &f/antiduping ")); + sender.sendMessage(ColorUtil.color("&7Usage: &f/antiduping ")); return true; } switch (args[0].toLowerCase()) { case "reload" -> { plugin.reloadAll(); - sender.sendMessage(color("&aAntiDupingMechanics reloaded.")); + sender.sendMessage(ColorUtil.color("&aAntiDupingMechanics reloaded.")); } case "status" -> { - sender.sendMessage(color("&6AntiDupingMechanics status:")); - sender.sendMessage(color("&7- Debug: &f" + listener.statusDebug())); - sender.sendMessage(color("&7- Worlds loaded: &f" + listener.statusWorldsLoaded())); - sender.sendMessage(color("&7- Bypass perm: &fantiduping.bypass")); - sender.sendMessage(color("&7Use /antiduping reload after config changes.")); + sender.sendMessage(ColorUtil.color("&6AntiDupingMechanics status:")); + sender.sendMessage(ColorUtil.color("&7- Debug: &f" + listener.statusDebug())); + sender.sendMessage(ColorUtil.color("&7- Worlds loaded: &f" + listener.statusWorldsLoaded())); + sender.sendMessage(ColorUtil.color("&7- Bypass perm: &fantiduping.bypass")); + sender.sendMessage(ColorUtil.color("&7Use /antiduping reload after config changes.")); } - default -> sender.sendMessage(color("&7Usage: &f/antiduping ")); + default -> sender.sendMessage(ColorUtil.color("&7Usage: &f/antiduping ")); } - return true; } diff --git a/src/main/java/com/mrfloris/antiduping/AntiDupingListener.java b/src/main/java/com/mrfloris/antiduping/AntiDupingListener.java index 2cb2512..d86a63f 100644 --- a/src/main/java/com/mrfloris/antiduping/AntiDupingListener.java +++ b/src/main/java/com/mrfloris/antiduping/AntiDupingListener.java @@ -1,9 +1,12 @@ package com.mrfloris.antiduping; +import com.mrfloris.antiduping.settings.world.WorldSettings; import java.util.HashMap; import java.util.Map; +import java.util.Objects; +import net.kyori.adventure.text.TextComponent; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import org.bukkit.Bukkit; -import org.bukkit.ChatColor; import org.bukkit.Material; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.entity.ChestBoat; @@ -31,7 +34,7 @@ public final class AntiDupingListener implements Listener { private final Map perWorld = new HashMap<>(); private boolean debugEnabled; private boolean debugToConsole; - private String msgPrefix; + private TextComponent msgPrefix; private WorldSettings defaultSettings; public AntiDupingListener(AntiDupingMechanics plugin) { @@ -39,33 +42,23 @@ public AntiDupingListener(AntiDupingMechanics plugin) { reloadFromConfig(); } - private static @NonNull String colorStatic(String s) { - return ChatColor.translateAlternateColorCodes('&', s == null ? "" : s); - } - public void reloadFromConfig() { var c = plugin.getConfig(); - debugEnabled = c.getBoolean("debug.enabled", false); debugToConsole = c.getBoolean("debug.log_to_console", true); msgPrefix = color(c.getString("messages.prefix", "&8[&6AntiDuping&8] &r")); - perWorld.clear(); - ConfigurationSection worlds = c.getConfigurationSection("worlds"); ConfigurationSection def = worlds != null ? worlds.getConfigurationSection("__default__") : null; defaultSettings = WorldSettings.fromConfig(def, new WorldSettings()); // baseline defaults if missing - if (worlds != null) { for (String key : worlds.getKeys(false)) { if (key == null) continue; if (key.equalsIgnoreCase("__default__")) continue; - ConfigurationSection worldSec = worlds.getConfigurationSection(key); if (worldSec == null) continue; - // merge: start from defaultSettings, then override - WorldSettings ws = defaultSettings.copy(); + WorldSettings ws = Objects.requireNonNull(defaultSettings).copy(); ws.applyOverrides(worldSec); perWorld.put(key.toLowerCase(), ws); } @@ -85,19 +78,20 @@ private WorldSettings settingsForWorld(String worldName) { return perWorld.getOrDefault(worldName.toLowerCase(), defaultSettings); } - private @NonNull String color(String s) { - return ChatColor.translateAlternateColorCodes('&', s == null ? "" : s); + private @NonNull TextComponent color(String s) { + return LegacyComponentSerializer.legacyAmpersand().deserialize(s); } - private void msg(Player p, String message) { - if (message != null && !message.isBlank()) { - p.sendMessage(msgPrefix + message); + private void msg(Player p, TextComponent message) { + if (message != null) { + p.sendMessage(msgPrefix.append(message)); } } private void debug(String line) { if (!debugEnabled) return; if (debugToConsole) { + //noinspection UnstableApiUsage Bukkit.getLogger().info("[AntiDupingMechanics] " + line); } } @@ -147,10 +141,8 @@ public void onInventoryOpen(@NonNull InventoryOpenEvent event) { public void onInteractEntity(@NonNull PlayerInteractEntityEvent event) { Player player = event.getPlayer(); if (bypass(player)) return; - WorldSettings ws = settingsForWorld(player.getWorld().getName()); Entity clicked = event.getRightClicked(); - if (ws.donkeys.enabled && ws.donkeys.blockAttachChest) { if (clicked instanceof ChestedHorse chestedHorse) { ItemStack inHand = player.getInventory().getItem(event.getHand()); @@ -182,13 +174,10 @@ public void onInteractEntity(@NonNull PlayerInteractEntityEvent event) { public void onInventoryClick(@NonNull InventoryClickEvent event) { if (!(event.getWhoClicked() instanceof Player player)) return; if (bypass(player)) return; - WorldSettings ws = settingsForWorld(player.getWorld().getName()); if (!ws.bundles.enabled || !ws.bundles.blockInsertItems) return; - ItemStack current = event.getCurrentItem(); // clicked slot item ItemStack cursor = event.getCursor(); // cursor item - // Putting a cursor item onto a bundle (insert) if (isBundle(current) && cursor.getType() != Material.AIR) { event.setCancelled(true); @@ -196,8 +185,7 @@ public void onInventoryClick(@NonNull InventoryClickEvent event) { debug("Blocked bundle insert via click (cursor -> bundle): player=" + player.getName() + ", world=" + player.getWorld().getName() + ", cursor=" + cursor.getType()); return; } - - // Risky swap case: bundle on cursor + clicking a non-air item (could insert depending on client behavior) + // Risky swap case: bundle on cursor + clicking a non-air item (could insert depending on client behaviour) if (isBundle(cursor) && current != null && current.getType() != Material.AIR) { event.setCancelled(true); msg(player, ws.bundles.message); @@ -209,13 +197,10 @@ public void onInventoryClick(@NonNull InventoryClickEvent event) { public void onInventoryDrag(@NonNull InventoryDragEvent event) { if (!(event.getWhoClicked() instanceof Player player)) return; if (bypass(player)) return; - WorldSettings ws = settingsForWorld(player.getWorld().getName()); if (!ws.bundles.enabled || !ws.bundles.blockInsertItems) return; - ItemStack cursor = event.getOldCursor(); if (cursor.getType() == Material.AIR) return; - for (int rawSlot : event.getRawSlots()) { ItemStack inSlot = event.getView().getItem(rawSlot); if (isBundle(inSlot)) { @@ -226,101 +211,4 @@ public void onInventoryDrag(@NonNull InventoryDragEvent event) { } } } - - // --- Settings types --- - static final class WorldSettings { - ChestBoatSettings chestBoats = new ChestBoatSettings(); - DonkeySettings donkeys = new DonkeySettings(); - BundleSettings bundles = new BundleSettings(); - - static @NonNull WorldSettings fromConfig(ConfigurationSection sec, WorldSettings fallback) { - WorldSettings ws = (fallback == null ? new WorldSettings() : fallback.copy()); - if (sec == null) return ws; - ws.applyOverrides(sec); - return ws; - } - - private static @NonNull String colorLocal(String s) { - return ChatColor.translateAlternateColorCodes('&', s == null ? "" : s); - } - - void applyOverrides(@NonNull ConfigurationSection sec) { - // chest_boats - ConfigurationSection cb = sec.getConfigurationSection("chest_boats"); - if (cb != null) { - if (cb.contains("enabled")) chestBoats.enabled = cb.getBoolean("enabled"); - if (cb.contains("block_open_inventory")) chestBoats.blockOpenInventory = cb.getBoolean("block_open_inventory"); - if (cb.contains("message")) chestBoats.message = colorLocal(cb.getString("message")); - } - - // donkeys - ConfigurationSection dk = sec.getConfigurationSection("donkeys"); - if (dk != null) { - if (dk.contains("enabled")) donkeys.enabled = dk.getBoolean("enabled"); - if (dk.contains("block_open_inventory")) donkeys.blockOpenInventory = dk.getBoolean("block_open_inventory"); - if (dk.contains("block_attach_chest")) donkeys.blockAttachChest = dk.getBoolean("block_attach_chest"); - if (dk.contains("message")) donkeys.message = colorLocal(dk.getString("message")); - } - - // bundles - ConfigurationSection bd = sec.getConfigurationSection("bundles"); - if (bd != null) { - if (bd.contains("enabled")) bundles.enabled = bd.getBoolean("enabled"); - if (bd.contains("block_insert_items")) bundles.blockInsertItems = bd.getBoolean("block_insert_items"); - if (bd.contains("message")) bundles.message = colorLocal(bd.getString("message")); - } - } - - @NonNull WorldSettings copy() { - WorldSettings ws = new WorldSettings(); - ws.chestBoats = chestBoats.copy(); - ws.donkeys = donkeys.copy(); - ws.bundles = bundles.copy(); - return ws; - } - } - - static final class ChestBoatSettings { - boolean enabled = true; - boolean blockOpenInventory = true; - String message = colorStatic("&cYou can't use chest storage in chest boats on this server."); - - @NonNull ChestBoatSettings copy() { - ChestBoatSettings s = new ChestBoatSettings(); - s.enabled = enabled; - s.blockOpenInventory = blockOpenInventory; - s.message = message; - return s; - } - } - - static final class DonkeySettings { - boolean enabled = true; - boolean blockOpenInventory = true; - boolean blockAttachChest = true; - String message = colorStatic("&cYou can't use chest storage on pack animals on this server."); - - @NonNull DonkeySettings copy() { - DonkeySettings s = new DonkeySettings(); - s.enabled = enabled; - s.blockOpenInventory = blockOpenInventory; - s.blockAttachChest = blockAttachChest; - s.message = message; - return s; - } - } - - static final class BundleSettings { - boolean enabled = true; - boolean blockInsertItems = true; - String message = colorStatic("&cYou can't put items into bundles on this server."); - - @NonNull BundleSettings copy() { - BundleSettings s = new BundleSettings(); - s.enabled = enabled; - s.blockInsertItems = blockInsertItems; - s.message = message; - return s; - } - } } diff --git a/src/main/java/com/mrfloris/antiduping/AntiDupingMechanics.java b/src/main/java/com/mrfloris/antiduping/AntiDupingMechanics.java index 5dc0562..da09b00 100644 --- a/src/main/java/com/mrfloris/antiduping/AntiDupingMechanics.java +++ b/src/main/java/com/mrfloris/antiduping/AntiDupingMechanics.java @@ -9,10 +9,8 @@ public final class AntiDupingMechanics extends JavaPlugin { @Override public void onEnable() { saveDefaultConfig(); - listener = new AntiDupingListener(this); getServer().getPluginManager().registerEvents(listener, this); - var cmd = getCommand("antiduping"); if (cmd != null) { var command = new AntiDupingCommand(this, listener); diff --git a/src/main/java/com/mrfloris/antiduping/settings/entity/BundleSettings.java b/src/main/java/com/mrfloris/antiduping/settings/entity/BundleSettings.java new file mode 100644 index 0000000..14da9c3 --- /dev/null +++ b/src/main/java/com/mrfloris/antiduping/settings/entity/BundleSettings.java @@ -0,0 +1,18 @@ +package com.mrfloris.antiduping.settings.entity; + +import com.mrfloris.antiduping.util.ColorUtil; +import net.kyori.adventure.text.TextComponent; + +public class BundleSettings { + public boolean enabled = true; + public boolean blockInsertItems = true; + public TextComponent message = ColorUtil.color("&cYou can't put items into bundles on this server."); + + public BundleSettings copy() { + BundleSettings s = new BundleSettings(); + s.enabled = enabled; + s.blockInsertItems = blockInsertItems; + s.message = message; + return s; + } +} diff --git a/src/main/java/com/mrfloris/antiduping/settings/entity/ChestBoatSettings.java b/src/main/java/com/mrfloris/antiduping/settings/entity/ChestBoatSettings.java new file mode 100644 index 0000000..f25d78c --- /dev/null +++ b/src/main/java/com/mrfloris/antiduping/settings/entity/ChestBoatSettings.java @@ -0,0 +1,18 @@ +package com.mrfloris.antiduping.settings.entity; + +import com.mrfloris.antiduping.util.ColorUtil; +import net.kyori.adventure.text.TextComponent; + +public class ChestBoatSettings { + public boolean enabled = true; + public boolean blockOpenInventory = true; + public TextComponent message = ColorUtil.color("&cYou can't use chest storage in chest boats on this server."); + + public ChestBoatSettings copy() { + ChestBoatSettings s = new ChestBoatSettings(); + s.enabled = enabled; + s.blockOpenInventory = blockOpenInventory; + s.message = message; + return s; + } +} diff --git a/src/main/java/com/mrfloris/antiduping/settings/entity/DonkeySettings.java b/src/main/java/com/mrfloris/antiduping/settings/entity/DonkeySettings.java new file mode 100644 index 0000000..31dbda6 --- /dev/null +++ b/src/main/java/com/mrfloris/antiduping/settings/entity/DonkeySettings.java @@ -0,0 +1,20 @@ +package com.mrfloris.antiduping.settings.entity; + +import com.mrfloris.antiduping.util.ColorUtil; +import net.kyori.adventure.text.TextComponent; + +public class DonkeySettings { + public boolean enabled = true; + public boolean blockOpenInventory = true; + public boolean blockAttachChest = true; + public TextComponent message = ColorUtil.color("&cYou can't use chest storage on pack animals on this server."); + + public DonkeySettings copy() { + DonkeySettings s = new DonkeySettings(); + s.enabled = enabled; + s.blockOpenInventory = blockOpenInventory; + s.blockAttachChest = blockAttachChest; + s.message = message; + return s; + } +} diff --git a/src/main/java/com/mrfloris/antiduping/settings/world/WorldSettings.java b/src/main/java/com/mrfloris/antiduping/settings/world/WorldSettings.java new file mode 100644 index 0000000..9c2d0bd --- /dev/null +++ b/src/main/java/com/mrfloris/antiduping/settings/world/WorldSettings.java @@ -0,0 +1,54 @@ +package com.mrfloris.antiduping.settings.world; + +import com.mrfloris.antiduping.util.ColorUtil; +import com.mrfloris.antiduping.settings.entity.BundleSettings; +import com.mrfloris.antiduping.settings.entity.ChestBoatSettings; +import com.mrfloris.antiduping.settings.entity.DonkeySettings; +import org.bukkit.configuration.ConfigurationSection; +import org.jspecify.annotations.NonNull; + +public class WorldSettings { + public ChestBoatSettings chestBoats = new ChestBoatSettings(); + public DonkeySettings donkeys = new DonkeySettings(); + public BundleSettings bundles = new BundleSettings(); + + public static WorldSettings fromConfig(ConfigurationSection sec, WorldSettings fallback) { + WorldSettings ws = (fallback == null ? new WorldSettings() : fallback.copy()); + if (sec == null) return ws; + ws.applyOverrides(sec); + return ws; + } + + public void applyOverrides(@NonNull ConfigurationSection sec) { + // chest_boats + ConfigurationSection cb = sec.getConfigurationSection("chest_boats"); + if (cb != null) { + if (cb.contains("enabled")) chestBoats.enabled = cb.getBoolean("enabled"); + if (cb.contains("block_open_inventory")) chestBoats.blockOpenInventory = cb.getBoolean("block_open_inventory"); + if (cb.contains("message")) chestBoats.message = ColorUtil.color(cb.getString("message")); + } + // donkeys + ConfigurationSection dk = sec.getConfigurationSection("donkeys"); + if (dk != null) { + if (dk.contains("enabled")) donkeys.enabled = dk.getBoolean("enabled"); + if (dk.contains("block_open_inventory")) donkeys.blockOpenInventory = dk.getBoolean("block_open_inventory"); + if (dk.contains("block_attach_chest")) donkeys.blockAttachChest = dk.getBoolean("block_attach_chest"); + if (dk.contains("message")) donkeys.message = ColorUtil.color(dk.getString("message")); + } + // bundles + ConfigurationSection bd = sec.getConfigurationSection("bundles"); + if (bd != null) { + if (bd.contains("enabled")) bundles.enabled = bd.getBoolean("enabled"); + if (bd.contains("block_insert_items")) bundles.blockInsertItems = bd.getBoolean("block_insert_items"); + if (bd.contains("message")) bundles.message = ColorUtil.color(bd.getString("message")); + } + } + + public WorldSettings copy() { + WorldSettings ws = new WorldSettings(); + ws.chestBoats = chestBoats.copy(); + ws.donkeys = donkeys.copy(); + ws.bundles = bundles.copy(); + return ws; + } +} diff --git a/src/main/java/com/mrfloris/antiduping/util/ColorUtil.java b/src/main/java/com/mrfloris/antiduping/util/ColorUtil.java new file mode 100644 index 0000000..22d2083 --- /dev/null +++ b/src/main/java/com/mrfloris/antiduping/util/ColorUtil.java @@ -0,0 +1,12 @@ +package com.mrfloris.antiduping.util; + +import net.kyori.adventure.text.TextComponent; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; +import org.jspecify.annotations.NonNull; + +public class ColorUtil { + + public static @NonNull TextComponent color(String s) { + return LegacyComponentSerializer.legacyAmpersand().deserialize(s); + } +} diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 03c8149..9e4116b 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,7 +1,7 @@ -name: AntiDupingMechanics -version: ${version} -main: com.example.antiduping.AntiDupingMechanics -api-version: "1.21.11" +name: 1MB-AntiDupingMechanics +version: @version@ +main: com.mrfloris.antiduping.AntiDupingMechanics +api-version: "26.1.2" author: Floris commands: