diff --git a/internal/v1_16_R3/src/main/java/com/lishid/openinv/internal/v1_16_R3/PlayerDataManager.java b/internal/v1_16_R3/src/main/java/com/lishid/openinv/internal/v1_16_R3/PlayerDataManager.java index e0737c5..4c029db 100644 --- a/internal/v1_16_R3/src/main/java/com/lishid/openinv/internal/v1_16_R3/PlayerDataManager.java +++ b/internal/v1_16_R3/src/main/java/com/lishid/openinv/internal/v1_16_R3/PlayerDataManager.java @@ -16,9 +16,9 @@ package com.lishid.openinv.internal.v1_16_R3; -import com.lishid.openinv.OpenInv; import com.lishid.openinv.internal.IPlayerDataManager; import com.lishid.openinv.internal.ISpecialInventory; +import com.lishid.openinv.internal.OpenInventoryView; import com.mojang.authlib.GameProfile; import java.io.File; import java.io.FileOutputStream; @@ -28,7 +28,6 @@ import net.minecraft.server.v1_16_R3.ChatMessageType; import net.minecraft.server.v1_16_R3.Container; import net.minecraft.server.v1_16_R3.Containers; import net.minecraft.server.v1_16_R3.Entity; -import net.minecraft.server.v1_16_R3.EntityHuman; import net.minecraft.server.v1_16_R3.EntityPlayer; import net.minecraft.server.v1_16_R3.MinecraftServer; import net.minecraft.server.v1_16_R3.NBTCompressedStreamTools; @@ -36,7 +35,6 @@ import net.minecraft.server.v1_16_R3.NBTTagCompound; import net.minecraft.server.v1_16_R3.PacketPlayOutChat; import net.minecraft.server.v1_16_R3.PacketPlayOutOpenWindow; import net.minecraft.server.v1_16_R3.PlayerInteractManager; -import net.minecraft.server.v1_16_R3.PlayerInventory; import net.minecraft.server.v1_16_R3.SystemUtils; import net.minecraft.server.v1_16_R3.World; import net.minecraft.server.v1_16_R3.WorldNBTStorage; @@ -49,10 +47,7 @@ import org.bukkit.craftbukkit.v1_16_R3.CraftServer; import org.bukkit.craftbukkit.v1_16_R3.entity.CraftPlayer; import org.bukkit.craftbukkit.v1_16_R3.event.CraftEventFactory; import org.bukkit.craftbukkit.v1_16_R3.inventory.CraftContainer; -import org.bukkit.entity.HumanEntity; import org.bukkit.entity.Player; -import org.bukkit.event.inventory.InventoryType; -import org.bukkit.inventory.Inventory; import org.bukkit.inventory.InventoryView; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -195,68 +190,20 @@ public class PlayerDataManager implements IPlayerDataManager { return null; } - String title; - if (inventory instanceof SpecialEnderChest) { - HumanEntity owner = (HumanEntity) ((SpecialEnderChest) inventory).getBukkitOwner(); - title = OpenInv.getPlugin(OpenInv.class).getLocalizedMessage(player, "container.enderchest", "%player%", owner.getName()); - if (title == null) { - title = owner.getName() + "'s Ender Chest"; - } - } else if (inventory instanceof SpecialPlayerInventory) { - EntityHuman owner = ((PlayerInventory) inventory).player; - title = OpenInv.getPlugin(OpenInv.class).getLocalizedMessage(player, "container.player", "%player%", owner.getName()); - if (title == null) { - title = owner.getName() + "'s Inventory"; - } - } else { + InventoryView view = getView(player, inventory); + + if (view == null) { return player.openInventory(inventory.getBukkitInventory()); } - String finalTitle = title; - Container container = new CraftContainer(new InventoryView() { - @Override - public @NotNull Inventory getTopInventory() { - return inventory.getBukkitInventory(); - } - @Override - public @NotNull Inventory getBottomInventory() { - return player.getInventory(); - } - @Override - public @NotNull HumanEntity getPlayer() { - return player; - } - @Override - public @NotNull InventoryType getType() { - return inventory.getBukkitInventory().getType(); - } - @Override - public @NotNull String getTitle() { - return finalTitle; - } - }, nmsPlayer, nmsPlayer.nextContainerCounter()) { + Container container = new CraftContainer(view, nmsPlayer, nmsPlayer.nextContainerCounter()) { @Override public Containers getType() { - switch (inventory.getBukkitInventory().getSize()) { - case 9: - return Containers.GENERIC_9X1; - case 18: - return Containers.GENERIC_9X2; - case 27: - default: - return Containers.GENERIC_9X3; - case 36: - return Containers.GENERIC_9X4; - case 41: // PLAYER - case 45: - return Containers.GENERIC_9X5; - case 54: - return Containers.GENERIC_9X6; - } + return getContainers(inventory.getBukkitInventory().getSize()); } }; - container.setTitle(new ChatComponentText(title)); + container.setTitle(new ChatComponentText(view.getTitle())); container = CraftEventFactory.callInventoryOpenEvent(nmsPlayer, container); if (container == null) { @@ -272,6 +219,63 @@ public class PlayerDataManager implements IPlayerDataManager { } + private @Nullable InventoryView getView(Player player, ISpecialInventory inventory) { + if (inventory instanceof SpecialEnderChest) { + return new OpenInventoryView(player, inventory, "container.enderchest", "'s Ender Chest"); + } else if (inventory instanceof SpecialPlayerInventory) { + return new OpenInventoryView(player, inventory, "container.player", "'s Inventory"); + } else { + return null; + } + } + + private @NotNull Containers getContainers(int inventorySize) { + switch (inventorySize) { + case 9: + return Containers.GENERIC_9X1; + case 18: + return Containers.GENERIC_9X2; + case 36: + return Containers.GENERIC_9X4; + case 41: // PLAYER + case 45: + return Containers.GENERIC_9X5; + case 54: + return Containers.GENERIC_9X6; + case 27: + default: + return Containers.GENERIC_9X3; + } + } + + @Override + public int convertToPlayerSlot(InventoryView view, int rawSlot) { + int topSize = view.getTopInventory().getSize(); + if (topSize <= rawSlot) { + // Slot is not inside special inventory, use Bukkit logic. + return view.convertSlot(rawSlot); + } + + // Main inventory, slots 0-26 -> 9-35 + if (rawSlot < 27) { + return rawSlot + 9; + } + // Hotbar, slots 27-35 -> 0-8 + if (rawSlot < 36) { + return rawSlot - 27; + } + // Armor, slots 36-39 -> 39-36 + if (rawSlot < 40) { + return 36 + (39 - rawSlot); + } + // Off hand + if (rawSlot == 40) { + return 40; + } + // Drop slots, "out of inventory" + return -1; + } + @Override public void sendSystemMessage(@NotNull Player player, @NotNull String message) { int newline = message.indexOf('\n'); diff --git a/plugin/src/main/java/com/lishid/openinv/OpenInv.java b/plugin/src/main/java/com/lishid/openinv/OpenInv.java index ba33e94..24cd9b0 100644 --- a/plugin/src/main/java/com/lishid/openinv/OpenInv.java +++ b/plugin/src/main/java/com/lishid/openinv/OpenInv.java @@ -150,6 +150,20 @@ public class OpenInv extends JavaPlugin implements IOpenInv { } } + /** + * Convert a raw slot number into a player inventory slot number. + * + *

Note that this method is specifically for converting an ISpecialPlayerInventory slot number into a regular + * player inventory slot number. + * + * @param view the open inventory view + * @param rawSlot the raw slot in the view + * @return the converted slot number + */ + public int convertToPlayerSlot(InventoryView view, int rawSlot) { + return this.accessor.getPlayerDataManager().convertToPlayerSlot(view, rawSlot); + } + @Override public boolean disableSaving() { return this.getConfig().getBoolean("settings.disable-saving", false); diff --git a/plugin/src/main/java/com/lishid/openinv/internal/IPlayerDataManager.java b/plugin/src/main/java/com/lishid/openinv/internal/IPlayerDataManager.java index 92c86af..b217479 100644 --- a/plugin/src/main/java/com/lishid/openinv/internal/IPlayerDataManager.java +++ b/plugin/src/main/java/com/lishid/openinv/internal/IPlayerDataManager.java @@ -54,6 +54,18 @@ public interface IPlayerDataManager { @Nullable InventoryView openInventory(@NotNull Player player, @NotNull ISpecialInventory inventory); + /** + * Convert a raw slot number into a player inventory slot number. + * + *

Note that this method is specifically for converting an ISpecialPlayerInventory slot number into a regular + * player inventory slot number. + * + * @param view the open inventory view + * @param rawSlot the raw slot in the view + * @return the converted slot number + */ + int convertToPlayerSlot(InventoryView view, int rawSlot); + void sendSystemMessage(@NotNull Player player, @NotNull String message); @NotNull diff --git a/plugin/src/main/java/com/lishid/openinv/internal/OpenInventoryView.java b/plugin/src/main/java/com/lishid/openinv/internal/OpenInventoryView.java new file mode 100644 index 0000000..8fc6f09 --- /dev/null +++ b/plugin/src/main/java/com/lishid/openinv/internal/OpenInventoryView.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2011-2021 lishid. All rights reserved. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lishid.openinv.internal; + +import com.lishid.openinv.OpenInv; +import org.bukkit.entity.HumanEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryView; +import org.jetbrains.annotations.NotNull; + +public class OpenInventoryView extends InventoryView { + + private final Player player; + private final ISpecialInventory inventory; + private final String titleKey; + private final String titleDefaultSuffix; + private String title; + + public OpenInventoryView(Player player, ISpecialInventory inventory, String titleKey, String titleDefaultSuffix) { + this.player = player; + this.inventory = inventory; + this.titleKey = titleKey; + this.titleDefaultSuffix = titleDefaultSuffix; + } + + @Override + public @NotNull Inventory getTopInventory() { + return inventory.getBukkitInventory(); + } + + @Override + public @NotNull Inventory getBottomInventory() { + return getPlayer().getInventory(); + } + + @Override + public @NotNull HumanEntity getPlayer() { + return player; + } + + @Override + public @NotNull InventoryType getType() { + return inventory.getBukkitInventory().getType(); + } + + @Override + public @NotNull String getTitle() { + if (title == null) { + HumanEntity owner = getPlayer(); + + String localTitle = OpenInv.getPlugin(OpenInv.class) + .getLocalizedMessage( + owner, + titleKey, + "%player%", + owner.getName()); + if (localTitle != null) { + title = localTitle; + } else { + title = owner.getName() + titleDefaultSuffix; + } + } + + return title; + } + +} diff --git a/plugin/src/main/java/com/lishid/openinv/listeners/InventoryListener.java b/plugin/src/main/java/com/lishid/openinv/listeners/InventoryListener.java index 676635a..eee6686 100644 --- a/plugin/src/main/java/com/lishid/openinv/listeners/InventoryListener.java +++ b/plugin/src/main/java/com/lishid/openinv/listeners/InventoryListener.java @@ -20,6 +20,9 @@ import com.lishid.openinv.OpenInv; import com.lishid.openinv.internal.ISpecialPlayerInventory; import com.lishid.openinv.util.InventoryAccess; import com.lishid.openinv.util.Permissions; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; import org.bukkit.GameMode; import org.bukkit.entity.HumanEntity; import org.bukkit.entity.Player; @@ -32,8 +35,10 @@ import org.bukkit.event.inventory.InventoryCloseEvent; import org.bukkit.event.inventory.InventoryDragEvent; import org.bukkit.event.inventory.InventoryInteractEvent; import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryView; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; /** * Listener for inventory-related events to prevent modification of inventories where not allowed. @@ -67,11 +72,6 @@ public class InventoryListener implements Listener { return; } - // Only specially handle actions in the player's own inventory. - if (!event.getWhoClicked().equals(event.getView().getTopInventory().getHolder())) { - return; - } - // Safe cast - has to be a player to be the holder of a special player inventory. Player player = (Player) event.getWhoClicked(); @@ -102,14 +102,65 @@ public class InventoryListener implements Listener { @EventHandler(priority = EventPriority.LOWEST) public void onInventoryDrag(@NotNull final InventoryDragEvent event) { - handleInventoryInteract(event); + if (handleInventoryInteract(event)) { + return; + } + + InventoryView view = event.getView(); + int topSize = view.getTopInventory().getSize(); + + // Get bottom inventory active slots as player inventory slots. + Set slots = event.getRawSlots().stream() + .filter(slot -> slot >= topSize) + .map(slot -> plugin.convertToPlayerSlot(view, slot)).collect(Collectors.toSet()); + + int overlapLosses = 0; + + // Count overlapping slots. + for (Map.Entry newItem : event.getNewItems().entrySet()) { + int rawSlot = newItem.getKey(); + + // Skip bottom inventory slots. + if (rawSlot >= topSize) { + continue; + } + + int convertedSlot = plugin.convertToPlayerSlot(view, rawSlot); + + if (slots.contains(convertedSlot)) { + overlapLosses += getCountDiff(view.getItem(rawSlot), newItem.getValue()); + } + } + + // Allow no overlap to proceed as usual. + if (overlapLosses < 1) { + return; + } + + ItemStack cursor = event.getCursor(); + if (cursor != null) { + cursor.setAmount(cursor.getAmount() + overlapLosses); + } else { + cursor = event.getOldCursor().clone(); + cursor.setAmount(overlapLosses); + } + + event.setCursor(cursor); + } + + private int getCountDiff(@Nullable ItemStack original, @NotNull ItemStack result) { + if (original == null || original.getType() != result.getType()) { + return result.getAmount(); + } + + return result.getAmount() - original.getAmount(); } /** * Handle common InventoryInteractEvent functions. * * @param event the InventoryInteractEvent - * @return true unless the top inventory is an opened player inventory + * @return true unless the top inventory is the holder's own inventory */ private boolean handleInventoryInteract(@NotNull final InventoryInteractEvent event) { HumanEntity entity = event.getWhoClicked(); @@ -147,7 +198,8 @@ public class InventoryListener implements Listener { return true; } - return false; + // Only specially handle actions in the player's own inventory. + return !event.getWhoClicked().equals(event.getView().getTopInventory().getHolder()); } }