From 7a42746c5c5f56dc507bde7396dfc43eadedf42f Mon Sep 17 00:00:00 2001 From: qbiter Date: Sat, 10 Oct 2026 21:18:05 +0200 Subject: [PATCH 01/15] add: create base for Invsee command implementation --- .../core/feature/invsee/InvseeCommand.java | 39 +++++++++++++++++++ .../core/feature/invsee/InvseeService.java | 7 ++++ .../feature/invsee/InvseeServiceImpl.java | 30 ++++++++++++++ .../invsee/messages/ENInvseeMessages.java | 12 ++++++ .../invsee/messages/InvseeMessages.java | 7 ++++ .../invsee/messages/PLInvseeMessages.java | 12 ++++++ .../core/translation/Translation.java | 3 ++ .../implementation/ENTranslation.java | 4 ++ .../implementation/PLTranslation.java | 4 ++ 9 files changed, 118 insertions(+) create mode 100644 eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeCommand.java create mode 100644 eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeService.java create mode 100644 eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeServiceImpl.java create mode 100644 eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/messages/ENInvseeMessages.java create mode 100644 eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/messages/InvseeMessages.java create mode 100644 eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/messages/PLInvseeMessages.java diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeCommand.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeCommand.java new file mode 100644 index 000000000..2ec0bc09e --- /dev/null +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeCommand.java @@ -0,0 +1,39 @@ +package com.eternalcode.core.feature.invsee; + +import com.eternalcode.annotations.scan.command.DescriptionDocs; +import com.eternalcode.core.injector.annotations.Inject; +import com.eternalcode.core.notice.NoticeService; +import com.eternalcode.core.user.User; +import dev.rollczi.litecommands.annotations.argument.Arg; +import dev.rollczi.litecommands.annotations.command.Command; +import dev.rollczi.litecommands.annotations.context.Context; +import dev.rollczi.litecommands.annotations.execute.Execute; +import dev.rollczi.litecommands.annotations.permission.Permission; +import java.util.UUID; + +@Command(name = "invsee") +@Permission("eternalcore.invsee") +public class InvseeCommand { + + private final NoticeService noticeService; + private final InvseeService invseeService; + + @Inject + InvseeCommand(NoticeService noticeService, InvseeService invseeService) { + this.noticeService = noticeService; + this.invseeService = invseeService; + } + + @Execute + @DescriptionDocs(description = "Inspect someone's inventory", arguments = "") + void invsee(@Context User sender, @Arg("inspected_player") User target) { + UUID senderUuid = sender.getUniqueId(); + UUID targetUuid = target.getUniqueId(); + + if (sender.equals(target)) { + this.noticeService.viewer(sender, translation -> translation.invsee().cannotInspectSelf()); + } + + this.invseeService.openInventory(senderUuid, targetUuid); + } +} diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeService.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeService.java new file mode 100644 index 000000000..ddf7bf128 --- /dev/null +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeService.java @@ -0,0 +1,7 @@ +package com.eternalcode.core.feature.invsee; + +import java.util.UUID; + +public interface InvseeService { + void openInventory(UUID requester, UUID target); +} diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeServiceImpl.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeServiceImpl.java new file mode 100644 index 000000000..18d7266de --- /dev/null +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeServiceImpl.java @@ -0,0 +1,30 @@ +package com.eternalcode.core.feature.invsee; + +import com.eternalcode.core.injector.annotations.Inject; +import com.eternalcode.core.injector.annotations.component.Service; +import com.eternalcode.core.notice.NoticeService; +import java.util.UUID; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +@Service +public class InvseeServiceImpl implements InvseeService { + + private final NoticeService noticeService; + + @Inject + public InvseeServiceImpl(NoticeService noticeService) { + this.noticeService = noticeService; + } + + @Override + public void openInventory(UUID requester, UUID target) { + Player player = Bukkit.getPlayer(requester); + Player targetPlayer = Bukkit.getPlayer(target); + + assert player != null; + assert targetPlayer != null; + + player.openInventory(targetPlayer.getInventory()); + } +} diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/messages/ENInvseeMessages.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/messages/ENInvseeMessages.java new file mode 100644 index 000000000..b7c38c394 --- /dev/null +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/messages/ENInvseeMessages.java @@ -0,0 +1,12 @@ +package com.eternalcode.core.feature.invsee.messages; + +import com.eternalcode.multification.notice.Notice; +import eu.okaeri.configs.OkaeriConfig; +import lombok.Getter; +import lombok.experimental.Accessors; + +@Getter +@Accessors(fluent = true) +public class ENInvseeMessages extends OkaeriConfig implements InvseeMessages { + Notice cannotInspectSelf = Notice.chat("✘ You can't inspect your inventory!"); +} diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/messages/InvseeMessages.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/messages/InvseeMessages.java new file mode 100644 index 000000000..c3581bdbe --- /dev/null +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/messages/InvseeMessages.java @@ -0,0 +1,7 @@ +package com.eternalcode.core.feature.invsee.messages; + +import com.eternalcode.multification.notice.Notice; + +public interface InvseeMessages { + Notice cannotInspectSelf(); +} diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/messages/PLInvseeMessages.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/messages/PLInvseeMessages.java new file mode 100644 index 000000000..18a6f7284 --- /dev/null +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/messages/PLInvseeMessages.java @@ -0,0 +1,12 @@ +package com.eternalcode.core.feature.invsee.messages; + +import com.eternalcode.multification.notice.Notice; +import eu.okaeri.configs.OkaeriConfig; +import lombok.Getter; +import lombok.experimental.Accessors; + +@Getter +@Accessors(fluent = true) +public class PLInvseeMessages extends OkaeriConfig implements InvseeMessages { + Notice cannotInspectSelf = Notice.chat("✘ Nie możesz sprawdzić samego siebie!"); +} diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/translation/Translation.java b/eternalcore-core/src/main/java/com/eternalcode/core/translation/Translation.java index f5357fda1..f9a58bcdc 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/translation/Translation.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/translation/Translation.java @@ -3,6 +3,7 @@ import com.eternalcode.core.feature.back.messages.BackMessages; import com.eternalcode.core.feature.deathteleport.messages.DeathTeleportMessages; import com.eternalcode.core.feature.freeze.messages.FreezeMessages; +import com.eternalcode.core.feature.invsee.messages.InvseeMessages; import com.eternalcode.core.feature.mobignore.messages.MobIgnoreMessages; import com.eternalcode.core.feature.lag.messages.LagMessages; import com.eternalcode.core.feature.playtime.messages.PlaytimeMessages; @@ -188,4 +189,6 @@ interface Format { EndScreenMessages endScreen(); LagMessages lag(); + + InvseeMessages invsee(); } diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/translation/implementation/ENTranslation.java b/eternalcore-core/src/main/java/com/eternalcode/core/translation/implementation/ENTranslation.java index c433159cd..98c28785e 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/translation/implementation/ENTranslation.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/translation/implementation/ENTranslation.java @@ -27,6 +27,7 @@ import com.eternalcode.core.feature.helpop.messages.ENHelpOpMessages; import com.eternalcode.core.feature.home.messages.ENHomeMessages; import com.eternalcode.core.feature.ignore.messages.ENIgnoreMessages; +import com.eternalcode.core.feature.invsee.messages.ENInvseeMessages; import com.eternalcode.core.feature.itemedit.messages.ENItemEditMessages; import com.eternalcode.core.feature.jail.messages.ENJailMessages; import com.eternalcode.core.feature.joinmessage.messages.ENJoinMessage; @@ -325,6 +326,9 @@ public static class ENFormatSection extends OkaeriConfig implements Format { @Comment("# Lag") public ENLagMessages lag = new ENLagMessages(); + @Comment("# Invsee") + public ENInvseeMessages invsee = new ENInvseeMessages(); + @Override public File getConfigFile(File dataFolder) { return new File(dataFolder, "lang" + File.separator + "en_messages.yml"); diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/translation/implementation/PLTranslation.java b/eternalcore-core/src/main/java/com/eternalcode/core/translation/implementation/PLTranslation.java index 17773c22d..4471af2cb 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/translation/implementation/PLTranslation.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/translation/implementation/PLTranslation.java @@ -3,6 +3,7 @@ import com.eternalcode.core.feature.back.messages.PLBackMessages; import com.eternalcode.core.feature.deathteleport.messages.PLDeathTeleportMessages; import com.eternalcode.core.feature.freeze.messages.PLFreezeMessages; +import com.eternalcode.core.feature.invsee.messages.PLInvseeMessages; import com.eternalcode.core.feature.lag.messages.PLLagMessages; import com.eternalcode.core.feature.near.messages.PLNearMessages; import com.eternalcode.core.feature.mobignore.messages.PLMobIgnoreMessages; @@ -321,6 +322,9 @@ public static class PLFormatSection extends OkaeriConfig implements Format { @Comment("# Lag") public PLLagMessages lag = new PLLagMessages(); + @Comment("# Sprawdzanie ekwipunku innych graczy") + public PLInvseeMessages invsee = new PLInvseeMessages(); + @Override public File getConfigFile(File dataFolder) { return new File(dataFolder, "lang" + File.separator + "pl_messages.yml"); From c6558193b2826dedcfe3dbbbaeafa4dcee1e981d Mon Sep 17 00:00:00 2001 From: qbiter Date: Tue, 20 Oct 2026 08:24:04 +0200 Subject: [PATCH 02/15] refactor: rename methods for clarity in InvseeCommand and InvseeService --- .../java/com/eternalcode/core/feature/invsee/InvseeCommand.java | 2 +- .../java/com/eternalcode/core/feature/invsee/InvseeService.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeCommand.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeCommand.java index 2ec0bc09e..33afbccab 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeCommand.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeCommand.java @@ -34,6 +34,6 @@ void invsee(@Context User sender, @Arg("inspected_player") User target) { this.noticeService.viewer(sender, translation -> translation.invsee().cannotInspectSelf()); } - this.invseeService.openInventory(senderUuid, targetUuid); + this.invseeService.inspectPlayer(senderUuid, targetUuid); } } diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeService.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeService.java index ddf7bf128..5c54c8a5d 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeService.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeService.java @@ -3,5 +3,5 @@ import java.util.UUID; public interface InvseeService { - void openInventory(UUID requester, UUID target); + void inspectPlayer(UUID requester, UUID target); } From 39e1bf0c1ad7f2414e78ca470d01af552ef13108 Mon Sep 17 00:00:00 2001 From: qbiter Date: Tue, 20 Oct 2026 08:24:10 +0200 Subject: [PATCH 03/15] feat: implement player inspection functionality and status management --- .../feature/invsee/InvseeServiceImpl.java | 31 ++++++++++++++++--- .../core/feature/invsee/InvseeStatus.java | 6 ++++ 2 files changed, 33 insertions(+), 4 deletions(-) create mode 100644 eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeStatus.java diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeServiceImpl.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeServiceImpl.java index 18d7266de..66e5c47f1 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeServiceImpl.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeServiceImpl.java @@ -3,12 +3,23 @@ import com.eternalcode.core.injector.annotations.Inject; import com.eternalcode.core.injector.annotations.component.Service; import com.eternalcode.core.notice.NoticeService; +import java.util.HashMap; import java.util.UUID; import org.bukkit.Bukkit; import org.bukkit.entity.Player; +import org.bukkit.inventory.Inventory; @Service public class InvseeServiceImpl implements InvseeService { + /** + * First UUID is the player who is inspecting, second UUID is the player being inspected + */ + private static final HashMap activeInsepctions = new HashMap<>(); + + /** + * First UUID is inspected player, second valie is {@link InvseeStatus} which contains the status of the inspection + */ + private static final HashMap inspectionStatus = new HashMap<>(); private final NoticeService noticeService; @@ -18,13 +29,25 @@ public InvseeServiceImpl(NoticeService noticeService) { } @Override - public void openInventory(UUID requester, UUID target) { + public void inspectPlayer(UUID requester, UUID target) { Player player = Bukkit.getPlayer(requester); Player targetPlayer = Bukkit.getPlayer(target); - assert player != null; - assert targetPlayer != null; + if (player == null || targetPlayer == null) { + return; + } + + activeInsepctions.put(player.getUniqueId(), targetPlayer.getUniqueId()); + this.updateOrPutInspectionStatus(targetPlayer.getUniqueId()); + + Inventory targetInventory = targetPlayer.getInventory(); + + player.openInventory(targetInventory); + } - player.openInventory(targetPlayer.getInventory()); + private void updateOrPutInspectionStatus(UUID target) { + if (!inspectionStatus.containsKey(target)) { + inspectionStatus.put(target, InvseeStatus.FREE); + } } } diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeStatus.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeStatus.java new file mode 100644 index 000000000..73abda459 --- /dev/null +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeStatus.java @@ -0,0 +1,6 @@ +package com.eternalcode.core.feature.invsee; + +public enum InvseeStatus { + FREE, + BLOCKED +} From c3e9afe761c712a6ce4ea16a4a0357b2114481ea Mon Sep 17 00:00:00 2001 From: qbiter Date: Sun, 25 Oct 2026 00:02:53 +0200 Subject: [PATCH 04/15] feat: enhance Invsee functionality with GUI support and inspection management --- .../core/feature/invsee/InvseeCommand.java | 1 + .../core/feature/invsee/InvseeService.java | 6 ++ .../feature/invsee/InvseeServiceImpl.java | 98 ++++++++++++++++++- 3 files changed, 103 insertions(+), 2 deletions(-) diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeCommand.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeCommand.java index 33afbccab..5a3161b26 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeCommand.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeCommand.java @@ -32,6 +32,7 @@ void invsee(@Context User sender, @Arg("inspected_player") User target) { if (sender.equals(target)) { this.noticeService.viewer(sender, translation -> translation.invsee().cannotInspectSelf()); + return; } this.invseeService.inspectPlayer(senderUuid, targetUuid); diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeService.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeService.java index 5c54c8a5d..ebf3f17d7 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeService.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeService.java @@ -4,4 +4,10 @@ public interface InvseeService { void inspectPlayer(UUID requester, UUID target); + + boolean isBeingInspected(UUID target); + + void refreshInspection(UUID target); + + void closeInspections(UUID target); } diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeServiceImpl.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeServiceImpl.java index 66e5c47f1..64dfbb232 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeServiceImpl.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeServiceImpl.java @@ -3,11 +3,18 @@ import com.eternalcode.core.injector.annotations.Inject; import com.eternalcode.core.injector.annotations.component.Service; import com.eternalcode.core.notice.NoticeService; +import dev.triumphteam.gui.guis.Gui; +import dev.triumphteam.gui.guis.GuiItem; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.UUID; +import net.kyori.adventure.text.minimessage.MiniMessage; import org.bukkit.Bukkit; +import org.bukkit.entity.HumanEntity; import org.bukkit.entity.Player; import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; @Service public class InvseeServiceImpl implements InvseeService { @@ -21,11 +28,18 @@ public class InvseeServiceImpl implements InvseeService { */ private static final HashMap inspectionStatus = new HashMap<>(); + /** + * First UUID is inspected, second field are GUIs of that player's inventory copy + */ + private static final HashMap> activeGuis = new HashMap<>(); + private final NoticeService noticeService; + private final MiniMessage miniMessage; @Inject - public InvseeServiceImpl(NoticeService noticeService) { + public InvseeServiceImpl(NoticeService noticeService, MiniMessage miniMessage) { this.noticeService = noticeService; + this.miniMessage = miniMessage; } @Override @@ -42,7 +56,87 @@ public void inspectPlayer(UUID requester, UUID target) { Inventory targetInventory = targetPlayer.getInventory(); - player.openInventory(targetInventory); + Gui gui = Gui.gui() + .title(miniMessage.deserialize("Inspecting " + targetPlayer.getName())) + .rows(5) + .create(); + + this.fillGui(gui, targetInventory); + + UUID targetUuid = targetPlayer.getUniqueId(); + activeGuis.computeIfAbsent(targetUuid, uuid -> new ArrayList<>()).add(gui); + + gui.setCloseGuiAction(event -> { + activeInsepctions.remove(player.getUniqueId()); + this.removeGui(targetUuid, gui); + }); + + gui.open(player); + } + + @Override + public boolean isBeingInspected(UUID target) { + return activeGuis.containsKey(target); + } + + @Override + public void refreshInspection(UUID target) { + Player targetPlayer = Bukkit.getPlayer(target); + List guis = activeGuis.get(target); + + if (targetPlayer == null || guis == null) { + return; + } + + for (Gui gui : guis) { + this.fillGui(gui, targetPlayer.getInventory()); + gui.update(); + } + } + + @Override + public void closeInspections(UUID target) { + List guis = activeGuis.remove(target); + + if (guis == null) { + return; + } + + for (Gui gui : List.copyOf(guis)) { + for (HumanEntity viewer : List.copyOf(gui.getInventory().getViewers())) { + viewer.closeInventory(); + } + } + + inspectionStatus.remove(target); + } + + private void fillGui(Gui gui, Inventory targetInventory) { + for (int slot = 0; slot < targetInventory.getSize(); slot++) { + ItemStack item = targetInventory.getItem(slot); + + if (item == null || item.getType().isAir()) { + gui.removeItem(slot); + continue; + } + + gui.setItem(slot, new GuiItem(item)); + } + } + + private void removeGui(UUID target, Gui gui) { + List guis = activeGuis.get(target); + + if (guis == null) { + return; + } + + guis.remove(gui); + + if (guis.isEmpty()) { + activeGuis.remove(target); + inspectionStatus.remove(target); + } } private void updateOrPutInspectionStatus(UUID target) { From 1ff7661cc95e58cbb68ced8917ac6ad892898e49 Mon Sep 17 00:00:00 2001 From: qbiter Date: Sun, 25 Oct 2026 00:08:21 +0200 Subject: [PATCH 05/15] feat: implement inventory modification and GUI interaction for Invsee --- .../feature/invsee/InvseeServiceImpl.java | 90 ++++++++++++++++--- 1 file changed, 80 insertions(+), 10 deletions(-) diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeServiceImpl.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeServiceImpl.java index 64dfbb232..45a7b163d 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeServiceImpl.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeServiceImpl.java @@ -1,23 +1,35 @@ package com.eternalcode.core.feature.invsee; +import com.cryptomorin.xseries.XMaterial; +import com.eternalcode.commons.scheduler.Scheduler; import com.eternalcode.core.injector.annotations.Inject; import com.eternalcode.core.injector.annotations.component.Service; import com.eternalcode.core.notice.NoticeService; +import com.eternalcode.core.util.MaterialUtil; +import dev.triumphteam.gui.builder.item.ItemBuilder; import dev.triumphteam.gui.guis.Gui; import dev.triumphteam.gui.guis.GuiItem; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.UUID; +import net.kyori.adventure.text.Component; import net.kyori.adventure.text.minimessage.MiniMessage; import org.bukkit.Bukkit; +import org.bukkit.Material; import org.bukkit.entity.HumanEntity; import org.bukkit.entity.Player; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.PlayerInventory; @Service public class InvseeServiceImpl implements InvseeService { + + private static final String MODIFY_PERMISSION = "eternalcore.invsee.modify"; + private static final int GUI_ROWS = 5; + private static final Material FILLER_MATERIAL = MaterialUtil.parseRequired(XMaterial.GRAY_STAINED_GLASS_PANE); + /** * First UUID is the player who is inspecting, second UUID is the player being inspected */ @@ -35,11 +47,13 @@ public class InvseeServiceImpl implements InvseeService { private final NoticeService noticeService; private final MiniMessage miniMessage; + private final Scheduler scheduler; @Inject - public InvseeServiceImpl(NoticeService noticeService, MiniMessage miniMessage) { + public InvseeServiceImpl(NoticeService noticeService, MiniMessage miniMessage, Scheduler scheduler) { this.noticeService = noticeService; this.miniMessage = miniMessage; + this.scheduler = scheduler; } @Override @@ -58,12 +72,21 @@ public void inspectPlayer(UUID requester, UUID target) { Gui gui = Gui.gui() .title(miniMessage.deserialize("Inspecting " + targetPlayer.getName())) - .rows(5) + .rows(GUI_ROWS) .create(); - this.fillGui(gui, targetInventory); - UUID targetUuid = targetPlayer.getUniqueId(); + this.setupFiller(gui, targetInventory.getSize()); + + if (player.hasPermission(MODIFY_PERMISSION)) { + // Clicks and drags are applied by vanilla first, so the GUI state is copied back on the next tick + gui.setDefaultClickAction(event -> this.scheduler.run(() -> this.applyChanges(gui, targetUuid))); + gui.setDragAction(event -> this.scheduler.run(() -> this.applyChanges(gui, targetUuid))); + } + else { + gui.disableAllInteractions(); + } + activeGuis.computeIfAbsent(targetUuid, uuid -> new ArrayList<>()).add(gui); gui.setCloseGuiAction(event -> { @@ -72,6 +95,7 @@ public void inspectPlayer(UUID requester, UUID target) { }); gui.open(player); + this.fillGui(gui, targetInventory); } @Override @@ -90,7 +114,6 @@ public void refreshInspection(UUID target) { for (Gui gui : guis) { this.fillGui(gui, targetPlayer.getInventory()); - gui.update(); } } @@ -111,19 +134,66 @@ public void closeInspections(UUID target) { inspectionStatus.remove(target); } + /** + * Copies the target's inventory into the GUI. Items are put into the inventory directly + * (not as {@link GuiItem}) so they stay untagged and can be moved freely by the inspector. + */ private void fillGui(Gui gui, Inventory targetInventory) { + Inventory guiInventory = gui.getInventory(); + + for (int slot = 0; slot < targetInventory.getSize(); slot++) { + guiInventory.setItem(slot, cloneOrNull(targetInventory.getItem(slot))); + } + } + + private void applyChanges(Gui gui, UUID target) { + Player targetPlayer = Bukkit.getPlayer(target); + + if (targetPlayer == null) { + return; + } + + PlayerInventory targetInventory = targetPlayer.getInventory(); + Inventory guiInventory = gui.getInventory(); + for (int slot = 0; slot < targetInventory.getSize(); slot++) { - ItemStack item = targetInventory.getItem(slot); + targetInventory.setItem(slot, cloneOrNull(guiInventory.getItem(slot))); + } + + List guis = activeGuis.get(target); + + if (guis == null) { + return; + } - if (item == null || item.getType().isAir()) { - gui.removeItem(slot); - continue; + for (Gui otherGui : guis) { + if (otherGui != gui) { + this.fillGui(otherGui, targetInventory); } + } + } + + /** + * Blocks the slots after the player's inventory (41-44), so items can't be placed there. + */ + private void setupFiller(Gui gui, int playerInventorySize) { + GuiItem filler = ItemBuilder.from(FILLER_MATERIAL) + .name(Component.empty()) + .asGuiItem(event -> event.setCancelled(true)); - gui.setItem(slot, new GuiItem(item)); + for (int slot = playerInventorySize; slot < GUI_ROWS * 9; slot++) { + gui.setItem(slot, filler); } } + private static ItemStack cloneOrNull(ItemStack item) { + if (item == null || item.getType().isAir()) { + return null; + } + + return item.clone(); + } + private void removeGui(UUID target, Gui gui) { List guis = activeGuis.get(target); From 32ab8065fb0511d2e0afa6499504ccaf7e5ef411 Mon Sep 17 00:00:00 2001 From: qbiter Date: Sun, 25 Oct 2026 00:17:04 +0200 Subject: [PATCH 06/15] feat: implement inventory synchronization for Invsee with event handling --- .../core/feature/invsee/InvseeService.java | 2 + .../feature/invsee/InvseeServiceImpl.java | 129 ++++++++++++++-- .../core/feature/invsee/InvseeStatus.java | 6 + .../listeners/InvseeInventoryController.java | 144 ++++++++++++++++++ 4 files changed, 266 insertions(+), 15 deletions(-) create mode 100644 eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/listeners/InvseeInventoryController.java diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeService.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeService.java index ebf3f17d7..c51b032d3 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeService.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeService.java @@ -7,6 +7,8 @@ public interface InvseeService { boolean isBeingInspected(UUID target); + boolean isInventoryBlocked(UUID target); + void refreshInspection(UUID target); void closeInspections(UUID target); diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeServiceImpl.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeServiceImpl.java index 45a7b163d..23fad3c2e 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeServiceImpl.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeServiceImpl.java @@ -12,6 +12,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.Objects; import java.util.UUID; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.minimessage.MiniMessage; @@ -19,6 +20,7 @@ import org.bukkit.Material; import org.bukkit.entity.HumanEntity; import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.PlayerInventory; @@ -45,6 +47,11 @@ public class InvseeServiceImpl implements InvseeService { */ private static final HashMap> activeGuis = new HashMap<>(); + /** + * First UUID is inspected player, second value is the inspector's change waiting to be written to the inventory + */ + private static final HashMap pendingChanges = new HashMap<>(); + private final NoticeService noticeService; private final MiniMessage miniMessage; private final Scheduler scheduler; @@ -79,9 +86,8 @@ public void inspectPlayer(UUID requester, UUID target) { this.setupFiller(gui, targetInventory.getSize()); if (player.hasPermission(MODIFY_PERMISSION)) { - // Clicks and drags are applied by vanilla first, so the GUI state is copied back on the next tick - gui.setDefaultClickAction(event -> this.scheduler.run(() -> this.applyChanges(gui, targetUuid))); - gui.setDragAction(event -> this.scheduler.run(() -> this.applyChanges(gui, targetUuid))); + gui.setDefaultClickAction(event -> this.handleInspectorAction(gui, targetUuid, event)); + gui.setDragAction(event -> this.handleInspectorAction(gui, targetUuid, event)); } else { gui.disableAllInteractions(); @@ -103,8 +109,18 @@ public boolean isBeingInspected(UUID target) { return activeGuis.containsKey(target); } + @Override + public boolean isInventoryBlocked(UUID target) { + return inspectionStatus.get(target) == InvseeStatus.BLOCKED; + } + @Override public void refreshInspection(UUID target) { + // While blocked, the pending inspector change refreshes all GUIs after it is written + if (this.isInventoryBlocked(target)) { + return; + } + Player targetPlayer = Bukkit.getPlayer(target); List guis = activeGuis.get(target); @@ -119,6 +135,9 @@ public void refreshInspection(UUID target) { @Override public void closeInspections(UUID target) { + // Write the pending change while the player is still online, otherwise the moved item would be duplicated + this.flushPendingChange(target); + List guis = activeGuis.remove(target); if (guis == null) { @@ -135,18 +154,57 @@ public void closeInspections(UUID target) { } /** - * Copies the target's inventory into the GUI. Items are put into the inventory directly - * (not as {@link GuiItem}) so they stay untagged and can be moved freely by the inspector. + * Runs before vanilla applies the inspector's click or drag. Only one change per inventory + * is allowed at a time - the inventory stays {@link InvseeStatus#BLOCKED} until the change is written + * on the next tick, so other inspectors and the target itself can't take the same item. */ - private void fillGui(Gui gui, Inventory targetInventory) { + private void handleInspectorAction(Gui gui, UUID target, Cancellable event) { + Player targetPlayer = Bukkit.getPlayer(target); + + if (targetPlayer == null || this.isInventoryBlocked(target)) { + event.setCancelled(true); + return; + } + + PlayerInventory targetInventory = targetPlayer.getInventory(); Inventory guiInventory = gui.getInventory(); - for (int slot = 0; slot < targetInventory.getSize(); slot++) { - guiInventory.setItem(slot, cloneOrNull(targetInventory.getItem(slot))); + // The inventory changed since the GUI was filled (e.g. the target picked up an item this tick) + if (!this.matches(guiInventory, targetInventory)) { + event.setCancelled(true); + this.fillGui(gui, targetInventory); + return; + } + + inspectionStatus.put(target, InvseeStatus.BLOCKED); + + ItemStack[] snapshot = this.snapshot(guiInventory, targetInventory.getSize()); + pendingChanges.put(target, () -> this.applyChanges(gui, target, snapshot)); + + this.scheduler.run(() -> this.flushPendingChange(target)); + } + + private void flushPendingChange(UUID target) { + Runnable pendingChange = pendingChanges.remove(target); + + if (pendingChange == null) { + return; + } + + pendingChange.run(); + + if (activeGuis.containsKey(target)) { + inspectionStatus.put(target, InvseeStatus.FREE); + } + else { + inspectionStatus.remove(target); } } - private void applyChanges(Gui gui, UUID target) { + /** + * Writes only the slots the inspector changed, so changes made to other slots in the meantime are not reverted. + */ + private void applyChanges(Gui gui, UUID target, ItemStack[] snapshot) { Player targetPlayer = Bukkit.getPlayer(target); if (targetPlayer == null) { @@ -156,8 +214,12 @@ private void applyChanges(Gui gui, UUID target) { PlayerInventory targetInventory = targetPlayer.getInventory(); Inventory guiInventory = gui.getInventory(); - for (int slot = 0; slot < targetInventory.getSize(); slot++) { - targetInventory.setItem(slot, cloneOrNull(guiInventory.getItem(slot))); + for (int slot = 0; slot < snapshot.length; slot++) { + ItemStack current = cloneOrNull(guiInventory.getItem(slot)); + + if (!Objects.equals(current, snapshot[slot])) { + targetInventory.setItem(slot, current); + } } List guis = activeGuis.get(target); @@ -166,11 +228,44 @@ private void applyChanges(Gui gui, UUID target) { return; } - for (Gui otherGui : guis) { - if (otherGui != gui) { - this.fillGui(otherGui, targetInventory); + for (Gui activeGui : guis) { + this.fillGui(activeGui, targetInventory); + } + } + + /** + * Copies the target's inventory into the GUI. Items are put into the inventory directly + * (not as {@link GuiItem}) so they stay untagged and can be moved freely by the inspector. + */ + private void fillGui(Gui gui, Inventory targetInventory) { + Inventory guiInventory = gui.getInventory(); + + for (int slot = 0; slot < targetInventory.getSize(); slot++) { + guiInventory.setItem(slot, cloneOrNull(targetInventory.getItem(slot))); + } + } + + private boolean matches(Inventory guiInventory, Inventory targetInventory) { + for (int slot = 0; slot < targetInventory.getSize(); slot++) { + ItemStack guiItem = cloneOrNull(guiInventory.getItem(slot)); + ItemStack targetItem = cloneOrNull(targetInventory.getItem(slot)); + + if (!Objects.equals(guiItem, targetItem)) { + return false; } } + + return true; + } + + private ItemStack[] snapshot(Inventory guiInventory, int size) { + ItemStack[] snapshot = new ItemStack[size]; + + for (int slot = 0; slot < size; slot++) { + snapshot[slot] = cloneOrNull(guiInventory.getItem(slot)); + } + + return snapshot; } /** @@ -205,7 +300,11 @@ private void removeGui(UUID target, Gui gui) { if (guis.isEmpty()) { activeGuis.remove(target); - inspectionStatus.remove(target); + + // Keep the BLOCKED status until the pending change is written + if (!pendingChanges.containsKey(target)) { + inspectionStatus.remove(target); + } } } diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeStatus.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeStatus.java index 73abda459..811ef9005 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeStatus.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeStatus.java @@ -1,6 +1,12 @@ package com.eternalcode.core.feature.invsee; public enum InvseeStatus { + /** + * Nobody is changing the inventory, inspectors can modify it. + */ FREE, + /** + * An inspector's change is waiting to be written - the target and other inspectors can't modify the inventory. + */ BLOCKED } diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/listeners/InvseeInventoryController.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/listeners/InvseeInventoryController.java new file mode 100644 index 000000000..08a1038c6 --- /dev/null +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/listeners/InvseeInventoryController.java @@ -0,0 +1,144 @@ +package com.eternalcode.core.feature.invsee.listeners; + +import com.destroystokyo.paper.event.player.PlayerArmorChangeEvent; +import com.eternalcode.commons.scheduler.Scheduler; +import com.eternalcode.core.feature.invsee.InvseeService; +import com.eternalcode.core.injector.annotations.Inject; +import com.eternalcode.core.injector.annotations.component.Controller; +import java.util.UUID; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.block.BlockPlaceEvent; +import org.bukkit.event.entity.EntityPickupItemEvent; +import org.bukkit.event.entity.PlayerDeathEvent; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.inventory.InventoryDragEvent; +import org.bukkit.event.player.PlayerDropItemEvent; +import org.bukkit.event.player.PlayerInteractEntityEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.event.player.PlayerItemBreakEvent; +import org.bukkit.event.player.PlayerItemConsumeEvent; +import org.bukkit.event.player.PlayerItemDamageEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.event.player.PlayerSwapHandItemsEvent; + +/** + * Keeps invsee GUIs in sync with the inspected player's inventory. + *

+ * While an inspector's change is pending ({@link com.eternalcode.core.feature.invsee.InvseeStatus#BLOCKED}), + * the inspected player can't modify their own inventory, so the same item can't end up in both inventories. + *

+ * Most of these events fire before the inventory is actually modified, + * so the refresh is scheduled for the next tick. + */ +@Controller +public class InvseeInventoryController implements Listener { + + private final InvseeService invseeService; + private final Scheduler scheduler; + + @Inject + InvseeInventoryController(InvseeService invseeService, Scheduler scheduler) { + this.invseeService = invseeService; + this.scheduler = scheduler; + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + void onDrop(PlayerDropItemEvent event) { + this.handleChange(event.getPlayer(), event); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + void onPickup(EntityPickupItemEvent event) { + if (event.getEntity() instanceof Player player) { + this.handleChange(player, event); + } + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + void onClick(InventoryClickEvent event) { + if (event.getWhoClicked() instanceof Player player) { + this.handleChange(player, event); + } + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + void onDrag(InventoryDragEvent event) { + if (event.getWhoClicked() instanceof Player player) { + this.handleChange(player, event); + } + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + void onConsume(PlayerItemConsumeEvent event) { + this.handleChange(event.getPlayer(), event); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + void onBlockPlace(BlockPlaceEvent event) { + this.handleChange(event.getPlayer(), event); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + void onSwapHands(PlayerSwapHandItemsEvent event) { + this.handleChange(event.getPlayer(), event); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + void onItemDamage(PlayerItemDamageEvent event) { + this.handleChange(event.getPlayer(), event); + } + + // Right click in air is fired as already cancelled, but the item is still used, so cancelled events are not ignored + @EventHandler(priority = EventPriority.HIGHEST) + void onInteract(PlayerInteractEvent event) { + this.handleChange(event.getPlayer(), event); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + void onInteractEntity(PlayerInteractEntityEvent event) { + this.handleChange(event.getPlayer(), event); + } + + @EventHandler(priority = EventPriority.MONITOR) + void onItemBreak(PlayerItemBreakEvent event) { + this.scheduleRefresh(event.getPlayer()); + } + + @EventHandler(priority = EventPriority.MONITOR) + void onArmorChange(PlayerArmorChangeEvent event) { + this.scheduleRefresh(event.getPlayer()); + } + + @EventHandler(priority = EventPriority.MONITOR) + void onDeath(PlayerDeathEvent event) { + this.scheduleRefresh(event.getEntity()); + } + + @EventHandler(priority = EventPriority.MONITOR) + void onQuit(PlayerQuitEvent event) { + this.invseeService.closeInspections(event.getPlayer().getUniqueId()); + } + + private void handleChange(Player target, Cancellable event) { + if (this.invseeService.isInventoryBlocked(target.getUniqueId())) { + event.setCancelled(true); + return; + } + + this.scheduleRefresh(target); + } + + private void scheduleRefresh(Player target) { + UUID targetUuid = target.getUniqueId(); + + if (!this.invseeService.isBeingInspected(targetUuid)) { + return; + } + + this.scheduler.run(() -> this.invseeService.refreshInspection(targetUuid)); + } +} From b189b7ea72d6746932e5c44165028310f5d9f0ea Mon Sep 17 00:00:00 2001 From: qbiter Date: Sun, 25 Oct 2026 00:26:11 +0200 Subject: [PATCH 07/15] feat: add inventory title with player reference to InvseeMessages --- .../core/feature/invsee/messages/ENInvseeMessages.java | 4 ++++ .../core/feature/invsee/messages/InvseeMessages.java | 2 ++ .../core/feature/invsee/messages/PLInvseeMessages.java | 4 ++++ 3 files changed, 10 insertions(+) diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/messages/ENInvseeMessages.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/messages/ENInvseeMessages.java index b7c38c394..c89428185 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/messages/ENInvseeMessages.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/messages/ENInvseeMessages.java @@ -2,6 +2,7 @@ import com.eternalcode.multification.notice.Notice; import eu.okaeri.configs.OkaeriConfig; +import eu.okaeri.configs.annotation.Comment; import lombok.Getter; import lombok.experimental.Accessors; @@ -9,4 +10,7 @@ @Accessors(fluent = true) public class ENInvseeMessages extends OkaeriConfig implements InvseeMessages { Notice cannotInspectSelf = Notice.chat("✘ You can't inspect your inventory!"); + + @Comment("# {PLAYER} - Inspected player") + String inventoryTitle = "Inventory of {PLAYER}"; } diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/messages/InvseeMessages.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/messages/InvseeMessages.java index c3581bdbe..20bd99e4c 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/messages/InvseeMessages.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/messages/InvseeMessages.java @@ -4,4 +4,6 @@ public interface InvseeMessages { Notice cannotInspectSelf(); + + String inventoryTitle(); } diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/messages/PLInvseeMessages.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/messages/PLInvseeMessages.java index 18a6f7284..8b95f1c94 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/messages/PLInvseeMessages.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/messages/PLInvseeMessages.java @@ -2,6 +2,7 @@ import com.eternalcode.multification.notice.Notice; import eu.okaeri.configs.OkaeriConfig; +import eu.okaeri.configs.annotation.Comment; import lombok.Getter; import lombok.experimental.Accessors; @@ -9,4 +10,7 @@ @Accessors(fluent = true) public class PLInvseeMessages extends OkaeriConfig implements InvseeMessages { Notice cannotInspectSelf = Notice.chat("✘ Nie możesz sprawdzić samego siebie!"); + + @Comment("# {PLAYER} - Sprawdzany gracz") + String inventoryTitle = "Ekwipunek gracza {PLAYER}"; } From 83aa2ddad166f21b1c0e0e6596b4935c57693139 Mon Sep 17 00:00:00 2001 From: qbiter Date: Sun, 25 Oct 2026 00:26:20 +0200 Subject: [PATCH 08/15] feat: implement handling of inspections on player death to prevent item loss --- .../core/feature/invsee/InvseeService.java | 3 + .../feature/invsee/InvseeServiceImpl.java | 81 +++++++++++++++---- .../listeners/InvseeInventoryController.java | 5 +- 3 files changed, 72 insertions(+), 17 deletions(-) diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeService.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeService.java index c51b032d3..6363cdae3 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeService.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeService.java @@ -1,6 +1,7 @@ package com.eternalcode.core.feature.invsee; import java.util.UUID; +import org.bukkit.event.entity.PlayerDeathEvent; public interface InvseeService { void inspectPlayer(UUID requester, UUID target); @@ -12,4 +13,6 @@ public interface InvseeService { void refreshInspection(UUID target); void closeInspections(UUID target); + + void closeInspectionsOnDeath(PlayerDeathEvent event); } diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeServiceImpl.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeServiceImpl.java index 23fad3c2e..a6cc97104 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeServiceImpl.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeServiceImpl.java @@ -5,6 +5,7 @@ import com.eternalcode.core.injector.annotations.Inject; import com.eternalcode.core.injector.annotations.component.Service; import com.eternalcode.core.notice.NoticeService; +import com.eternalcode.core.translation.TranslationManager; import com.eternalcode.core.util.MaterialUtil; import dev.triumphteam.gui.builder.item.ItemBuilder; import dev.triumphteam.gui.guis.Gui; @@ -18,9 +19,11 @@ import net.kyori.adventure.text.minimessage.MiniMessage; import org.bukkit.Bukkit; import org.bukkit.Material; +import org.bukkit.enchantments.Enchantment; import org.bukkit.entity.HumanEntity; import org.bukkit.entity.Player; import org.bukkit.event.Cancellable; +import org.bukkit.event.entity.PlayerDeathEvent; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.PlayerInventory; @@ -50,17 +53,24 @@ public class InvseeServiceImpl implements InvseeService { /** * First UUID is inspected player, second value is the inspector's change waiting to be written to the inventory */ - private static final HashMap pendingChanges = new HashMap<>(); + private static final HashMap pendingChanges = new HashMap<>(); private final NoticeService noticeService; private final MiniMessage miniMessage; private final Scheduler scheduler; + private final TranslationManager translationManager; @Inject - public InvseeServiceImpl(NoticeService noticeService, MiniMessage miniMessage, Scheduler scheduler) { + public InvseeServiceImpl( + NoticeService noticeService, + MiniMessage miniMessage, + Scheduler scheduler, + TranslationManager translationManager + ) { this.noticeService = noticeService; this.miniMessage = miniMessage; this.scheduler = scheduler; + this.translationManager = translationManager; } @Override @@ -77,8 +87,11 @@ public void inspectPlayer(UUID requester, UUID target) { Inventory targetInventory = targetPlayer.getInventory(); + String title = this.translationManager.getMessages().invsee().inventoryTitle() + .replace("{PLAYER}", targetPlayer.getName()); + Gui gui = Gui.gui() - .title(miniMessage.deserialize("Inspecting " + targetPlayer.getName())) + .title(this.miniMessage.deserialize(title)) .rows(GUI_ROWS) .create(); @@ -153,6 +166,33 @@ public void closeInspections(UUID target) { inspectionStatus.remove(target); } + /** + * The pending change can't be discarded on death - vanilla already moved the item to the inspector, + * so it would be both dropped and kept by the inspector. Instead, the change is written + * and the drops (already collected from the inventory) are corrected by the same difference. + */ + @Override + public void closeInspectionsOnDeath(PlayerDeathEvent event) { + UUID target = event.getEntity().getUniqueId(); + List changes = this.flushPendingChange(target); + + if (!event.getKeepInventory()) { + List drops = event.getDrops(); + + for (SlotChange change : changes) { + if (change.previous() != null) { + drops.remove(change.previous()); + } + + if (change.current() != null && !change.current().containsEnchantment(Enchantment.VANISHING_CURSE)) { + drops.add(change.current().clone()); + } + } + } + + this.closeInspections(target); + } + /** * Runs before vanilla applies the inspector's click or drag. Only one change per inventory * is allowed at a time - the inventory stays {@link InvseeStatus#BLOCKED} until the change is written @@ -179,19 +219,19 @@ private void handleInspectorAction(Gui gui, UUID target, Cancellable event) { inspectionStatus.put(target, InvseeStatus.BLOCKED); ItemStack[] snapshot = this.snapshot(guiInventory, targetInventory.getSize()); - pendingChanges.put(target, () -> this.applyChanges(gui, target, snapshot)); + pendingChanges.put(target, new PendingChange(gui, snapshot)); this.scheduler.run(() -> this.flushPendingChange(target)); } - private void flushPendingChange(UUID target) { - Runnable pendingChange = pendingChanges.remove(target); + private List flushPendingChange(UUID target) { + PendingChange pendingChange = pendingChanges.remove(target); if (pendingChange == null) { - return; + return List.of(); } - pendingChange.run(); + List changes = this.applyChanges(pendingChange.gui(), target, pendingChange.snapshot()); if (activeGuis.containsKey(target)) { inspectionStatus.put(target, InvseeStatus.FREE); @@ -199,38 +239,40 @@ private void flushPendingChange(UUID target) { else { inspectionStatus.remove(target); } + + return changes; } /** * Writes only the slots the inspector changed, so changes made to other slots in the meantime are not reverted. */ - private void applyChanges(Gui gui, UUID target, ItemStack[] snapshot) { + private List applyChanges(Gui gui, UUID target, ItemStack[] snapshot) { Player targetPlayer = Bukkit.getPlayer(target); if (targetPlayer == null) { - return; + return List.of(); } PlayerInventory targetInventory = targetPlayer.getInventory(); Inventory guiInventory = gui.getInventory(); + List changes = new ArrayList<>(); for (int slot = 0; slot < snapshot.length; slot++) { ItemStack current = cloneOrNull(guiInventory.getItem(slot)); if (!Objects.equals(current, snapshot[slot])) { targetInventory.setItem(slot, current); + changes.add(new SlotChange(snapshot[slot], current)); } } - List guis = activeGuis.get(target); - - if (guis == null) { - return; - } + List guis = activeGuis.getOrDefault(target, List.of()); for (Gui activeGui : guis) { this.fillGui(activeGui, targetInventory); } + + return changes; } /** @@ -313,4 +355,13 @@ private void updateOrPutInspectionStatus(UUID target) { inspectionStatus.put(target, InvseeStatus.FREE); } } + + /** + * Inspector's change waiting to be written - the GUI it was made in and the GUI contents from before the change. + */ + private record PendingChange(Gui gui, ItemStack[] snapshot) { + } + + private record SlotChange(ItemStack previous, ItemStack current) { + } } diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/listeners/InvseeInventoryController.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/listeners/InvseeInventoryController.java index 08a1038c6..0af8d985b 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/listeners/InvseeInventoryController.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/listeners/InvseeInventoryController.java @@ -113,9 +113,10 @@ void onArmorChange(PlayerArmorChangeEvent event) { this.scheduleRefresh(event.getPlayer()); } - @EventHandler(priority = EventPriority.MONITOR) + // LOWEST, so other plugins already see drops corrected by the pending inspector change + @EventHandler(priority = EventPriority.LOWEST) void onDeath(PlayerDeathEvent event) { - this.scheduleRefresh(event.getEntity()); + this.invseeService.closeInspectionsOnDeath(event); } @EventHandler(priority = EventPriority.MONITOR) From 2184c0603ebdc2e725183c40c0325026988171f8 Mon Sep 17 00:00:00 2001 From: qbiter Date: Sun, 25 Oct 2026 13:20:20 +0100 Subject: [PATCH 09/15] change: rewrite triumph GUI to bukkit holders --- .../feature/invsee/InvseeInventoryHolder.java | 73 ++++++ .../core/feature/invsee/InvseeService.java | 7 + .../feature/invsee/InvseeServiceImpl.java | 213 ++++++++---------- .../invsee/listeners/InvseeGuiController.java | 83 +++++++ 4 files changed, 259 insertions(+), 117 deletions(-) create mode 100644 eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeInventoryHolder.java create mode 100644 eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/listeners/InvseeGuiController.java diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeInventoryHolder.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeInventoryHolder.java new file mode 100644 index 000000000..ab4dbc77c --- /dev/null +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeInventoryHolder.java @@ -0,0 +1,73 @@ +package com.eternalcode.core.feature.invsee; + +import com.cryptomorin.xseries.XMaterial; +import com.eternalcode.core.util.MaterialUtil; +import java.util.UUID; +import net.kyori.adventure.text.Component; +import org.bukkit.Material; +import org.bukkit.Server; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryHolder; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.jetbrains.annotations.NotNull; + +/** + * Inventory showing a copy of the inspected player's inventory. + * Slots {@code 0 - contentSize} mirror the player's inventory slots, the remaining ones are blocked by a filler. + */ +public class InvseeInventoryHolder implements InventoryHolder { + + private static final int SIZE = 45; + private static final Material FILLER_MATERIAL = MaterialUtil.parseRequired(XMaterial.GRAY_STAINED_GLASS_PANE); + private static final ItemStack FILLER = createFiller(); + + private final UUID target; + private final boolean editable; + private final int contentSize; + private final Inventory inventory; + + InvseeInventoryHolder(Server server, UUID target, boolean editable, int contentSize, Component title) { + this.target = target; + this.editable = editable; + this.contentSize = contentSize; + this.inventory = server.createInventory(this, SIZE, title); + + for (int slot = contentSize; slot < SIZE; slot++) { + this.inventory.setItem(slot, FILLER.clone()); + } + } + + public UUID target() { + return this.target; + } + + public boolean isEditable() { + return this.editable; + } + + public int contentSize() { + return this.contentSize; + } + + public boolean isFillerSlot(int rawSlot) { + return rawSlot >= this.contentSize && rawSlot < SIZE; + } + + public static boolean isFiller(ItemStack item) { + return item != null && item.isSimilar(FILLER); + } + + @Override + public @NotNull Inventory getInventory() { + return this.inventory; + } + + private static ItemStack createFiller() { + ItemStack filler = new ItemStack(FILLER_MATERIAL); + ItemMeta meta = filler.getItemMeta(); + meta.displayName(Component.empty()); + filler.setItemMeta(meta); + return filler; + } +} diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeService.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeService.java index 6363cdae3..e0b1e5684 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeService.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeService.java @@ -1,6 +1,7 @@ package com.eternalcode.core.feature.invsee; import java.util.UUID; +import org.bukkit.event.Cancellable; import org.bukkit.event.entity.PlayerDeathEvent; public interface InvseeService { @@ -14,5 +15,11 @@ public interface InvseeService { void closeInspections(UUID target); + void closeAllInspections(); + void closeInspectionsOnDeath(PlayerDeathEvent event); + + void handleInspectorAction(InvseeInventoryHolder holder, Cancellable event); + + void closeInspection(InvseeInventoryHolder holder, UUID viewer); } diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeServiceImpl.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeServiceImpl.java index a6cc97104..38100ba3a 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeServiceImpl.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeServiceImpl.java @@ -1,24 +1,17 @@ package com.eternalcode.core.feature.invsee; -import com.cryptomorin.xseries.XMaterial; import com.eternalcode.commons.scheduler.Scheduler; import com.eternalcode.core.injector.annotations.Inject; import com.eternalcode.core.injector.annotations.component.Service; import com.eternalcode.core.notice.NoticeService; import com.eternalcode.core.translation.TranslationManager; -import com.eternalcode.core.util.MaterialUtil; -import dev.triumphteam.gui.builder.item.ItemBuilder; -import dev.triumphteam.gui.guis.Gui; -import dev.triumphteam.gui.guis.GuiItem; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Objects; import java.util.UUID; -import net.kyori.adventure.text.Component; import net.kyori.adventure.text.minimessage.MiniMessage; -import org.bukkit.Bukkit; -import org.bukkit.Material; +import org.bukkit.Server; import org.bukkit.enchantments.Enchantment; import org.bukkit.entity.HumanEntity; import org.bukkit.entity.Player; @@ -32,8 +25,6 @@ public class InvseeServiceImpl implements InvseeService { private static final String MODIFY_PERMISSION = "eternalcore.invsee.modify"; - private static final int GUI_ROWS = 5; - private static final Material FILLER_MATERIAL = MaterialUtil.parseRequired(XMaterial.GRAY_STAINED_GLASS_PANE); /** * First UUID is the player who is inspecting, second UUID is the player being inspected @@ -46,9 +37,9 @@ public class InvseeServiceImpl implements InvseeService { private static final HashMap inspectionStatus = new HashMap<>(); /** - * First UUID is inspected, second field are GUIs of that player's inventory copy + * First UUID is inspected, second field are inventories with that player's inventory copy */ - private static final HashMap> activeGuis = new HashMap<>(); + private static final HashMap> activeInventories = new HashMap<>(); /** * First UUID is inspected player, second value is the inspector's change waiting to be written to the inventory @@ -59,24 +50,27 @@ public class InvseeServiceImpl implements InvseeService { private final MiniMessage miniMessage; private final Scheduler scheduler; private final TranslationManager translationManager; + private final Server server; @Inject public InvseeServiceImpl( NoticeService noticeService, MiniMessage miniMessage, Scheduler scheduler, - TranslationManager translationManager + TranslationManager translationManager, + Server server ) { this.noticeService = noticeService; this.miniMessage = miniMessage; this.scheduler = scheduler; this.translationManager = translationManager; + this.server = server; } @Override public void inspectPlayer(UUID requester, UUID target) { - Player player = Bukkit.getPlayer(requester); - Player targetPlayer = Bukkit.getPlayer(target); + Player player = this.server.getPlayer(requester); + Player targetPlayer = this.server.getPlayer(target); if (player == null || targetPlayer == null) { return; @@ -85,41 +79,28 @@ public void inspectPlayer(UUID requester, UUID target) { activeInsepctions.put(player.getUniqueId(), targetPlayer.getUniqueId()); this.updateOrPutInspectionStatus(targetPlayer.getUniqueId()); - Inventory targetInventory = targetPlayer.getInventory(); + PlayerInventory targetInventory = targetPlayer.getInventory(); String title = this.translationManager.getMessages().invsee().inventoryTitle() .replace("{PLAYER}", targetPlayer.getName()); - Gui gui = Gui.gui() - .title(this.miniMessage.deserialize(title)) - .rows(GUI_ROWS) - .create(); - - UUID targetUuid = targetPlayer.getUniqueId(); - this.setupFiller(gui, targetInventory.getSize()); - - if (player.hasPermission(MODIFY_PERMISSION)) { - gui.setDefaultClickAction(event -> this.handleInspectorAction(gui, targetUuid, event)); - gui.setDragAction(event -> this.handleInspectorAction(gui, targetUuid, event)); - } - else { - gui.disableAllInteractions(); - } - - activeGuis.computeIfAbsent(targetUuid, uuid -> new ArrayList<>()).add(gui); + InvseeInventoryHolder holder = new InvseeInventoryHolder( + this.server, + targetPlayer.getUniqueId(), + player.hasPermission(MODIFY_PERMISSION), + targetInventory.getSize(), + this.miniMessage.deserialize(title) + ); - gui.setCloseGuiAction(event -> { - activeInsepctions.remove(player.getUniqueId()); - this.removeGui(targetUuid, gui); - }); + this.fillInventory(holder, targetInventory); + activeInventories.computeIfAbsent(targetPlayer.getUniqueId(), uuid -> new ArrayList<>()).add(holder); - gui.open(player); - this.fillGui(gui, targetInventory); + player.openInventory(holder.getInventory()); } @Override public boolean isBeingInspected(UUID target) { - return activeGuis.containsKey(target); + return activeInventories.containsKey(target); } @Override @@ -129,20 +110,20 @@ public boolean isInventoryBlocked(UUID target) { @Override public void refreshInspection(UUID target) { - // While blocked, the pending inspector change refreshes all GUIs after it is written + // While blocked, the pending inspector change refreshes all inventories after it is written if (this.isInventoryBlocked(target)) { return; } - Player targetPlayer = Bukkit.getPlayer(target); - List guis = activeGuis.get(target); + Player targetPlayer = this.server.getPlayer(target); + List holders = activeInventories.get(target); - if (targetPlayer == null || guis == null) { + if (targetPlayer == null || holders == null) { return; } - for (Gui gui : guis) { - this.fillGui(gui, targetPlayer.getInventory()); + for (InvseeInventoryHolder holder : holders) { + this.fillInventory(holder, targetPlayer.getInventory()); } } @@ -151,14 +132,14 @@ public void closeInspections(UUID target) { // Write the pending change while the player is still online, otherwise the moved item would be duplicated this.flushPendingChange(target); - List guis = activeGuis.remove(target); + List holders = activeInventories.remove(target); - if (guis == null) { + if (holders == null) { return; } - for (Gui gui : List.copyOf(guis)) { - for (HumanEntity viewer : List.copyOf(gui.getInventory().getViewers())) { + for (InvseeInventoryHolder holder : holders) { + for (HumanEntity viewer : List.copyOf(holder.getInventory().getViewers())) { viewer.closeInventory(); } } @@ -166,6 +147,13 @@ public void closeInspections(UUID target) { inspectionStatus.remove(target); } + @Override + public void closeAllInspections() { + for (UUID target : List.copyOf(activeInventories.keySet())) { + this.closeInspections(target); + } + } + /** * The pending change can't be discarded on death - vanilla already moved the item to the inspector, * so it would be both dropped and kept by the inspector. Instead, the change is written @@ -198,8 +186,10 @@ public void closeInspectionsOnDeath(PlayerDeathEvent event) { * is allowed at a time - the inventory stays {@link InvseeStatus#BLOCKED} until the change is written * on the next tick, so other inspectors and the target itself can't take the same item. */ - private void handleInspectorAction(Gui gui, UUID target, Cancellable event) { - Player targetPlayer = Bukkit.getPlayer(target); + @Override + public void handleInspectorAction(InvseeInventoryHolder holder, Cancellable event) { + UUID target = holder.target(); + Player targetPlayer = this.server.getPlayer(target); if (targetPlayer == null || this.isInventoryBlocked(target)) { event.setCancelled(true); @@ -207,23 +197,43 @@ private void handleInspectorAction(Gui gui, UUID target, Cancellable event) { } PlayerInventory targetInventory = targetPlayer.getInventory(); - Inventory guiInventory = gui.getInventory(); - // The inventory changed since the GUI was filled (e.g. the target picked up an item this tick) - if (!this.matches(guiInventory, targetInventory)) { + // The inventory changed since it was copied (e.g. the target picked up an item this tick) + if (!this.matches(holder, targetInventory)) { event.setCancelled(true); - this.fillGui(gui, targetInventory); + this.fillInventory(holder, targetInventory); return; } inspectionStatus.put(target, InvseeStatus.BLOCKED); - - ItemStack[] snapshot = this.snapshot(guiInventory, targetInventory.getSize()); - pendingChanges.put(target, new PendingChange(gui, snapshot)); + pendingChanges.put(target, new PendingChange(holder, this.snapshot(holder))); this.scheduler.run(() -> this.flushPendingChange(target)); } + @Override + public void closeInspection(InvseeInventoryHolder holder, UUID viewer) { + activeInsepctions.remove(viewer); + + UUID target = holder.target(); + List holders = activeInventories.get(target); + + if (holders == null) { + return; + } + + holders.remove(holder); + + if (holders.isEmpty()) { + activeInventories.remove(target); + + // Keep the BLOCKED status until the pending change is written + if (!pendingChanges.containsKey(target)) { + inspectionStatus.remove(target); + } + } + } + private List flushPendingChange(UUID target) { PendingChange pendingChange = pendingChanges.remove(target); @@ -231,9 +241,9 @@ private List flushPendingChange(UUID target) { return List.of(); } - List changes = this.applyChanges(pendingChange.gui(), target, pendingChange.snapshot()); + List changes = this.applyChanges(pendingChange.holder(), pendingChange.snapshot()); - if (activeGuis.containsKey(target)) { + if (activeInventories.containsKey(target)) { inspectionStatus.put(target, InvseeStatus.FREE); } else { @@ -246,19 +256,20 @@ private List flushPendingChange(UUID target) { /** * Writes only the slots the inspector changed, so changes made to other slots in the meantime are not reverted. */ - private List applyChanges(Gui gui, UUID target, ItemStack[] snapshot) { - Player targetPlayer = Bukkit.getPlayer(target); + private List applyChanges(InvseeInventoryHolder holder, ItemStack[] snapshot) { + UUID target = holder.target(); + Player targetPlayer = this.server.getPlayer(target); if (targetPlayer == null) { return List.of(); } PlayerInventory targetInventory = targetPlayer.getInventory(); - Inventory guiInventory = gui.getInventory(); + Inventory inventory = holder.getInventory(); List changes = new ArrayList<>(); for (int slot = 0; slot < snapshot.length; slot++) { - ItemStack current = cloneOrNull(guiInventory.getItem(slot)); + ItemStack current = cloneOrNull(inventory.getItem(slot)); if (!Objects.equals(current, snapshot[slot])) { targetInventory.setItem(slot, current); @@ -266,33 +277,32 @@ private List applyChanges(Gui gui, UUID target, ItemStack[] snapshot } } - List guis = activeGuis.getOrDefault(target, List.of()); - - for (Gui activeGui : guis) { - this.fillGui(activeGui, targetInventory); + for (InvseeInventoryHolder activeHolder : activeInventories.getOrDefault(target, List.of())) { + this.fillInventory(activeHolder, targetInventory); } return changes; } /** - * Copies the target's inventory into the GUI. Items are put into the inventory directly - * (not as {@link GuiItem}) so they stay untagged and can be moved freely by the inspector. + * Copies the target's inventory into the inspection inventory as plain item copies. */ - private void fillGui(Gui gui, Inventory targetInventory) { - Inventory guiInventory = gui.getInventory(); + private void fillInventory(InvseeInventoryHolder holder, Inventory targetInventory) { + Inventory inventory = holder.getInventory(); - for (int slot = 0; slot < targetInventory.getSize(); slot++) { - guiInventory.setItem(slot, cloneOrNull(targetInventory.getItem(slot))); + for (int slot = 0; slot < holder.contentSize(); slot++) { + inventory.setItem(slot, cloneOrNull(targetInventory.getItem(slot))); } } - private boolean matches(Inventory guiInventory, Inventory targetInventory) { - for (int slot = 0; slot < targetInventory.getSize(); slot++) { - ItemStack guiItem = cloneOrNull(guiInventory.getItem(slot)); + private boolean matches(InvseeInventoryHolder holder, Inventory targetInventory) { + Inventory inventory = holder.getInventory(); + + for (int slot = 0; slot < holder.contentSize(); slot++) { + ItemStack item = cloneOrNull(inventory.getItem(slot)); ItemStack targetItem = cloneOrNull(targetInventory.getItem(slot)); - if (!Objects.equals(guiItem, targetItem)) { + if (!Objects.equals(item, targetItem)) { return false; } } @@ -300,29 +310,17 @@ private boolean matches(Inventory guiInventory, Inventory targetInventory) { return true; } - private ItemStack[] snapshot(Inventory guiInventory, int size) { - ItemStack[] snapshot = new ItemStack[size]; + private ItemStack[] snapshot(InvseeInventoryHolder holder) { + Inventory inventory = holder.getInventory(); + ItemStack[] snapshot = new ItemStack[holder.contentSize()]; - for (int slot = 0; slot < size; slot++) { - snapshot[slot] = cloneOrNull(guiInventory.getItem(slot)); + for (int slot = 0; slot < snapshot.length; slot++) { + snapshot[slot] = cloneOrNull(inventory.getItem(slot)); } return snapshot; } - /** - * Blocks the slots after the player's inventory (41-44), so items can't be placed there. - */ - private void setupFiller(Gui gui, int playerInventorySize) { - GuiItem filler = ItemBuilder.from(FILLER_MATERIAL) - .name(Component.empty()) - .asGuiItem(event -> event.setCancelled(true)); - - for (int slot = playerInventorySize; slot < GUI_ROWS * 9; slot++) { - gui.setItem(slot, filler); - } - } - private static ItemStack cloneOrNull(ItemStack item) { if (item == null || item.getType().isAir()) { return null; @@ -331,25 +329,6 @@ private static ItemStack cloneOrNull(ItemStack item) { return item.clone(); } - private void removeGui(UUID target, Gui gui) { - List guis = activeGuis.get(target); - - if (guis == null) { - return; - } - - guis.remove(gui); - - if (guis.isEmpty()) { - activeGuis.remove(target); - - // Keep the BLOCKED status until the pending change is written - if (!pendingChanges.containsKey(target)) { - inspectionStatus.remove(target); - } - } - } - private void updateOrPutInspectionStatus(UUID target) { if (!inspectionStatus.containsKey(target)) { inspectionStatus.put(target, InvseeStatus.FREE); @@ -357,9 +336,9 @@ private void updateOrPutInspectionStatus(UUID target) { } /** - * Inspector's change waiting to be written - the GUI it was made in and the GUI contents from before the change. + * Inspector's change waiting to be written - the inventory it was made in and its contents from before the change. */ - private record PendingChange(Gui gui, ItemStack[] snapshot) { + private record PendingChange(InvseeInventoryHolder holder, ItemStack[] snapshot) { } private record SlotChange(ItemStack previous, ItemStack current) { diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/listeners/InvseeGuiController.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/listeners/InvseeGuiController.java new file mode 100644 index 000000000..a0043600b --- /dev/null +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/listeners/InvseeGuiController.java @@ -0,0 +1,83 @@ +package com.eternalcode.core.feature.invsee.listeners; + +import com.eternalcode.core.feature.invsee.InvseeInventoryHolder; +import com.eternalcode.core.feature.invsee.InvseeService; +import com.eternalcode.core.injector.annotations.Inject; +import com.eternalcode.core.injector.annotations.component.Controller; +import com.eternalcode.core.publish.Subscribe; +import com.eternalcode.core.publish.event.EternalShutdownEvent; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.inventory.InventoryCloseEvent; +import org.bukkit.event.inventory.InventoryDragEvent; +import org.bukkit.inventory.Inventory; + +/** + * Handles the inspector's interactions with the {@link InvseeInventoryHolder} inventory. + */ +@Controller +public class InvseeGuiController implements Listener { + + private final InvseeService invseeService; + + @Inject + InvseeGuiController(InvseeService invseeService) { + this.invseeService = invseeService; + } + + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) + void onClick(InventoryClickEvent event) { + if (!(getHolder(event.getInventory()) instanceof InvseeInventoryHolder holder)) { + return; + } + + if (!holder.isEditable() + || holder.isFillerSlot(event.getRawSlot()) + || InvseeInventoryHolder.isFiller(event.getCurrentItem()) + || InvseeInventoryHolder.isFiller(event.getCursor()) + ) { + event.setCancelled(true); + return; + } + + // Clicking outside the inventory only drops the cursor item, the inspected inventory doesn't change + if (event.getClickedInventory() == null) { + return; + } + + this.invseeService.handleInspectorAction(holder, event); + } + + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) + void onDrag(InventoryDragEvent event) { + if (!(getHolder(event.getInventory()) instanceof InvseeInventoryHolder holder)) { + return; + } + + if (!holder.isEditable() || event.getRawSlots().stream().anyMatch(holder::isFillerSlot)) { + event.setCancelled(true); + return; + } + + this.invseeService.handleInspectorAction(holder, event); + } + + @EventHandler(priority = EventPriority.MONITOR) + void onClose(InventoryCloseEvent event) { + if (getHolder(event.getInventory()) instanceof InvseeInventoryHolder holder) { + this.invseeService.closeInspection(holder, event.getPlayer().getUniqueId()); + } + } + + // Inventories with item copies can't stay open after the plugin is disabled, the copies could be taken out + @Subscribe + void onShutdown(EternalShutdownEvent event) { + this.invseeService.closeAllInspections(); + } + + private static Object getHolder(Inventory inventory) { + return inventory.getHolder(false); + } +} From 8642cb7c9166f0bb8960d2b7a731b6c413b2d450 Mon Sep 17 00:00:00 2001 From: qbiter Date: Sun, 25 Oct 2026 13:30:30 +0100 Subject: [PATCH 10/15] change: remove comments --- .../feature/invsee/InvseeInventoryHolder.java | 4 --- .../feature/invsee/InvseeServiceImpl.java | 35 ------------------- .../core/feature/invsee/InvseeStatus.java | 6 ---- .../invsee/listeners/InvseeGuiController.java | 5 --- .../listeners/InvseeInventoryController.java | 11 ------ 5 files changed, 61 deletions(-) diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeInventoryHolder.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeInventoryHolder.java index ab4dbc77c..d68e986ae 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeInventoryHolder.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeInventoryHolder.java @@ -12,10 +12,6 @@ import org.bukkit.inventory.meta.ItemMeta; import org.jetbrains.annotations.NotNull; -/** - * Inventory showing a copy of the inspected player's inventory. - * Slots {@code 0 - contentSize} mirror the player's inventory slots, the remaining ones are blocked by a filler. - */ public class InvseeInventoryHolder implements InventoryHolder { private static final int SIZE = 45; diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeServiceImpl.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeServiceImpl.java index 38100ba3a..1735a666f 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeServiceImpl.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeServiceImpl.java @@ -26,24 +26,12 @@ public class InvseeServiceImpl implements InvseeService { private static final String MODIFY_PERMISSION = "eternalcore.invsee.modify"; - /** - * First UUID is the player who is inspecting, second UUID is the player being inspected - */ private static final HashMap activeInsepctions = new HashMap<>(); - /** - * First UUID is inspected player, second valie is {@link InvseeStatus} which contains the status of the inspection - */ private static final HashMap inspectionStatus = new HashMap<>(); - /** - * First UUID is inspected, second field are inventories with that player's inventory copy - */ private static final HashMap> activeInventories = new HashMap<>(); - /** - * First UUID is inspected player, second value is the inspector's change waiting to be written to the inventory - */ private static final HashMap pendingChanges = new HashMap<>(); private final NoticeService noticeService; @@ -110,7 +98,6 @@ public boolean isInventoryBlocked(UUID target) { @Override public void refreshInspection(UUID target) { - // While blocked, the pending inspector change refreshes all inventories after it is written if (this.isInventoryBlocked(target)) { return; } @@ -129,7 +116,6 @@ public void refreshInspection(UUID target) { @Override public void closeInspections(UUID target) { - // Write the pending change while the player is still online, otherwise the moved item would be duplicated this.flushPendingChange(target); List holders = activeInventories.remove(target); @@ -154,11 +140,6 @@ public void closeAllInspections() { } } - /** - * The pending change can't be discarded on death - vanilla already moved the item to the inspector, - * so it would be both dropped and kept by the inspector. Instead, the change is written - * and the drops (already collected from the inventory) are corrected by the same difference. - */ @Override public void closeInspectionsOnDeath(PlayerDeathEvent event) { UUID target = event.getEntity().getUniqueId(); @@ -181,11 +162,6 @@ public void closeInspectionsOnDeath(PlayerDeathEvent event) { this.closeInspections(target); } - /** - * Runs before vanilla applies the inspector's click or drag. Only one change per inventory - * is allowed at a time - the inventory stays {@link InvseeStatus#BLOCKED} until the change is written - * on the next tick, so other inspectors and the target itself can't take the same item. - */ @Override public void handleInspectorAction(InvseeInventoryHolder holder, Cancellable event) { UUID target = holder.target(); @@ -198,7 +174,6 @@ public void handleInspectorAction(InvseeInventoryHolder holder, Cancellable even PlayerInventory targetInventory = targetPlayer.getInventory(); - // The inventory changed since it was copied (e.g. the target picked up an item this tick) if (!this.matches(holder, targetInventory)) { event.setCancelled(true); this.fillInventory(holder, targetInventory); @@ -227,7 +202,6 @@ public void closeInspection(InvseeInventoryHolder holder, UUID viewer) { if (holders.isEmpty()) { activeInventories.remove(target); - // Keep the BLOCKED status until the pending change is written if (!pendingChanges.containsKey(target)) { inspectionStatus.remove(target); } @@ -253,9 +227,6 @@ private List flushPendingChange(UUID target) { return changes; } - /** - * Writes only the slots the inspector changed, so changes made to other slots in the meantime are not reverted. - */ private List applyChanges(InvseeInventoryHolder holder, ItemStack[] snapshot) { UUID target = holder.target(); Player targetPlayer = this.server.getPlayer(target); @@ -284,9 +255,6 @@ private List applyChanges(InvseeInventoryHolder holder, ItemStack[] return changes; } - /** - * Copies the target's inventory into the inspection inventory as plain item copies. - */ private void fillInventory(InvseeInventoryHolder holder, Inventory targetInventory) { Inventory inventory = holder.getInventory(); @@ -335,9 +303,6 @@ private void updateOrPutInspectionStatus(UUID target) { } } - /** - * Inspector's change waiting to be written - the inventory it was made in and its contents from before the change. - */ private record PendingChange(InvseeInventoryHolder holder, ItemStack[] snapshot) { } diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeStatus.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeStatus.java index 811ef9005..73abda459 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeStatus.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeStatus.java @@ -1,12 +1,6 @@ package com.eternalcode.core.feature.invsee; public enum InvseeStatus { - /** - * Nobody is changing the inventory, inspectors can modify it. - */ FREE, - /** - * An inspector's change is waiting to be written - the target and other inspectors can't modify the inventory. - */ BLOCKED } diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/listeners/InvseeGuiController.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/listeners/InvseeGuiController.java index a0043600b..0d4022961 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/listeners/InvseeGuiController.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/listeners/InvseeGuiController.java @@ -14,9 +14,6 @@ import org.bukkit.event.inventory.InventoryDragEvent; import org.bukkit.inventory.Inventory; -/** - * Handles the inspector's interactions with the {@link InvseeInventoryHolder} inventory. - */ @Controller public class InvseeGuiController implements Listener { @@ -42,7 +39,6 @@ void onClick(InventoryClickEvent event) { return; } - // Clicking outside the inventory only drops the cursor item, the inspected inventory doesn't change if (event.getClickedInventory() == null) { return; } @@ -71,7 +67,6 @@ void onClose(InventoryCloseEvent event) { } } - // Inventories with item copies can't stay open after the plugin is disabled, the copies could be taken out @Subscribe void onShutdown(EternalShutdownEvent event) { this.invseeService.closeAllInspections(); diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/listeners/InvseeInventoryController.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/listeners/InvseeInventoryController.java index 0af8d985b..600882870 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/listeners/InvseeInventoryController.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/listeners/InvseeInventoryController.java @@ -25,15 +25,6 @@ import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.event.player.PlayerSwapHandItemsEvent; -/** - * Keeps invsee GUIs in sync with the inspected player's inventory. - *

- * While an inspector's change is pending ({@link com.eternalcode.core.feature.invsee.InvseeStatus#BLOCKED}), - * the inspected player can't modify their own inventory, so the same item can't end up in both inventories. - *

- * Most of these events fire before the inventory is actually modified, - * so the refresh is scheduled for the next tick. - */ @Controller public class InvseeInventoryController implements Listener { @@ -92,7 +83,6 @@ void onItemDamage(PlayerItemDamageEvent event) { this.handleChange(event.getPlayer(), event); } - // Right click in air is fired as already cancelled, but the item is still used, so cancelled events are not ignored @EventHandler(priority = EventPriority.HIGHEST) void onInteract(PlayerInteractEvent event) { this.handleChange(event.getPlayer(), event); @@ -113,7 +103,6 @@ void onArmorChange(PlayerArmorChangeEvent event) { this.scheduleRefresh(event.getPlayer()); } - // LOWEST, so other plugins already see drops corrected by the pending inspector change @EventHandler(priority = EventPriority.LOWEST) void onDeath(PlayerDeathEvent event) { this.invseeService.closeInspectionsOnDeath(event); From 2f8f7324e70c2c16b7a42bcb8c8f910a38ec21e7 Mon Sep 17 00:00:00 2001 From: qbiter Date: Sun, 25 Oct 2026 13:51:46 +0100 Subject: [PATCH 11/15] change: method to non static + this --- .../feature/invsee/listeners/InvseeGuiController.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/listeners/InvseeGuiController.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/listeners/InvseeGuiController.java index 0d4022961..a7f41bd52 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/listeners/InvseeGuiController.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/listeners/InvseeGuiController.java @@ -26,7 +26,7 @@ public class InvseeGuiController implements Listener { @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) void onClick(InventoryClickEvent event) { - if (!(getHolder(event.getInventory()) instanceof InvseeInventoryHolder holder)) { + if (!(this.getHolder(event.getInventory()) instanceof InvseeInventoryHolder holder)) { return; } @@ -48,7 +48,7 @@ void onClick(InventoryClickEvent event) { @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) void onDrag(InventoryDragEvent event) { - if (!(getHolder(event.getInventory()) instanceof InvseeInventoryHolder holder)) { + if (!(this.getHolder(event.getInventory()) instanceof InvseeInventoryHolder holder)) { return; } @@ -62,7 +62,7 @@ void onDrag(InventoryDragEvent event) { @EventHandler(priority = EventPriority.MONITOR) void onClose(InventoryCloseEvent event) { - if (getHolder(event.getInventory()) instanceof InvseeInventoryHolder holder) { + if (this.getHolder(event.getInventory()) instanceof InvseeInventoryHolder holder) { this.invseeService.closeInspection(holder, event.getPlayer().getUniqueId()); } } @@ -72,7 +72,7 @@ void onShutdown(EternalShutdownEvent event) { this.invseeService.closeAllInspections(); } - private static Object getHolder(Inventory inventory) { + private Object getHolder(Inventory inventory) { return inventory.getHolder(false); } } From 845921b24a8941534a62f9319c90379788788e24 Mon Sep 17 00:00:00 2001 From: qbiter Date: Sun, 25 Oct 2026 14:26:30 +0100 Subject: [PATCH 12/15] add: message to inspector on inspected death --- .../core/feature/invsee/InvseeServiceImpl.java | 16 ++++++++++++++++ .../invsee/messages/ENInvseeMessages.java | 2 ++ .../feature/invsee/messages/InvseeMessages.java | 2 ++ .../invsee/messages/PLInvseeMessages.java | 2 ++ 4 files changed, 22 insertions(+) diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeServiceImpl.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeServiceImpl.java index 1735a666f..fc6deadf9 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeServiceImpl.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeServiceImpl.java @@ -159,7 +159,23 @@ public void closeInspectionsOnDeath(PlayerDeathEvent event) { } } + List viewers = new ArrayList<>(); + + for (InvseeInventoryHolder holder : activeInventories.getOrDefault(target, List.of())) { + for (HumanEntity viewer : holder.getInventory().getViewers()) { + viewers.add(viewer.getUniqueId()); + } + } + this.closeInspections(target); + + for (UUID viewer : viewers) { + this.noticeService.create() + .player(viewer) + .notice(translation -> translation.invsee().inspectedPlayerDied()) + .placeholder("{PLAYER}", event.getEntity().getName()) + .send(); + } } @Override diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/messages/ENInvseeMessages.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/messages/ENInvseeMessages.java index c89428185..705b1a235 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/messages/ENInvseeMessages.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/messages/ENInvseeMessages.java @@ -11,6 +11,8 @@ public class ENInvseeMessages extends OkaeriConfig implements InvseeMessages { Notice cannotInspectSelf = Notice.chat("✘ You can't inspect your inventory!"); + Notice inspectedPlayerDied = Notice.chat("✘ The player {PLAYER} has died, so you can't inspect his inventory!"); + @Comment("# {PLAYER} - Inspected player") String inventoryTitle = "Inventory of {PLAYER}"; } diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/messages/InvseeMessages.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/messages/InvseeMessages.java index 20bd99e4c..1ee4b3c17 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/messages/InvseeMessages.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/messages/InvseeMessages.java @@ -5,5 +5,7 @@ public interface InvseeMessages { Notice cannotInspectSelf(); + Notice inspectedPlayerDied(); + String inventoryTitle(); } diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/messages/PLInvseeMessages.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/messages/PLInvseeMessages.java index 8b95f1c94..269c23001 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/messages/PLInvseeMessages.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/messages/PLInvseeMessages.java @@ -11,6 +11,8 @@ public class PLInvseeMessages extends OkaeriConfig implements InvseeMessages { Notice cannotInspectSelf = Notice.chat("✘ Nie możesz sprawdzić samego siebie!"); + Notice inspectedPlayerDied = Notice.chat("✘ Gracz {PLAYER} umarł, więc nie możesz sprawdzić jego ekwipunku!"); + @Comment("# {PLAYER} - Sprawdzany gracz") String inventoryTitle = "Ekwipunek gracza {PLAYER}"; } From 02191e7c40f5944395a1f05a661b6cde770afe16 Mon Sep 17 00:00:00 2001 From: qbiter Date: Sun, 25 Oct 2026 14:27:44 +0100 Subject: [PATCH 13/15] add: message to inspector on inspected death --- .../core/feature/invsee/messages/ENInvseeMessages.java | 1 + .../core/feature/invsee/messages/PLInvseeMessages.java | 1 + 2 files changed, 2 insertions(+) diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/messages/ENInvseeMessages.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/messages/ENInvseeMessages.java index 705b1a235..e0b2ab703 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/messages/ENInvseeMessages.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/messages/ENInvseeMessages.java @@ -11,6 +11,7 @@ public class ENInvseeMessages extends OkaeriConfig implements InvseeMessages { Notice cannotInspectSelf = Notice.chat("✘ You can't inspect your inventory!"); + @Comment("# {PLAYER} - Inspected player") Notice inspectedPlayerDied = Notice.chat("✘ The player {PLAYER} has died, so you can't inspect his inventory!"); @Comment("# {PLAYER} - Inspected player") diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/messages/PLInvseeMessages.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/messages/PLInvseeMessages.java index 269c23001..118714114 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/messages/PLInvseeMessages.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/messages/PLInvseeMessages.java @@ -11,6 +11,7 @@ public class PLInvseeMessages extends OkaeriConfig implements InvseeMessages { Notice cannotInspectSelf = Notice.chat("✘ Nie możesz sprawdzić samego siebie!"); + @Comment("# {PLAYER} - Inspected player") Notice inspectedPlayerDied = Notice.chat("✘ Gracz {PLAYER} umarł, więc nie możesz sprawdzić jego ekwipunku!"); @Comment("# {PLAYER} - Sprawdzany gracz") From da9fe6fe17a7e53ab97588d084258f768fb21207 Mon Sep 17 00:00:00 2001 From: qbiter Date: Sun, 25 Oct 2026 15:13:57 +0100 Subject: [PATCH 14/15] add: implement armor slot indicators in inventory GUI --- .../feature/invsee/InvseeInventoryHolder.java | 93 ++++++++++++++++--- .../feature/invsee/InvseeServiceImpl.java | 55 ++++++++--- .../invsee/listeners/InvseeGuiController.java | 8 +- .../invsee/messages/ENInvseeMessages.java | 7 ++ .../invsee/messages/InvseeMessages.java | 10 ++ .../invsee/messages/PLInvseeMessages.java | 7 ++ 6 files changed, 153 insertions(+), 27 deletions(-) diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeInventoryHolder.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeInventoryHolder.java index d68e986ae..93d0f9538 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeInventoryHolder.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeInventoryHolder.java @@ -2,6 +2,8 @@ import com.cryptomorin.xseries.XMaterial; import com.eternalcode.core.util.MaterialUtil; +import java.util.Arrays; +import java.util.List; import java.util.UUID; import net.kyori.adventure.text.Component; import org.bukkit.Material; @@ -12,25 +14,57 @@ import org.bukkit.inventory.meta.ItemMeta; import org.jetbrains.annotations.NotNull; +/** + * Layout: + *

+ * rows 1-4 | player's inventory (player slots 0-35)
+ * row 5    | slot indicators above the armor and offhand slots, filler
+ * row 6    | helmet, chestplate, leggings, boots, filler, filler, offhand, filler, filler
+ * 
+ */ public class InvseeInventoryHolder implements InventoryHolder { - private static final int SIZE = 45; + public static final int PLAYER_INVENTORY_SIZE = 41; + + private static final int SIZE = 54; + private static final int STORAGE_SIZE = 36; + private static final int NO_PLAYER_SLOT = -1; + + private static final int HELMET_SLOT = 39; + private static final int CHESTPLATE_SLOT = 38; + private static final int LEGGINGS_SLOT = 37; + private static final int BOOTS_SLOT = 36; + private static final int OFFHAND_SLOT = 40; + + private static final int[] INDICATOR_SLOTS = { 36, 37, 38, 39, 42 }; + private static final int ARMOR_ROW_START = 45; + private static final int OFFHAND_GUI_SLOT = 51; + + private static final int[] PLAYER_SLOT_BY_GUI_SLOT = createSlotMapping(); + private static final int[] GUI_SLOT_BY_PLAYER_SLOT = createReverseSlotMapping(); + private static final Material FILLER_MATERIAL = MaterialUtil.parseRequired(XMaterial.GRAY_STAINED_GLASS_PANE); private static final ItemStack FILLER = createFiller(); private final UUID target; private final boolean editable; - private final int contentSize; + private final List slotIndicators; private final Inventory inventory; - InvseeInventoryHolder(Server server, UUID target, boolean editable, int contentSize, Component title) { + InvseeInventoryHolder(Server server, UUID target, boolean editable, Component title, List slotIndicators) { this.target = target; this.editable = editable; - this.contentSize = contentSize; + this.slotIndicators = List.copyOf(slotIndicators); this.inventory = server.createInventory(this, SIZE, title); - for (int slot = contentSize; slot < SIZE; slot++) { - this.inventory.setItem(slot, FILLER.clone()); + for (int slot = 0; slot < SIZE; slot++) { + if (PLAYER_SLOT_BY_GUI_SLOT[slot] == NO_PLAYER_SLOT) { + this.inventory.setItem(slot, FILLER.clone()); + } + } + + for (int index = 0; index < this.slotIndicators.size(); index++) { + this.inventory.setItem(INDICATOR_SLOTS[index], this.slotIndicators.get(index).clone()); } } @@ -42,16 +76,20 @@ public boolean isEditable() { return this.editable; } - public int contentSize() { - return this.contentSize; + public int guiSlot(int playerSlot) { + return GUI_SLOT_BY_PLAYER_SLOT[playerSlot]; } - public boolean isFillerSlot(int rawSlot) { - return rawSlot >= this.contentSize && rawSlot < SIZE; + public boolean isLockedSlot(int rawSlot) { + return rawSlot >= 0 && rawSlot < SIZE && PLAYER_SLOT_BY_GUI_SLOT[rawSlot] == NO_PLAYER_SLOT; } - public static boolean isFiller(ItemStack item) { - return item != null && item.isSimilar(FILLER); + public boolean isDecoration(ItemStack item) { + if (item == null) { + return false; + } + + return item.isSimilar(FILLER) || this.slotIndicators.stream().anyMatch(item::isSimilar); } @Override @@ -59,6 +97,37 @@ public static boolean isFiller(ItemStack item) { return this.inventory; } + private static int[] createSlotMapping() { + int[] mapping = new int[SIZE]; + Arrays.fill(mapping, NO_PLAYER_SLOT); + + for (int slot = 0; slot < STORAGE_SIZE; slot++) { + mapping[slot] = slot; + } + + mapping[ARMOR_ROW_START] = HELMET_SLOT; + mapping[ARMOR_ROW_START + 1] = CHESTPLATE_SLOT; + mapping[ARMOR_ROW_START + 2] = LEGGINGS_SLOT; + mapping[ARMOR_ROW_START + 3] = BOOTS_SLOT; + mapping[OFFHAND_GUI_SLOT] = OFFHAND_SLOT; + + return mapping; + } + + private static int[] createReverseSlotMapping() { + int[] mapping = new int[PLAYER_INVENTORY_SIZE]; + + for (int guiSlot = 0; guiSlot < SIZE; guiSlot++) { + int playerSlot = PLAYER_SLOT_BY_GUI_SLOT[guiSlot]; + + if (playerSlot != NO_PLAYER_SLOT) { + mapping[playerSlot] = guiSlot; + } + } + + return mapping; + } + private static ItemStack createFiller() { ItemStack filler = new ItemStack(FILLER_MATERIAL); ItemMeta meta = filler.getItemMeta(); diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeServiceImpl.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeServiceImpl.java index fc6deadf9..a2394b8c5 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeServiceImpl.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeServiceImpl.java @@ -1,16 +1,22 @@ package com.eternalcode.core.feature.invsee; +import com.cryptomorin.xseries.XMaterial; +import com.eternalcode.commons.adventure.AdventureUtil; import com.eternalcode.commons.scheduler.Scheduler; +import com.eternalcode.core.feature.invsee.messages.InvseeMessages; import com.eternalcode.core.injector.annotations.Inject; import com.eternalcode.core.injector.annotations.component.Service; import com.eternalcode.core.notice.NoticeService; import com.eternalcode.core.translation.TranslationManager; +import com.eternalcode.core.util.MaterialUtil; +import com.google.common.collect.ImmutableMultimap; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Objects; import java.util.UUID; import net.kyori.adventure.text.minimessage.MiniMessage; +import org.bukkit.Material; import org.bukkit.Server; import org.bukkit.enchantments.Enchantment; import org.bukkit.entity.HumanEntity; @@ -18,14 +24,22 @@ import org.bukkit.event.Cancellable; import org.bukkit.event.entity.PlayerDeathEvent; import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemFlag; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.PlayerInventory; +import org.bukkit.inventory.meta.ItemMeta; @Service public class InvseeServiceImpl implements InvseeService { private static final String MODIFY_PERMISSION = "eternalcore.invsee.modify"; + private static final Material HELMET_INDICATOR = MaterialUtil.parseRequired(XMaterial.LEATHER_HELMET); + private static final Material CHESTPLATE_INDICATOR = MaterialUtil.parseRequired(XMaterial.LEATHER_CHESTPLATE); + private static final Material LEGGINGS_INDICATOR = MaterialUtil.parseRequired(XMaterial.LEATHER_LEGGINGS); + private static final Material BOOTS_INDICATOR = MaterialUtil.parseRequired(XMaterial.LEATHER_BOOTS); + private static final Material OFFHAND_INDICATOR = MaterialUtil.parseRequired(XMaterial.SHIELD); + private static final HashMap activeInsepctions = new HashMap<>(); private static final HashMap inspectionStatus = new HashMap<>(); @@ -69,15 +83,21 @@ public void inspectPlayer(UUID requester, UUID target) { PlayerInventory targetInventory = targetPlayer.getInventory(); - String title = this.translationManager.getMessages().invsee().inventoryTitle() - .replace("{PLAYER}", targetPlayer.getName()); + InvseeMessages messages = this.translationManager.getMessages().invsee(); + String title = messages.inventoryTitle().replace("{PLAYER}", targetPlayer.getName()); InvseeInventoryHolder holder = new InvseeInventoryHolder( this.server, targetPlayer.getUniqueId(), player.hasPermission(MODIFY_PERMISSION), - targetInventory.getSize(), - this.miniMessage.deserialize(title) + this.miniMessage.deserialize(title), + List.of( + this.createSlotIndicator(HELMET_INDICATOR, messages.helmetSlotName()), + this.createSlotIndicator(CHESTPLATE_INDICATOR, messages.chestplateSlotName()), + this.createSlotIndicator(LEGGINGS_INDICATOR, messages.leggingsSlotName()), + this.createSlotIndicator(BOOTS_INDICATOR, messages.bootsSlotName()), + this.createSlotIndicator(OFFHAND_INDICATOR, messages.offhandSlotName()) + ) ); this.fillInventory(holder, targetInventory); @@ -256,7 +276,7 @@ private List applyChanges(InvseeInventoryHolder holder, ItemStack[] List changes = new ArrayList<>(); for (int slot = 0; slot < snapshot.length; slot++) { - ItemStack current = cloneOrNull(inventory.getItem(slot)); + ItemStack current = cloneOrNull(inventory.getItem(holder.guiSlot(slot))); if (!Objects.equals(current, snapshot[slot])) { targetInventory.setItem(slot, current); @@ -274,16 +294,16 @@ private List applyChanges(InvseeInventoryHolder holder, ItemStack[] private void fillInventory(InvseeInventoryHolder holder, Inventory targetInventory) { Inventory inventory = holder.getInventory(); - for (int slot = 0; slot < holder.contentSize(); slot++) { - inventory.setItem(slot, cloneOrNull(targetInventory.getItem(slot))); + for (int slot = 0; slot < InvseeInventoryHolder.PLAYER_INVENTORY_SIZE; slot++) { + inventory.setItem(holder.guiSlot(slot), cloneOrNull(targetInventory.getItem(slot))); } } private boolean matches(InvseeInventoryHolder holder, Inventory targetInventory) { Inventory inventory = holder.getInventory(); - for (int slot = 0; slot < holder.contentSize(); slot++) { - ItemStack item = cloneOrNull(inventory.getItem(slot)); + for (int slot = 0; slot < InvseeInventoryHolder.PLAYER_INVENTORY_SIZE; slot++) { + ItemStack item = cloneOrNull(inventory.getItem(holder.guiSlot(slot))); ItemStack targetItem = cloneOrNull(targetInventory.getItem(slot)); if (!Objects.equals(item, targetItem)) { @@ -296,15 +316,28 @@ private boolean matches(InvseeInventoryHolder holder, Inventory targetInventory) private ItemStack[] snapshot(InvseeInventoryHolder holder) { Inventory inventory = holder.getInventory(); - ItemStack[] snapshot = new ItemStack[holder.contentSize()]; + ItemStack[] snapshot = new ItemStack[InvseeInventoryHolder.PLAYER_INVENTORY_SIZE]; for (int slot = 0; slot < snapshot.length; slot++) { - snapshot[slot] = cloneOrNull(inventory.getItem(slot)); + snapshot[slot] = cloneOrNull(inventory.getItem(holder.guiSlot(slot))); } return snapshot; } + private ItemStack createSlotIndicator(Material material, String name) { + ItemStack indicator = new ItemStack(material); + ItemMeta meta = indicator.getItemMeta(); + + meta.displayName(AdventureUtil.resetItalic(this.miniMessage.deserialize(name))); + meta.setEnchantmentGlintOverride(true); + meta.setAttributeModifiers(ImmutableMultimap.of()); + meta.addItemFlags(ItemFlag.values()); + + indicator.setItemMeta(meta); + return indicator; + } + private static ItemStack cloneOrNull(ItemStack item) { if (item == null || item.getType().isAir()) { return null; diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/listeners/InvseeGuiController.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/listeners/InvseeGuiController.java index a7f41bd52..65e9ad8b6 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/listeners/InvseeGuiController.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/listeners/InvseeGuiController.java @@ -31,9 +31,9 @@ void onClick(InventoryClickEvent event) { } if (!holder.isEditable() - || holder.isFillerSlot(event.getRawSlot()) - || InvseeInventoryHolder.isFiller(event.getCurrentItem()) - || InvseeInventoryHolder.isFiller(event.getCursor()) + || holder.isLockedSlot(event.getRawSlot()) + || holder.isDecoration(event.getCurrentItem()) + || holder.isDecoration(event.getCursor()) ) { event.setCancelled(true); return; @@ -52,7 +52,7 @@ void onDrag(InventoryDragEvent event) { return; } - if (!holder.isEditable() || event.getRawSlots().stream().anyMatch(holder::isFillerSlot)) { + if (!holder.isEditable() || event.getRawSlots().stream().anyMatch(holder::isLockedSlot)) { event.setCancelled(true); return; } diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/messages/ENInvseeMessages.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/messages/ENInvseeMessages.java index e0b2ab703..445e03cad 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/messages/ENInvseeMessages.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/messages/ENInvseeMessages.java @@ -16,4 +16,11 @@ public class ENInvseeMessages extends OkaeriConfig implements InvseeMessages { @Comment("# {PLAYER} - Inspected player") String inventoryTitle = "Inventory of {PLAYER}"; + + @Comment("# Names of the items marking which item is placed in the slot below them") + String helmetSlotName = "↓ Helmet ↓"; + String chestplateSlotName = "↓ Chestplate ↓"; + String leggingsSlotName = "↓ Leggings ↓"; + String bootsSlotName = "↓ Boots ↓"; + String offhandSlotName = "↓ Offhand ↓"; } diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/messages/InvseeMessages.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/messages/InvseeMessages.java index 1ee4b3c17..15c14e69e 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/messages/InvseeMessages.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/messages/InvseeMessages.java @@ -8,4 +8,14 @@ public interface InvseeMessages { Notice inspectedPlayerDied(); String inventoryTitle(); + + String helmetSlotName(); + + String chestplateSlotName(); + + String leggingsSlotName(); + + String bootsSlotName(); + + String offhandSlotName(); } diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/messages/PLInvseeMessages.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/messages/PLInvseeMessages.java index 118714114..b75bebda6 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/messages/PLInvseeMessages.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/messages/PLInvseeMessages.java @@ -16,4 +16,11 @@ public class PLInvseeMessages extends OkaeriConfig implements InvseeMessages { @Comment("# {PLAYER} - Sprawdzany gracz") String inventoryTitle = "Ekwipunek gracza {PLAYER}"; + + @Comment("# Nazwy przedmiotów oznaczających, jaki przedmiot znajduje się w slocie pod nimi") + String helmetSlotName = "↓ Hełm ↓"; + String chestplateSlotName = "↓ Napierśnik ↓"; + String leggingsSlotName = "↓ Spodnie ↓"; + String bootsSlotName = "↓ Buty ↓"; + String offhandSlotName = "↓ Druga ręka ↓"; } From e94d578f179d1f51a17c59e43af874f83b98d2cc Mon Sep 17 00:00:00 2001 From: qbiter Date: Sun, 25 Oct 2026 15:52:33 +0100 Subject: [PATCH 15/15] change: refactor closeInspection method and update variable access modifiers --- .../core/feature/invsee/InvseeService.java | 2 +- .../feature/invsee/InvseeServiceImpl.java | 68 +++++++++---------- .../invsee/listeners/InvseeGuiController.java | 2 +- 3 files changed, 34 insertions(+), 38 deletions(-) diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeService.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeService.java index e0b1e5684..7e9919c14 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeService.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeService.java @@ -21,5 +21,5 @@ public interface InvseeService { void handleInspectorAction(InvseeInventoryHolder holder, Cancellable event); - void closeInspection(InvseeInventoryHolder holder, UUID viewer); + void closeInspection(InvseeInventoryHolder holder); } diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeServiceImpl.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeServiceImpl.java index a2394b8c5..5363d6b29 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeServiceImpl.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeServiceImpl.java @@ -13,6 +13,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.UUID; import net.kyori.adventure.text.minimessage.MiniMessage; @@ -40,13 +41,11 @@ public class InvseeServiceImpl implements InvseeService { private static final Material BOOTS_INDICATOR = MaterialUtil.parseRequired(XMaterial.LEATHER_BOOTS); private static final Material OFFHAND_INDICATOR = MaterialUtil.parseRequired(XMaterial.SHIELD); - private static final HashMap activeInsepctions = new HashMap<>(); + private final Map inspectionStatus = new HashMap<>(); - private static final HashMap inspectionStatus = new HashMap<>(); + private final Map> activeInventories = new HashMap<>(); - private static final HashMap> activeInventories = new HashMap<>(); - - private static final HashMap pendingChanges = new HashMap<>(); + private final Map pendingChanges = new HashMap<>(); private final NoticeService noticeService; private final MiniMessage miniMessage; @@ -78,7 +77,6 @@ public void inspectPlayer(UUID requester, UUID target) { return; } - activeInsepctions.put(player.getUniqueId(), targetPlayer.getUniqueId()); this.updateOrPutInspectionStatus(targetPlayer.getUniqueId()); PlayerInventory targetInventory = targetPlayer.getInventory(); @@ -101,19 +99,19 @@ public void inspectPlayer(UUID requester, UUID target) { ); this.fillInventory(holder, targetInventory); - activeInventories.computeIfAbsent(targetPlayer.getUniqueId(), uuid -> new ArrayList<>()).add(holder); + this.activeInventories.computeIfAbsent(targetPlayer.getUniqueId(), uuid -> new ArrayList<>()).add(holder); player.openInventory(holder.getInventory()); } @Override public boolean isBeingInspected(UUID target) { - return activeInventories.containsKey(target); + return this.activeInventories.containsKey(target); } @Override public boolean isInventoryBlocked(UUID target) { - return inspectionStatus.get(target) == InvseeStatus.BLOCKED; + return this.inspectionStatus.get(target) == InvseeStatus.BLOCKED; } @Override @@ -123,7 +121,7 @@ public void refreshInspection(UUID target) { } Player targetPlayer = this.server.getPlayer(target); - List holders = activeInventories.get(target); + List holders = this.activeInventories.get(target); if (targetPlayer == null || holders == null) { return; @@ -138,7 +136,7 @@ public void refreshInspection(UUID target) { public void closeInspections(UUID target) { this.flushPendingChange(target); - List holders = activeInventories.remove(target); + List holders = this.activeInventories.remove(target); if (holders == null) { return; @@ -150,12 +148,12 @@ public void closeInspections(UUID target) { } } - inspectionStatus.remove(target); + this.inspectionStatus.remove(target); } @Override public void closeAllInspections() { - for (UUID target : List.copyOf(activeInventories.keySet())) { + for (UUID target : List.copyOf(this.activeInventories.keySet())) { this.closeInspections(target); } } @@ -181,7 +179,7 @@ public void closeInspectionsOnDeath(PlayerDeathEvent event) { List viewers = new ArrayList<>(); - for (InvseeInventoryHolder holder : activeInventories.getOrDefault(target, List.of())) { + for (InvseeInventoryHolder holder : this.activeInventories.getOrDefault(target, List.of())) { for (HumanEntity viewer : holder.getInventory().getViewers()) { viewers.add(viewer.getUniqueId()); } @@ -216,18 +214,16 @@ public void handleInspectorAction(InvseeInventoryHolder holder, Cancellable even return; } - inspectionStatus.put(target, InvseeStatus.BLOCKED); - pendingChanges.put(target, new PendingChange(holder, this.snapshot(holder))); + this.inspectionStatus.put(target, InvseeStatus.BLOCKED); + this.pendingChanges.put(target, new PendingChange(holder, this.snapshot(holder))); this.scheduler.run(() -> this.flushPendingChange(target)); } @Override - public void closeInspection(InvseeInventoryHolder holder, UUID viewer) { - activeInsepctions.remove(viewer); - + public void closeInspection(InvseeInventoryHolder holder) { UUID target = holder.target(); - List holders = activeInventories.get(target); + List holders = this.activeInventories.get(target); if (holders == null) { return; @@ -236,16 +232,16 @@ public void closeInspection(InvseeInventoryHolder holder, UUID viewer) { holders.remove(holder); if (holders.isEmpty()) { - activeInventories.remove(target); + this.activeInventories.remove(target); - if (!pendingChanges.containsKey(target)) { - inspectionStatus.remove(target); + if (!this.pendingChanges.containsKey(target)) { + this.inspectionStatus.remove(target); } } } private List flushPendingChange(UUID target) { - PendingChange pendingChange = pendingChanges.remove(target); + PendingChange pendingChange = this.pendingChanges.remove(target); if (pendingChange == null) { return List.of(); @@ -253,11 +249,11 @@ private List flushPendingChange(UUID target) { List changes = this.applyChanges(pendingChange.holder(), pendingChange.snapshot()); - if (activeInventories.containsKey(target)) { - inspectionStatus.put(target, InvseeStatus.FREE); + if (this.activeInventories.containsKey(target)) { + this.inspectionStatus.put(target, InvseeStatus.FREE); } else { - inspectionStatus.remove(target); + this.inspectionStatus.remove(target); } return changes; @@ -276,7 +272,7 @@ private List applyChanges(InvseeInventoryHolder holder, ItemStack[] List changes = new ArrayList<>(); for (int slot = 0; slot < snapshot.length; slot++) { - ItemStack current = cloneOrNull(inventory.getItem(holder.guiSlot(slot))); + ItemStack current = this.cloneOrNull(inventory.getItem(holder.guiSlot(slot))); if (!Objects.equals(current, snapshot[slot])) { targetInventory.setItem(slot, current); @@ -284,7 +280,7 @@ private List applyChanges(InvseeInventoryHolder holder, ItemStack[] } } - for (InvseeInventoryHolder activeHolder : activeInventories.getOrDefault(target, List.of())) { + for (InvseeInventoryHolder activeHolder : this.activeInventories.getOrDefault(target, List.of())) { this.fillInventory(activeHolder, targetInventory); } @@ -295,7 +291,7 @@ private void fillInventory(InvseeInventoryHolder holder, Inventory targetInvento Inventory inventory = holder.getInventory(); for (int slot = 0; slot < InvseeInventoryHolder.PLAYER_INVENTORY_SIZE; slot++) { - inventory.setItem(holder.guiSlot(slot), cloneOrNull(targetInventory.getItem(slot))); + inventory.setItem(holder.guiSlot(slot), this.cloneOrNull(targetInventory.getItem(slot))); } } @@ -303,8 +299,8 @@ private boolean matches(InvseeInventoryHolder holder, Inventory targetInventory) Inventory inventory = holder.getInventory(); for (int slot = 0; slot < InvseeInventoryHolder.PLAYER_INVENTORY_SIZE; slot++) { - ItemStack item = cloneOrNull(inventory.getItem(holder.guiSlot(slot))); - ItemStack targetItem = cloneOrNull(targetInventory.getItem(slot)); + ItemStack item = this.cloneOrNull(inventory.getItem(holder.guiSlot(slot))); + ItemStack targetItem = this.cloneOrNull(targetInventory.getItem(slot)); if (!Objects.equals(item, targetItem)) { return false; @@ -319,7 +315,7 @@ private ItemStack[] snapshot(InvseeInventoryHolder holder) { ItemStack[] snapshot = new ItemStack[InvseeInventoryHolder.PLAYER_INVENTORY_SIZE]; for (int slot = 0; slot < snapshot.length; slot++) { - snapshot[slot] = cloneOrNull(inventory.getItem(holder.guiSlot(slot))); + snapshot[slot] = this.cloneOrNull(inventory.getItem(holder.guiSlot(slot))); } return snapshot; @@ -338,7 +334,7 @@ private ItemStack createSlotIndicator(Material material, String name) { return indicator; } - private static ItemStack cloneOrNull(ItemStack item) { + private ItemStack cloneOrNull(ItemStack item) { if (item == null || item.getType().isAir()) { return null; } @@ -347,8 +343,8 @@ private static ItemStack cloneOrNull(ItemStack item) { } private void updateOrPutInspectionStatus(UUID target) { - if (!inspectionStatus.containsKey(target)) { - inspectionStatus.put(target, InvseeStatus.FREE); + if (!this.inspectionStatus.containsKey(target)) { + this.inspectionStatus.put(target, InvseeStatus.FREE); } } diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/listeners/InvseeGuiController.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/listeners/InvseeGuiController.java index 65e9ad8b6..3e0d6e707 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/listeners/InvseeGuiController.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/listeners/InvseeGuiController.java @@ -63,7 +63,7 @@ void onDrag(InventoryDragEvent event) { @EventHandler(priority = EventPriority.MONITOR) void onClose(InventoryCloseEvent event) { if (this.getHolder(event.getInventory()) instanceof InvseeInventoryHolder holder) { - this.invseeService.closeInspection(holder, event.getPlayer().getUniqueId()); + this.invseeService.closeInspection(holder); } }