From 37cf541139f9649d56d355e0800ed5654af2e6ea Mon Sep 17 00:00:00 2001 From: 1robie <97293924+1robie@users.noreply.github.com> Date: Tue, 14 Jul 2026 17:14:19 +0200 Subject: [PATCH 1/6] fix: ensure color values are clamped in ColorUtils for proper range handling + fix potential NPE --- .../java/fr/maxlego08/menu/api/utils/ColorUtils.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/API/src/main/java/fr/maxlego08/menu/api/utils/ColorUtils.java b/API/src/main/java/fr/maxlego08/menu/api/utils/ColorUtils.java index befaa266..a93b813d 100644 --- a/API/src/main/java/fr/maxlego08/menu/api/utils/ColorUtils.java +++ b/API/src/main/java/fr/maxlego08/menu/api/utils/ColorUtils.java @@ -15,10 +15,10 @@ private ColorUtils() { public static Color parse(@NotNull Object obj) { switch (obj) { case Float[] floatsArray when (floatsArray.length == 3 || floatsArray.length == 4) -> { - return getColor(floatsArray[0] * 255, floatsArray[1] * 255, floatsArray[2] * 255, floatsArray.length == 3, floatsArray[3] * 255); + return getColor(floatsArray[0] * 255, floatsArray[1] * 255, floatsArray[2] * 255, floatsArray.length == 3, floatsArray.length == 4 ? floatsArray[3] * 255 : 0); } case Double[] doublesArray when (doublesArray.length == 3 || doublesArray.length == 4) -> { - return getColor(doublesArray[0] * 255, doublesArray[1] * 255, doublesArray[2] * 255, doublesArray.length == 3, doublesArray[3] * 255); + return getColor(doublesArray[0] * 255, doublesArray[1] * 255, doublesArray[2] * 255, doublesArray.length == 3, doublesArray.length == 4 ? doublesArray[3] * 255 : 0); } case List list when (list.size() == 3 || list.size() == 4) -> { try { @@ -114,14 +114,14 @@ private static int clamp(int value) { @NotNull private static Color getColor(double v, double v2, double v3, boolean b2, double v4) { - int r = (int) (v); - int g = (int) (v2); - int b = (int) (v3); + int r = clamp((int) (v)); + int g = clamp((int) (v2)); + int b = clamp((int) (v3)); if (b2) { // RGB return Color.fromRGB(r, g, b); } else { // RGBA - int a = (int) (v4); + int a = clamp((int) (v4)); return Color.fromARGB(a, r, g, b); } } From 630e90f93faeb6f9da0d28148f71d3c9fa3e63b8 Mon Sep 17 00:00:00 2001 From: 1robie <97293924+1robie@users.noreply.github.com> Date: Tue, 14 Jul 2026 17:14:48 +0200 Subject: [PATCH 2/6] fix: improve cooldown management by cleaning up expired entries and handling empty cooldown maps --- .../menu/mechanics/onclick/OnClickMechanicListener.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/java/fr/maxlego08/menu/mechanics/onclick/OnClickMechanicListener.java b/src/main/java/fr/maxlego08/menu/mechanics/onclick/OnClickMechanicListener.java index 3b2257d5..3bee9fde 100644 --- a/src/main/java/fr/maxlego08/menu/mechanics/onclick/OnClickMechanicListener.java +++ b/src/main/java/fr/maxlego08/menu/mechanics/onclick/OnClickMechanicListener.java @@ -92,8 +92,14 @@ private boolean isOnCooldown(String itemId, UUID playerUuid, int cooldownSeconds if (cooldownSeconds <= 0) return false; Map playerCooldowns = this.cooldowns.get(itemId); if (playerCooldowns == null) return false; + long now = System.currentTimeMillis(); + playerCooldowns.entrySet().removeIf(entry -> entry.getValue() <= now); + if (playerCooldowns.isEmpty()) { + this.cooldowns.remove(itemId); + return false; + } Long expiry = playerCooldowns.get(playerUuid); - return expiry != null && System.currentTimeMillis() < expiry; + return expiry != null && now < expiry; } private void setCooldown(String itemId, UUID playerUuid, int cooldownSeconds) { From 5ccd4026a59da9f4ba586fada335d686d8600177 Mon Sep 17 00:00:00 2001 From: 1robie <97293924+1robie@users.noreply.github.com> Date: Fri, 17 Jul 2026 17:42:39 +0200 Subject: [PATCH 3/6] fix: limit inventory processing to the first 36 slots and clear items after cloning --- .../java/fr/maxlego08/menu/inventory/zinv/ZInventory.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Common/src/main/java/fr/maxlego08/menu/inventory/zinv/ZInventory.java b/Common/src/main/java/fr/maxlego08/menu/inventory/zinv/ZInventory.java index 1ee5f023..a910f580 100644 --- a/Common/src/main/java/fr/maxlego08/menu/inventory/zinv/ZInventory.java +++ b/Common/src/main/java/fr/maxlego08/menu/inventory/zinv/ZInventory.java @@ -279,11 +279,13 @@ private List collectSessionItems(Player player, InventoryEngine inven List sessionItems = new ArrayList<>(); var playerInventory = player.getInventory(); - for (int slot = 0; slot < playerInventory.getSize(); slot++) { + int storageSize = Math.min(playerInventory.getSize(), 36); + for (int slot = 0; slot < storageSize; slot++) { if (buttonSlots.contains(slot)) continue; ItemStack item = playerInventory.getItem(slot); if (item != null && !item.getType().isAir()) { sessionItems.add(item.clone()); + playerInventory.setItem(slot, null); } } return sessionItems; From dbe67a7539b5f0c590861bc29a188c26454ad20f Mon Sep 17 00:00:00 2001 From: 1robie <97293924+1robie@users.noreply.github.com> Date: Sat, 18 Jul 2026 09:51:47 +0200 Subject: [PATCH 4/6] feat: add support for first world join items and track visited worlds --- .../menu/api/storage/StorageManager.java | 6 ++++ .../fr/maxlego08/menu/api/storage/Tables.java | 1 + .../api/storage/dto/FirstWorldJoinDTO.java | 6 ++++ .../mechanics/itemjoin/ItemJoinMechanic.java | 18 ++++++++++++ .../itemjoin/ItemJoinMechanicListener.java | 26 +++++++++++++++++ .../menu/storage/ZStorageManager.java | 28 ++++++++++++++++--- .../migrations/FirstWorldJoinMigration.java | 15 ++++++++++ 7 files changed, 96 insertions(+), 4 deletions(-) create mode 100644 API/src/main/java/fr/maxlego08/menu/api/storage/dto/FirstWorldJoinDTO.java create mode 100644 src/main/java/fr/maxlego08/menu/storage/migrations/FirstWorldJoinMigration.java diff --git a/API/src/main/java/fr/maxlego08/menu/api/storage/StorageManager.java b/API/src/main/java/fr/maxlego08/menu/api/storage/StorageManager.java index 972316b7..3f8e8c26 100644 --- a/API/src/main/java/fr/maxlego08/menu/api/storage/StorageManager.java +++ b/API/src/main/java/fr/maxlego08/menu/api/storage/StorageManager.java @@ -8,6 +8,7 @@ import org.jetbrains.annotations.NotNull; import java.util.List; +import java.util.Set; import java.util.UUID; public interface StorageManager extends Listener { @@ -35,4 +36,9 @@ public interface StorageManager extends Listener { void storeInventory(@NotNull UUID uuid,@NotNull InventoryPlayer inventoryPlayer); void removeInventory(@NotNull UUID uuid); + + @NotNull + Set getVisitedWorlds(@NotNull UUID playerId); + + void markWorldVisited(@NotNull UUID playerId, @NotNull String worldName); } diff --git a/API/src/main/java/fr/maxlego08/menu/api/storage/Tables.java b/API/src/main/java/fr/maxlego08/menu/api/storage/Tables.java index b6e1eef0..2791f346 100644 --- a/API/src/main/java/fr/maxlego08/menu/api/storage/Tables.java +++ b/API/src/main/java/fr/maxlego08/menu/api/storage/Tables.java @@ -10,5 +10,6 @@ public interface Tables { @NotNull String PLAYER_OPEN_INVENTORIES = "%prefix%player_open_inventories"; @NotNull String PLAYER_DATAS = "%prefix%player_datas"; @NotNull String PLAYER_INVENTORIES = "%prefix%player_inventories"; + @NotNull String FIRST_WORLD_JOIN = "%prefix%first_world_join"; } diff --git a/API/src/main/java/fr/maxlego08/menu/api/storage/dto/FirstWorldJoinDTO.java b/API/src/main/java/fr/maxlego08/menu/api/storage/dto/FirstWorldJoinDTO.java new file mode 100644 index 00000000..bc68dde3 --- /dev/null +++ b/API/src/main/java/fr/maxlego08/menu/api/storage/dto/FirstWorldJoinDTO.java @@ -0,0 +1,6 @@ +package fr.maxlego08.menu.api.storage.dto; + +import java.util.UUID; + +public record FirstWorldJoinDTO(UUID playerId, String worldName) { +} diff --git a/src/main/java/fr/maxlego08/menu/mechanics/itemjoin/ItemJoinMechanic.java b/src/main/java/fr/maxlego08/menu/mechanics/itemjoin/ItemJoinMechanic.java index a5e3e656..f574b423 100644 --- a/src/main/java/fr/maxlego08/menu/mechanics/itemjoin/ItemJoinMechanic.java +++ b/src/main/java/fr/maxlego08/menu/mechanics/itemjoin/ItemJoinMechanic.java @@ -3,16 +3,26 @@ import fr.maxlego08.menu.api.mechanic.Mechanic; import org.bukkit.configuration.ConfigurationSection; +import java.util.Collections; +import java.util.List; import java.util.OptionalInt; public class ItemJoinMechanic extends Mechanic { private final boolean grantOnFirstJoin; + private final List firstWorldJoinWorlds; private final boolean preventInventoryChanges; private final OptionalInt fixedSlot; public ItemJoinMechanic(String itemId, ItemJoinMechanicFactory itemJoinMechanicFactory, ConfigurationSection mechanicSection) { super(itemId, itemJoinMechanicFactory, mechanicSection); this.grantOnFirstJoin = mechanicSection.getBoolean("give-first-join", false); + if (mechanicSection.isList("give-first-world-join")) { + this.firstWorldJoinWorlds = mechanicSection.getStringList("give-first-world-join"); + } else if (mechanicSection.isString("give-first-world-join")) { + this.firstWorldJoinWorlds = Collections.singletonList(mechanicSection.getString("give-first-world-join")); + } else { + this.firstWorldJoinWorlds = Collections.emptyList(); + } this.preventInventoryChanges = mechanicSection.getBoolean("prevent-inventory-modification", true); int slot = mechanicSection.getInt("fixed-slot", -1); this.fixedSlot = slot >= 0 && slot <= 36 ? OptionalInt.of(slot) : OptionalInt.empty(); @@ -22,6 +32,14 @@ public boolean shouldGrantOnFirstJoin() { return this.grantOnFirstJoin; } + public List getFirstWorldJoinWorlds() { + return this.firstWorldJoinWorlds; + } + + public boolean hasFirstWorldJoinWorlds() { + return !this.firstWorldJoinWorlds.isEmpty(); + } + public boolean preventsInventoryChanges() { return this.preventInventoryChanges; } diff --git a/src/main/java/fr/maxlego08/menu/mechanics/itemjoin/ItemJoinMechanicListener.java b/src/main/java/fr/maxlego08/menu/mechanics/itemjoin/ItemJoinMechanicListener.java index f16ce28d..24b3211e 100644 --- a/src/main/java/fr/maxlego08/menu/mechanics/itemjoin/ItemJoinMechanicListener.java +++ b/src/main/java/fr/maxlego08/menu/mechanics/itemjoin/ItemJoinMechanicListener.java @@ -10,6 +10,7 @@ import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.inventory.InventoryDragEvent; import org.bukkit.event.inventory.InventoryType; +import org.bukkit.event.player.PlayerChangedWorldEvent; import org.bukkit.event.player.PlayerDropItemEvent; import org.bukkit.event.player.PlayerInteractEntityEvent; import org.bukkit.event.player.PlayerJoinEvent; @@ -19,14 +20,17 @@ import java.util.Map; import java.util.Optional; +import java.util.Set; public class ItemJoinMechanicListener extends MechanicListener { private final ItemJoinMechanicFactory itemJoinMechanicFactory; private final ItemManager itemManager; + private final MenuPlugin plugin; public ItemJoinMechanicListener(ItemJoinMechanicFactory itemJoinMechanicFactory, MenuPlugin plugin) { this.itemJoinMechanicFactory = itemJoinMechanicFactory; this.itemManager = plugin.getItemManager(); + this.plugin = plugin; } /** @@ -61,6 +65,7 @@ private boolean isProtectedItem(ItemStack item) { @EventHandler public void onConnect(PlayerJoinEvent event) { Player player = event.getPlayer(); + if (!player.hasPlayedBefore()) { for (Map.Entry entry : this.itemJoinMechanicFactory.getAllMechanics()) { ItemJoinMechanic mechanic = entry.getValue(); @@ -69,6 +74,27 @@ public void onConnect(PlayerJoinEvent event) { } } } + + this.grantFirstWorldJoinItems(player, player.getWorld().getName()); + } + + @EventHandler + public void onWorldChange(PlayerChangedWorldEvent event) { + this.grantFirstWorldJoinItems(event.getPlayer(), event.getPlayer().getWorld().getName()); + } + + private void grantFirstWorldJoinItems(Player player, String worldName) { + Set visitedWorlds = this.plugin.getStorageManager().getVisitedWorlds(player.getUniqueId()); + + for (Map.Entry entry : this.itemJoinMechanicFactory.getAllMechanics()) { + ItemJoinMechanic mechanic = entry.getValue(); + if (!mechanic.hasFirstWorldJoinWorlds()) continue; + if (!mechanic.getFirstWorldJoinWorlds().contains(worldName)) continue; + if (visitedWorlds.contains(worldName)) continue; + + this.itemManager.giveItem(player, entry.getKey()); + this.plugin.getStorageManager().markWorldVisited(player.getUniqueId(), worldName); + } } @EventHandler diff --git a/src/main/java/fr/maxlego08/menu/storage/ZStorageManager.java b/src/main/java/fr/maxlego08/menu/storage/ZStorageManager.java index 1f7556d0..8fba936e 100644 --- a/src/main/java/fr/maxlego08/menu/storage/ZStorageManager.java +++ b/src/main/java/fr/maxlego08/menu/storage/ZStorageManager.java @@ -9,7 +9,9 @@ import fr.maxlego08.menu.api.storage.StorageManager; import fr.maxlego08.menu.api.storage.Tables; import fr.maxlego08.menu.api.storage.dto.DataDTO; +import fr.maxlego08.menu.api.storage.dto.FirstWorldJoinDTO; import fr.maxlego08.menu.api.storage.dto.InventoryDTO; +import fr.maxlego08.menu.storage.migrations.FirstWorldJoinMigration; import fr.maxlego08.menu.storage.migrations.PlayerDataMigration; import fr.maxlego08.menu.storage.migrations.PlayerInventoriesMigration; import fr.maxlego08.menu.storage.migrations.PlayerOpenInventoryMigration; @@ -25,10 +27,7 @@ import org.bukkit.event.EventPriority; import org.jspecify.annotations.NonNull; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.UUID; +import java.util.*; import java.util.concurrent.TimeUnit; public class ZStorageManager implements StorageManager { @@ -55,6 +54,7 @@ public void loadDatabase() { MigrationManager.registerMigration(new PlayerOpenInventoryMigration()); MigrationManager.registerMigration(new PlayerDataMigration()); MigrationManager.registerMigration(new PlayerInventoriesMigration()); + MigrationManager.registerMigration(new FirstWorldJoinMigration()); GlobalDatabaseConfiguration globalDatabaseConfiguration = new GlobalDatabaseConfiguration(this.plugin.getConfig()); String user = globalDatabaseConfiguration.getUser(); @@ -235,6 +235,26 @@ public void removeInventory(@NonNull UUID uuid) { this.plugin.getScheduler().runAsync(w -> this.requestHelper.delete(Tables.PLAYER_INVENTORIES, table -> table.where("player_id", uuid))); } + @Override + public @NonNull Set getVisitedWorlds(@NonNull UUID playerId) { + if (!this.isEnable()) return Set.of(); + + return new HashSet<>(this.requestHelper.select(Tables.FIRST_WORLD_JOIN, FirstWorldJoinDTO.class, table -> table.where("player_id", playerId)) + .stream() + .map(FirstWorldJoinDTO::worldName) + .toList()); + } + + @Override + public void markWorldVisited(@NonNull UUID playerId, @NonNull String worldName) { + if (!this.isEnable()) return; + + this.plugin.getScheduler().runAsync(w -> this.requestHelper.upsert(Tables.FIRST_WORLD_JOIN, table -> { + table.uuid("player_id", playerId).primary(); + table.string("world_name", worldName).primary(); + })); + } + @Override public void storeInventory(@NonNull UUID uuid, @NonNull InventoryPlayer inventoryPlayer) { diff --git a/src/main/java/fr/maxlego08/menu/storage/migrations/FirstWorldJoinMigration.java b/src/main/java/fr/maxlego08/menu/storage/migrations/FirstWorldJoinMigration.java new file mode 100644 index 00000000..2ca6381e --- /dev/null +++ b/src/main/java/fr/maxlego08/menu/storage/migrations/FirstWorldJoinMigration.java @@ -0,0 +1,15 @@ +package fr.maxlego08.menu.storage.migrations; + +import fr.maxlego08.menu.api.storage.Tables; +import fr.maxlego08.sarah.database.Migration; + +public class FirstWorldJoinMigration extends Migration { + @Override + public void up() { + this.create(Tables.FIRST_WORLD_JOIN, table -> { + table.uuid("player_id").primary(); + table.string("world_name", 255).primary(); + table.timestamps(); + }); + } +} From 674583e5e92514b9ce18556a2a6fbafaddb40730 Mon Sep 17 00:00:00 2001 From: 1robie <97293924+1robie@users.noreply.github.com> Date: Wed, 22 Jul 2026 17:54:38 +0200 Subject: [PATCH 5/6] feat: update item loading (CRaftEngine) to include player context and fix missing plugins dependencies in paper-plugin.yml --- .../menu/hooks/CraftEngineLoader.java | 2 +- src/main/resources/paper-plugin.yml | 39 +++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/Hooks/CraftEngine/src/main/java/fr/maxlego08/menu/hooks/CraftEngineLoader.java b/Hooks/CraftEngine/src/main/java/fr/maxlego08/menu/hooks/CraftEngineLoader.java index 128bc770..4baa7a45 100644 --- a/Hooks/CraftEngine/src/main/java/fr/maxlego08/menu/hooks/CraftEngineLoader.java +++ b/Hooks/CraftEngine/src/main/java/fr/maxlego08/menu/hooks/CraftEngineLoader.java @@ -22,6 +22,6 @@ public CraftEngineLoader() { public ItemStack load(@NonNull Player player, @NonNull YamlConfiguration configuration, @NonNull String path, @NonNull String materialString) { BukkitItemDefinition custom = CraftEngineItems.byId(materialString); if (custom == null) return null; - return custom.buildBukkitItem(); + return custom.buildBukkitItem(player); } } diff --git a/src/main/resources/paper-plugin.yml b/src/main/resources/paper-plugin.yml index 9d97d4e6..6119ffcb 100644 --- a/src/main/resources/paper-plugin.yml +++ b/src/main/resources/paper-plugin.yml @@ -11,11 +11,50 @@ loader: fr.maxlego08.menu.ZMenuPluginLoader folia-supported: true dependencies: server: + CraftEngine: + load: BEFORE + required: false + join-classpath: true + + Denizen: + load: BEFORE + required: false + join-classpath: true + + Shopkeepers: + load: BEFORE + required: false + join-classpath: true + + zItems: + load: BEFORE + required: false + join-classpath: true + Oraxen: load: BEFORE required: false join-classpath: true + eco: + load: BEFORE + required: false + join-classpath: true + + Nexo: + load: BEFORE + required: false + join-classpath: true + + NextGens: + required: false + join-classpath: true + + Nova: + load: BEFORE + required: false + join-classpath: true + PlaceholderAPI: load: BEFORE required: false From b02e54e9e08746d0fb530a69cf096c97f0c092e4 Mon Sep 17 00:00:00 2001 From: 1robie <97293924+1robie@users.noreply.github.com> Date: Thu, 23 Jul 2026 13:58:00 +0200 Subject: [PATCH 6/6] feat: optimize packet listeners by using ConcurrentHashMap for thread safety and simplify inventory task execution --- .../fr/maxlego08/menu/api/PacketManager.java | 2 + .../menu/hooks/packetevents/PacketUtils.java | 11 +++- .../listener/PacketAnimationListener.java | 60 +++++-------------- .../PacketEventClickLimiterListener.java | 48 +++++++++------ .../listener/PacketTitleListener.java | 8 +-- .../java/fr/maxlego08/menu/ZMenuPlugin.java | 4 ++ 6 files changed, 65 insertions(+), 68 deletions(-) diff --git a/API/src/main/java/fr/maxlego08/menu/api/PacketManager.java b/API/src/main/java/fr/maxlego08/menu/api/PacketManager.java index 649bc2e8..1d330714 100644 --- a/API/src/main/java/fr/maxlego08/menu/api/PacketManager.java +++ b/API/src/main/java/fr/maxlego08/menu/api/PacketManager.java @@ -10,6 +10,8 @@ public interface PacketManager { void onEnable(); + void onPostEnable(); + void onDisable(); void editInventoryTitleName(@NotNull Player player, @NotNull Component title); diff --git a/Hooks/PacketEvents/src/main/java/fr/maxlego08/menu/hooks/packetevents/PacketUtils.java b/Hooks/PacketEvents/src/main/java/fr/maxlego08/menu/hooks/packetevents/PacketUtils.java index 044a7ebb..f805cb4b 100644 --- a/Hooks/PacketEvents/src/main/java/fr/maxlego08/menu/hooks/packetevents/PacketUtils.java +++ b/Hooks/PacketEvents/src/main/java/fr/maxlego08/menu/hooks/packetevents/PacketUtils.java @@ -35,6 +35,7 @@ public class PacketUtils implements InventoryListener, PacketManager { private PacketAnimationListener packetAnimationListener; private PacketTitleListener packetTitleListener; + private PacketEventClickLimiterListener packetEventClickLimiterListener; public static final Map fakeContents = new HashMap<>(); private final MenuPlugin plugin; @@ -57,7 +58,15 @@ public void onEnable() { eventManager.registerListener(this.packetAnimationListener = new PacketAnimationListener(this.plugin), PacketListenerPriority.LOW); eventManager.registerListener(this.packetTitleListener = new PacketTitleListener(), PacketListenerPriority.LOW); if (Configuration.enablePacketEventClickLimiter){ - eventManager.registerListener(new PacketEventClickLimiterListener(), PacketListenerPriority.HIGH); + this.packetEventClickLimiterListener = new PacketEventClickLimiterListener(); + eventManager.registerListener(this.packetEventClickLimiterListener, PacketListenerPriority.HIGH); + } + } + + @Override + public void onPostEnable() { + if (this.packetEventClickLimiterListener != null) { + this.plugin.getInventoryManager().registerInventoryListener(this.packetEventClickLimiterListener); } } diff --git a/Hooks/PacketEvents/src/main/java/fr/maxlego08/menu/hooks/packetevents/listener/PacketAnimationListener.java b/Hooks/PacketEvents/src/main/java/fr/maxlego08/menu/hooks/packetevents/listener/PacketAnimationListener.java index c6d20480..c2ff1bd4 100644 --- a/Hooks/PacketEvents/src/main/java/fr/maxlego08/menu/hooks/packetevents/listener/PacketAnimationListener.java +++ b/Hooks/PacketEvents/src/main/java/fr/maxlego08/menu/hooks/packetevents/listener/PacketAnimationListener.java @@ -13,19 +13,18 @@ import fr.maxlego08.menu.api.utils.CompatibilityUtil; import fr.maxlego08.menu.hooks.packetevents.animation.PacketPlayerTitleAnimation; import org.bukkit.Bukkit; -import org.bukkit.Location; import org.bukkit.entity.Player; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.InventoryHolder; import org.jetbrains.annotations.NotNull; import java.util.Arrays; -import java.util.HashMap; import java.util.Map; import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; public class PacketAnimationListener implements PacketListener { - private final Map playerAnimationData = new HashMap<>(); + private final Map playerAnimationData = new ConcurrentHashMap<>(); private final MenuPlugin plugin; public PacketAnimationListener(MenuPlugin plugin) { @@ -57,7 +56,7 @@ public void setWindowId(int windowId) { } } - private void runInventoryTask(@NotNull Player player,@NotNull Inventory inventory,@NotNull Runnable task) { + private void runInventoryTask(@NotNull Player player, @NotNull Runnable task) { if (Bukkit.isPrimaryThread()) { task.run(); return; @@ -67,24 +66,7 @@ private void runInventoryTask(@NotNull Player player,@NotNull Inventory inventor return; } - Location location = this.resolveSafeLocation(inventory); - - if (location != null) { - this.plugin.getScheduler().runAtLocation(location, w -> task.run()); - } else { - this.plugin.getScheduler().runAtEntity(player, w -> task.run()); - } - } - - private Location resolveSafeLocation(@NotNull Inventory inventory) { - try { - Location location = inventory.getLocation(); - if (location != null && location.getWorld() != null) { - return location; - } - } catch (Exception ignored) { - } - return null; + this.plugin.getScheduler().runAtEntity(player, w -> task.run()); } @Override @@ -102,12 +84,10 @@ public void onPacketSend(PacketSendEvent event) { if (data != null && data.getContainerId() == containerId) { return; } - Inventory topInventory = CompatibilityUtil.getTopInventory(player); - if (topInventory == null) { - return; - } Runnable task = () -> { + Inventory topInventory = CompatibilityUtil.getTopInventory(player); + if (topInventory == null) return; InventoryHolder holder = topInventory.getHolder(); if (holder instanceof BaseInventory baseInventory) { TitleAnimation titleAnimation = baseInventory.getTitleAnimation(); @@ -124,17 +104,15 @@ public void onPacketSend(PacketSendEvent event) { } }; - this.runInventoryTask(player, topInventory, task); + this.runInventoryTask(player, task); } case PacketType.Play.Server.CLOSE_WINDOW -> { Player player = event.getPlayer(); if (player == null) return; - Inventory topInventory = CompatibilityUtil.getTopInventory(player); - if (topInventory == null) { - return; - } Runnable task = () -> { + Inventory topInventory = CompatibilityUtil.getTopInventory(player); + if (topInventory == null) return; InventoryHolder holder = topInventory.getHolder(); if (holder instanceof BaseInventory) { UUID playerUniqueId = player.getUniqueId(); @@ -142,7 +120,7 @@ public void onPacketSend(PacketSendEvent event) { } }; - this.runInventoryTask(player, topInventory, task); + this.runInventoryTask(player, task); } case PacketType.Play.Server.WINDOW_ITEMS -> { WrapperPlayServerWindowItems wrapper = new WrapperPlayServerWindowItems(event); @@ -152,23 +130,15 @@ public void onPacketSend(PacketSendEvent event) { int windowId = wrapper.getWindowId(); - PlayerAnimationData data = this.playerAnimationData.get(playerUniqueId); - if (data != null && data.getWindowId() == windowId) { + PlayerAnimationData data = this.playerAnimationData.computeIfAbsent(playerUniqueId, k -> new PlayerAnimationData(0)); + if (data.getWindowId() == windowId) { return; } - - if (data == null) { - data = new PlayerAnimationData(0); - this.playerAnimationData.put(playerUniqueId, data); - } data.setWindowId(windowId); - Inventory topInventory = CompatibilityUtil.getTopInventory(player); - if (topInventory == null) { - return; - } - Runnable task = () -> { + Inventory topInventory = CompatibilityUtil.getTopInventory(player); + if (topInventory == null) return; InventoryHolder holder = topInventory.getHolder(); if (holder instanceof BaseInventory baseInventory && baseInventory.getPlayerTitleAnimation() instanceof PacketPlayerTitleAnimation playerTitleAnimation) { playerTitleAnimation.setWrapperPlayServerWindowItems(wrapper); @@ -178,7 +148,7 @@ public void onPacketSend(PacketSendEvent event) { } }; - this.runInventoryTask(player, topInventory, task); + this.runInventoryTask(player, task); } default -> { } diff --git a/Hooks/PacketEvents/src/main/java/fr/maxlego08/menu/hooks/packetevents/listener/PacketEventClickLimiterListener.java b/Hooks/PacketEvents/src/main/java/fr/maxlego08/menu/hooks/packetevents/listener/PacketEventClickLimiterListener.java index cdb096e4..ba8f6ccb 100644 --- a/Hooks/PacketEvents/src/main/java/fr/maxlego08/menu/hooks/packetevents/listener/PacketEventClickLimiterListener.java +++ b/Hooks/PacketEvents/src/main/java/fr/maxlego08/menu/hooks/packetevents/listener/PacketEventClickLimiterListener.java @@ -4,18 +4,22 @@ import com.github.retrooper.packetevents.event.PacketReceiveEvent; import com.github.retrooper.packetevents.protocol.packettype.PacketType; import com.github.retrooper.packetevents.protocol.packettype.PacketTypeCommon; +import fr.maxlego08.menu.api.InventoryListener; import fr.maxlego08.menu.api.configuration.Configuration; import fr.maxlego08.menu.api.engine.BaseInventory; -import fr.maxlego08.menu.api.utils.CompatibilityUtil; import org.bukkit.entity.Player; -import org.bukkit.inventory.Inventory; -import java.util.HashMap; import java.util.Map; +import java.util.Set; import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; -public class PacketEventClickLimiterListener implements PacketListener { - private final Map lastClickTimes = new HashMap<>(); +public class PacketEventClickLimiterListener implements PacketListener, InventoryListener { + private final Map lastClickTimes = new ConcurrentHashMap<>(); + private final Set clickLimitedPlayers = ConcurrentHashMap.newKeySet(); + + public PacketEventClickLimiterListener() { + } @Override public void onPacketReceive(PacketReceiveEvent event) { @@ -24,20 +28,16 @@ public void onPacketReceive(PacketReceiveEvent event) { if (packetType == PacketType.Play.Client.CLICK_WINDOW) { Player player = event.getPlayer(); if (player == null) return; - Inventory topInventory = CompatibilityUtil.getTopInventory(player); - try { - if (topInventory != null && topInventory.getHolder() instanceof BaseInventory baseInventory && baseInventory.isClickLimiterEnabled()) { - UUID playerUniqueId = player.getUniqueId(); - - long currentTime = System.currentTimeMillis(); - Long lastClickTime = this.lastClickTimes.get(playerUniqueId); - if (lastClickTime != null && (currentTime - lastClickTime) < Configuration.packetEventClickLimiterMilliseconds) { - event.setCancelled(true); - return; - } - this.lastClickTimes.put(playerUniqueId, currentTime); + UUID playerUniqueId = player.getUniqueId(); + + if (this.clickLimitedPlayers.contains(playerUniqueId)) { + long currentTime = System.currentTimeMillis(); + Long lastClickTime = this.lastClickTimes.get(playerUniqueId); + if (lastClickTime != null && (currentTime - lastClickTime) < Configuration.packetEventClickLimiterMilliseconds) { + event.setCancelled(true); + return; } - } catch (Exception ignored) { + this.lastClickTimes.put(playerUniqueId, currentTime); } } else if (packetType == PacketType.Play.Client.CLOSE_WINDOW) { @@ -46,4 +46,16 @@ public void onPacketReceive(PacketReceiveEvent event) { this.lastClickTimes.remove(playerUniqueId); } } + + @Override + public void onInventoryPostOpen(Player player, BaseInventory inventory) { + if (inventory.isClickLimiterEnabled()) { + this.clickLimitedPlayers.add(player.getUniqueId()); + } + } + + @Override + public void onInventoryClose(Player player, BaseInventory inventory) { + this.clickLimitedPlayers.remove(player.getUniqueId()); + } } diff --git a/Hooks/PacketEvents/src/main/java/fr/maxlego08/menu/hooks/packetevents/listener/PacketTitleListener.java b/Hooks/PacketEvents/src/main/java/fr/maxlego08/menu/hooks/packetevents/listener/PacketTitleListener.java index 7c4edbcc..b0b015f2 100644 --- a/Hooks/PacketEvents/src/main/java/fr/maxlego08/menu/hooks/packetevents/listener/PacketTitleListener.java +++ b/Hooks/PacketEvents/src/main/java/fr/maxlego08/menu/hooks/packetevents/listener/PacketTitleListener.java @@ -8,17 +8,17 @@ import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerWindowItems; import org.bukkit.entity.Player; -import java.util.HashMap; import java.util.Map; import java.util.Optional; import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; public class PacketTitleListener implements PacketListener { - private final Map playerPacketInformation = new HashMap<>(); + private final Map playerPacketInformation = new ConcurrentHashMap<>(); public static class PlayerPacketInformation { - private WrapperPlayServerWindowItems wrapperPlayServerWindowItems; - private WrapperPlayServerOpenWindow wrapperPlayServerOpenWindow; + private volatile WrapperPlayServerWindowItems wrapperPlayServerWindowItems; + private volatile WrapperPlayServerOpenWindow wrapperPlayServerOpenWindow; public WrapperPlayServerWindowItems getWrapperPlayServerWindowItems() { return this.wrapperPlayServerWindowItems; diff --git a/src/main/java/fr/maxlego08/menu/ZMenuPlugin.java b/src/main/java/fr/maxlego08/menu/ZMenuPlugin.java index 6c073991..2952e425 100644 --- a/src/main/java/fr/maxlego08/menu/ZMenuPlugin.java +++ b/src/main/java/fr/maxlego08/menu/ZMenuPlugin.java @@ -327,6 +327,10 @@ public void onEnable() { // this.inventoryManager.registerInventoryListener(this.packetUtils); if (this.isActive(Plugins.PACKETEVENTS)) this.inventoryManager.registerInventoryListener(new PacketEventPlayerInventoryManager(this)); + if (this.packetManager != null) { + this.packetManager.onPostEnable(); + } + this.postEnable(); }