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..5a3161b26 --- /dev/null +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeCommand.java @@ -0,0 +1,40 @@ +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()); + return; + } + + this.invseeService.inspectPlayer(senderUuid, targetUuid); + } +} 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..93d0f9538 --- /dev/null +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeInventoryHolder.java @@ -0,0 +1,138 @@ +package com.eternalcode.core.feature.invsee; + +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; +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; + +/** + * 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 { + + 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 List slotIndicators; + private final Inventory inventory; + + InvseeInventoryHolder(Server server, UUID target, boolean editable, Component title, List slotIndicators) { + this.target = target; + this.editable = editable; + this.slotIndicators = List.copyOf(slotIndicators); + this.inventory = server.createInventory(this, SIZE, title); + + 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()); + } + } + + public UUID target() { + return this.target; + } + + public boolean isEditable() { + return this.editable; + } + + public int guiSlot(int playerSlot) { + return GUI_SLOT_BY_PLAYER_SLOT[playerSlot]; + } + + public boolean isLockedSlot(int rawSlot) { + return rawSlot >= 0 && rawSlot < SIZE && PLAYER_SLOT_BY_GUI_SLOT[rawSlot] == NO_PLAYER_SLOT; + } + + public boolean isDecoration(ItemStack item) { + if (item == null) { + return false; + } + + return item.isSimilar(FILLER) || this.slotIndicators.stream().anyMatch(item::isSimilar); + } + + @Override + public @NotNull Inventory getInventory() { + 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(); + 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 new file mode 100644 index 000000000..7e9919c14 --- /dev/null +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeService.java @@ -0,0 +1,25 @@ +package com.eternalcode.core.feature.invsee; + +import java.util.UUID; +import org.bukkit.event.Cancellable; +import org.bukkit.event.entity.PlayerDeathEvent; + +public interface InvseeService { + void inspectPlayer(UUID requester, UUID target); + + boolean isBeingInspected(UUID target); + + boolean isInventoryBlocked(UUID target); + + void refreshInspection(UUID target); + + void closeInspections(UUID target); + + void closeAllInspections(); + + void closeInspectionsOnDeath(PlayerDeathEvent event); + + void handleInspectorAction(InvseeInventoryHolder holder, Cancellable event); + + 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 new file mode 100644 index 000000000..5363d6b29 --- /dev/null +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/InvseeServiceImpl.java @@ -0,0 +1,356 @@ +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.Map; +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; +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.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 final Map inspectionStatus = new HashMap<>(); + + private final Map> activeInventories = new HashMap<>(); + + private final Map pendingChanges = new HashMap<>(); + + private final NoticeService noticeService; + 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, + 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 = this.server.getPlayer(requester); + Player targetPlayer = this.server.getPlayer(target); + + if (player == null || targetPlayer == null) { + return; + } + + this.updateOrPutInspectionStatus(targetPlayer.getUniqueId()); + + PlayerInventory targetInventory = targetPlayer.getInventory(); + + 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), + 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); + this.activeInventories.computeIfAbsent(targetPlayer.getUniqueId(), uuid -> new ArrayList<>()).add(holder); + + player.openInventory(holder.getInventory()); + } + + @Override + public boolean isBeingInspected(UUID target) { + return this.activeInventories.containsKey(target); + } + + @Override + public boolean isInventoryBlocked(UUID target) { + return this.inspectionStatus.get(target) == InvseeStatus.BLOCKED; + } + + @Override + public void refreshInspection(UUID target) { + if (this.isInventoryBlocked(target)) { + return; + } + + Player targetPlayer = this.server.getPlayer(target); + List holders = this.activeInventories.get(target); + + if (targetPlayer == null || holders == null) { + return; + } + + for (InvseeInventoryHolder holder : holders) { + this.fillInventory(holder, targetPlayer.getInventory()); + } + } + + @Override + public void closeInspections(UUID target) { + this.flushPendingChange(target); + + List holders = this.activeInventories.remove(target); + + if (holders == null) { + return; + } + + for (InvseeInventoryHolder holder : holders) { + for (HumanEntity viewer : List.copyOf(holder.getInventory().getViewers())) { + viewer.closeInventory(); + } + } + + this.inspectionStatus.remove(target); + } + + @Override + public void closeAllInspections() { + for (UUID target : List.copyOf(this.activeInventories.keySet())) { + this.closeInspections(target); + } + } + + @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()); + } + } + } + + List viewers = new ArrayList<>(); + + for (InvseeInventoryHolder holder : this.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 + 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); + return; + } + + PlayerInventory targetInventory = targetPlayer.getInventory(); + + if (!this.matches(holder, targetInventory)) { + event.setCancelled(true); + this.fillInventory(holder, targetInventory); + return; + } + + 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 target = holder.target(); + List holders = this.activeInventories.get(target); + + if (holders == null) { + return; + } + + holders.remove(holder); + + if (holders.isEmpty()) { + this.activeInventories.remove(target); + + if (!this.pendingChanges.containsKey(target)) { + this.inspectionStatus.remove(target); + } + } + } + + private List flushPendingChange(UUID target) { + PendingChange pendingChange = this.pendingChanges.remove(target); + + if (pendingChange == null) { + return List.of(); + } + + List changes = this.applyChanges(pendingChange.holder(), pendingChange.snapshot()); + + if (this.activeInventories.containsKey(target)) { + this.inspectionStatus.put(target, InvseeStatus.FREE); + } + else { + this.inspectionStatus.remove(target); + } + + return changes; + } + + 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 inventory = holder.getInventory(); + List changes = new ArrayList<>(); + + for (int slot = 0; slot < snapshot.length; slot++) { + ItemStack current = this.cloneOrNull(inventory.getItem(holder.guiSlot(slot))); + + if (!Objects.equals(current, snapshot[slot])) { + targetInventory.setItem(slot, current); + changes.add(new SlotChange(snapshot[slot], current)); + } + } + + for (InvseeInventoryHolder activeHolder : this.activeInventories.getOrDefault(target, List.of())) { + this.fillInventory(activeHolder, targetInventory); + } + + return changes; + } + + private void fillInventory(InvseeInventoryHolder holder, Inventory targetInventory) { + Inventory inventory = holder.getInventory(); + + for (int slot = 0; slot < InvseeInventoryHolder.PLAYER_INVENTORY_SIZE; slot++) { + inventory.setItem(holder.guiSlot(slot), this.cloneOrNull(targetInventory.getItem(slot))); + } + } + + private boolean matches(InvseeInventoryHolder holder, Inventory targetInventory) { + Inventory inventory = holder.getInventory(); + + for (int slot = 0; slot < InvseeInventoryHolder.PLAYER_INVENTORY_SIZE; slot++) { + ItemStack item = this.cloneOrNull(inventory.getItem(holder.guiSlot(slot))); + ItemStack targetItem = this.cloneOrNull(targetInventory.getItem(slot)); + + if (!Objects.equals(item, targetItem)) { + return false; + } + } + + return true; + } + + private ItemStack[] snapshot(InvseeInventoryHolder holder) { + Inventory inventory = holder.getInventory(); + ItemStack[] snapshot = new ItemStack[InvseeInventoryHolder.PLAYER_INVENTORY_SIZE]; + + for (int slot = 0; slot < snapshot.length; slot++) { + snapshot[slot] = this.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 ItemStack cloneOrNull(ItemStack item) { + if (item == null || item.getType().isAir()) { + return null; + } + + return item.clone(); + } + + private void updateOrPutInspectionStatus(UUID target) { + if (!this.inspectionStatus.containsKey(target)) { + this.inspectionStatus.put(target, InvseeStatus.FREE); + } + } + + 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/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 +} 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..3e0d6e707 --- /dev/null +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/listeners/InvseeGuiController.java @@ -0,0 +1,78 @@ +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; + +@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 (!(this.getHolder(event.getInventory()) instanceof InvseeInventoryHolder holder)) { + return; + } + + if (!holder.isEditable() + || holder.isLockedSlot(event.getRawSlot()) + || holder.isDecoration(event.getCurrentItem()) + || holder.isDecoration(event.getCursor()) + ) { + event.setCancelled(true); + return; + } + + if (event.getClickedInventory() == null) { + return; + } + + this.invseeService.handleInspectorAction(holder, event); + } + + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) + void onDrag(InventoryDragEvent event) { + if (!(this.getHolder(event.getInventory()) instanceof InvseeInventoryHolder holder)) { + return; + } + + if (!holder.isEditable() || event.getRawSlots().stream().anyMatch(holder::isLockedSlot)) { + event.setCancelled(true); + return; + } + + this.invseeService.handleInspectorAction(holder, event); + } + + @EventHandler(priority = EventPriority.MONITOR) + void onClose(InventoryCloseEvent event) { + if (this.getHolder(event.getInventory()) instanceof InvseeInventoryHolder holder) { + this.invseeService.closeInspection(holder); + } + } + + @Subscribe + void onShutdown(EternalShutdownEvent event) { + this.invseeService.closeAllInspections(); + } + + private Object getHolder(Inventory inventory) { + return inventory.getHolder(false); + } +} 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..600882870 --- /dev/null +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/listeners/InvseeInventoryController.java @@ -0,0 +1,134 @@ +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; + +@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); + } + + @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.LOWEST) + void onDeath(PlayerDeathEvent event) { + this.invseeService.closeInspectionsOnDeath(event); + } + + @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)); + } +} 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..445e03cad --- /dev/null +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/messages/ENInvseeMessages.java @@ -0,0 +1,26 @@ +package com.eternalcode.core.feature.invsee.messages; + +import com.eternalcode.multification.notice.Notice; +import eu.okaeri.configs.OkaeriConfig; +import eu.okaeri.configs.annotation.Comment; +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!"); + + @Comment("# {PLAYER} - Inspected player") + 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}"; + + @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 new file mode 100644 index 000000000..15c14e69e --- /dev/null +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/messages/InvseeMessages.java @@ -0,0 +1,21 @@ +package com.eternalcode.core.feature.invsee.messages; + +import com.eternalcode.multification.notice.Notice; + +public interface InvseeMessages { + Notice cannotInspectSelf(); + + 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 new file mode 100644 index 000000000..b75bebda6 --- /dev/null +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/invsee/messages/PLInvseeMessages.java @@ -0,0 +1,26 @@ +package com.eternalcode.core.feature.invsee.messages; + +import com.eternalcode.multification.notice.Notice; +import eu.okaeri.configs.OkaeriConfig; +import eu.okaeri.configs.annotation.Comment; +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!"); + + @Comment("# {PLAYER} - Inspected player") + Notice inspectedPlayerDied = Notice.chat("✘ Gracz {PLAYER} umarł, więc nie możesz sprawdzić jego ekwipunku!"); + + @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 ↓"; +} 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");