From 21f6b005cdbffb83accff17db3b7c28f94a5a1d6 Mon Sep 17 00:00:00 2001 From: Adam Date: Tue, 29 Oct 2024 10:51:24 -0400 Subject: [PATCH] Add support for 1.21.3 (#254) * Add support for 1.21.3 * Drop 1.20.4, 1.20.6, 1.21.0 * 1.20.0 is incompatible with the final changes made to inventories in 1.21.1 --- .../openinv/internal/OpenInventoryView.java | 77 -- internal/v1_20_R3/pom.xml | 77 -- .../internal/v1_20_R3/AnySilentContainer.java | 208 ----- .../internal/v1_20_R3/InternalAccessor.java | 64 -- .../openinv/internal/v1_20_R3/OpenPlayer.java | 184 ---- .../internal/v1_20_R3/PlayerManager.java | 292 ------- .../internal/v1_20_R3/SpecialEnderChest.java | 351 -------- .../v1_20_R3/SpecialPlayerInventory.java | 807 ------------------ .../openinv/internal/v1_20_R4/OpenPlayer.java | 194 ----- .../internal/v1_20_R4/PlayerManager.java | 292 ------- .../internal/v1_20_R4/SpecialEnderChest.java | 352 -------- .../v1_20_R4/SpecialPlayerInventory.java | 768 ----------------- .../container/menu/OpenChestMenu.java | 6 +- .../container/menu/OpenInventoryMenu.java | 2 +- internal/{v1_20_R4 => v1_21_R2}/pom.xml | 6 +- .../internal/v1_21_R2}/InternalAccessor.java | 29 +- .../container}/AnySilentContainer.java | 6 +- .../v1_21_R2/container/OpenEnderChest.java | 196 +++++ .../v1_21_R2/container/OpenInventory.java | 364 ++++++++ .../v1_21_R2/container/Placeholders.java | 183 ++++ .../container/bukkit/OpenDummyInventory.java | 163 ++++ .../container/bukkit/OpenPlayerInventory.java | 221 +++++ .../bukkit/OpenPlayerInventorySelf.java | 26 + .../container/menu/OpenChestMenu.java | 478 +++++++++++ .../container/menu/OpenEnderChestMenu.java | 53 ++ .../container/menu/OpenInventoryMenu.java | 262 ++++++ .../v1_21_R2/container/slot/Content.java | 69 ++ .../container/slot/ContentCrafting.java | 131 +++ .../container/slot/ContentCraftingResult.java | 48 ++ .../container/slot/ContentCursor.java | 117 +++ .../v1_21_R2/container/slot/ContentDrop.java | 88 ++ .../container/slot/ContentEquipment.java | 78 ++ .../v1_21_R2/container/slot/ContentList.java | 58 ++ .../container/slot/ContentOffHand.java | 46 + .../container/slot/ContentViewOnly.java | 56 ++ .../container/slot/SlotPlaceholder.java | 20 + .../v1_21_R2/container/slot/SlotViewOnly.java | 151 ++++ .../internal/v1_21_R2/player/OpenPlayer.java | 178 ++++ .../v1_21_R2/player/PlayerManager.java | 267 ++++++ plugin/pom.xml | 10 +- .../main/java/com/lishid/openinv/OpenInv.java | 7 - .../listener/LegacyInventoryListener.java | 234 ----- .../lishid/openinv/util/InternalAccessor.java | 15 +- pom.xml | 7 +- 44 files changed, 3301 insertions(+), 3940 deletions(-) delete mode 100644 common/src/main/java/com/lishid/openinv/internal/OpenInventoryView.java delete mode 100644 internal/v1_20_R3/pom.xml delete mode 100644 internal/v1_20_R3/src/main/java/com/lishid/openinv/internal/v1_20_R3/AnySilentContainer.java delete mode 100644 internal/v1_20_R3/src/main/java/com/lishid/openinv/internal/v1_20_R3/InternalAccessor.java delete mode 100644 internal/v1_20_R3/src/main/java/com/lishid/openinv/internal/v1_20_R3/OpenPlayer.java delete mode 100644 internal/v1_20_R3/src/main/java/com/lishid/openinv/internal/v1_20_R3/PlayerManager.java delete mode 100644 internal/v1_20_R3/src/main/java/com/lishid/openinv/internal/v1_20_R3/SpecialEnderChest.java delete mode 100644 internal/v1_20_R3/src/main/java/com/lishid/openinv/internal/v1_20_R3/SpecialPlayerInventory.java delete mode 100644 internal/v1_20_R4/src/main/java/com/lishid/openinv/internal/v1_20_R4/OpenPlayer.java delete mode 100644 internal/v1_20_R4/src/main/java/com/lishid/openinv/internal/v1_20_R4/PlayerManager.java delete mode 100644 internal/v1_20_R4/src/main/java/com/lishid/openinv/internal/v1_20_R4/SpecialEnderChest.java delete mode 100644 internal/v1_20_R4/src/main/java/com/lishid/openinv/internal/v1_20_R4/SpecialPlayerInventory.java rename internal/{v1_20_R4 => v1_21_R2}/pom.xml (94%) rename internal/{v1_20_R4/src/main/java/com/lishid/openinv/internal/v1_20_R4 => v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2}/InternalAccessor.java (62%) rename internal/{v1_20_R4/src/main/java/com/lishid/openinv/internal/v1_20_R4 => v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/container}/AnySilentContainer.java (96%) create mode 100644 internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/container/OpenEnderChest.java create mode 100644 internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/container/OpenInventory.java create mode 100644 internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/container/Placeholders.java create mode 100644 internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/container/bukkit/OpenDummyInventory.java create mode 100644 internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/container/bukkit/OpenPlayerInventory.java create mode 100644 internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/container/bukkit/OpenPlayerInventorySelf.java create mode 100644 internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/container/menu/OpenChestMenu.java create mode 100644 internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/container/menu/OpenEnderChestMenu.java create mode 100644 internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/container/menu/OpenInventoryMenu.java create mode 100644 internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/container/slot/Content.java create mode 100644 internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/container/slot/ContentCrafting.java create mode 100644 internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/container/slot/ContentCraftingResult.java create mode 100644 internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/container/slot/ContentCursor.java create mode 100644 internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/container/slot/ContentDrop.java create mode 100644 internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/container/slot/ContentEquipment.java create mode 100644 internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/container/slot/ContentList.java create mode 100644 internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/container/slot/ContentOffHand.java create mode 100644 internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/container/slot/ContentViewOnly.java create mode 100644 internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/container/slot/SlotPlaceholder.java create mode 100644 internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/container/slot/SlotViewOnly.java create mode 100644 internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/player/OpenPlayer.java create mode 100644 internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/player/PlayerManager.java delete mode 100644 plugin/src/main/java/com/lishid/openinv/listener/LegacyInventoryListener.java diff --git a/common/src/main/java/com/lishid/openinv/internal/OpenInventoryView.java b/common/src/main/java/com/lishid/openinv/internal/OpenInventoryView.java deleted file mode 100644 index 618cdc01..00000000 --- a/common/src/main/java/com/lishid/openinv/internal/OpenInventoryView.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (C) 2011-2022 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 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 @NotNull Player player; - private final @NotNull ISpecialInventory inventory; - private final @NotNull String originalTitle; - private String title; - - public OpenInventoryView( - @NotNull Player player, - @NotNull ISpecialInventory inventory, - @NotNull String originalTitle) { - this.player = player; - this.inventory = inventory; - this.originalTitle = originalTitle; - } - - @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() { - return title == null ? originalTitle : title; - } - - @Override - public @NotNull String getOriginalTitle() { - return originalTitle; - } - - @Override - public void setTitle(@NotNull String title) { - this.title = title; - } - -} diff --git a/internal/v1_20_R3/pom.xml b/internal/v1_20_R3/pom.xml deleted file mode 100644 index 54b542c7..00000000 --- a/internal/v1_20_R3/pom.xml +++ /dev/null @@ -1,77 +0,0 @@ - - - - - 4.0.0 - - - openinvparent - com.lishid - ../../pom.xml - 5.1.3-SNAPSHOT - - - openinvadapter1_20_R3 - OpenInvAdapter1_20_R3 - - - 17 - 17 - 1.20.4-R0.1-SNAPSHOT - - - - - org.spigotmc - spigot-api - ${spigot.version} - - - spigot - org.spigotmc - provided - ${spigot.version} - remapped-mojang - - - openinvapi - com.lishid - - - openinvcommon - com.lishid - - - annotations - org.jetbrains - - - - - - - maven-compiler-plugin - - - net.md-5 - specialsource-maven-plugin - - - - - diff --git a/internal/v1_20_R3/src/main/java/com/lishid/openinv/internal/v1_20_R3/AnySilentContainer.java b/internal/v1_20_R3/src/main/java/com/lishid/openinv/internal/v1_20_R3/AnySilentContainer.java deleted file mode 100644 index aaddac01..00000000 --- a/internal/v1_20_R3/src/main/java/com/lishid/openinv/internal/v1_20_R3/AnySilentContainer.java +++ /dev/null @@ -1,208 +0,0 @@ -/* - * Copyright (C) 2011-2023 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.v1_20_R3; - -import com.lishid.openinv.internal.AnySilentContainerBase; -import com.lishid.openinv.util.ReflectionHelper; -import com.lishid.openinv.util.lang.LanguageManager; -import net.minecraft.core.BlockPos; -import net.minecraft.network.chat.Component; -import net.minecraft.server.level.ServerPlayer; -import net.minecraft.server.level.ServerPlayerGameMode; -import net.minecraft.world.MenuProvider; -import net.minecraft.world.SimpleMenuProvider; -import net.minecraft.world.inventory.ChestMenu; -import net.minecraft.world.inventory.MenuType; -import net.minecraft.world.inventory.PlayerEnderChestContainer; -import net.minecraft.world.level.GameType; -import net.minecraft.world.level.block.BarrelBlock; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.ChestBlock; -import net.minecraft.world.level.block.ShulkerBoxBlock; -import net.minecraft.world.level.block.TrappedChestBlock; -import net.minecraft.world.level.block.entity.BlockEntity; -import net.minecraft.world.level.block.entity.EnderChestBlockEntity; -import net.minecraft.world.level.block.entity.RandomizableContainerBlockEntity; -import net.minecraft.world.level.block.state.BlockState; -import org.bukkit.GameMode; -import org.bukkit.Material; -import org.bukkit.Statistic; -import org.bukkit.entity.Player; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.lang.reflect.Field; -import java.util.logging.Logger; - -public class AnySilentContainer extends AnySilentContainerBase { - - private final @NotNull Logger logger; - private final @NotNull LanguageManager lang; - private @Nullable Field serverPlayerGameModeGameType; - - public AnySilentContainer(@NotNull Logger logger, @NotNull LanguageManager lang) { - this.logger = logger; - this.lang = lang; - try { - try { - this.serverPlayerGameModeGameType = ServerPlayerGameMode.class.getDeclaredField("b"); - this.serverPlayerGameModeGameType.setAccessible(true); - } catch (NoSuchFieldException e) { - logger.warning("ServerPlayerGameMode#gameModeForPlayer's obfuscated name has changed!"); - logger.warning("Please report this at https://github.com/Jikoo/OpenInv/issues"); - logger.warning("Attempting to fall through using reflection. Please verify that SilentContainer does not fail."); - // N.B. gameModeForPlayer is (for now) declared before previousGameModeForPlayer so silent shouldn't break. - this.serverPlayerGameModeGameType = ReflectionHelper.grabFieldByType(ServerPlayerGameMode.class, GameType.class); - } - } catch (SecurityException e) { - logger.warning("Unable to directly write player game mode! SilentContainer will fail."); - logger.log(java.util.logging.Level.WARNING, "Error obtaining GameType field", e); - } - } - - @Override - public boolean activateContainer( - @NotNull final Player bukkitPlayer, - final boolean silentchest, - @NotNull final org.bukkit.block.Block bukkitBlock) { - - // Silent ender chest is API-only - if (silentchest && bukkitBlock.getType() == Material.ENDER_CHEST) { - bukkitPlayer.openInventory(bukkitPlayer.getEnderChest()); - bukkitPlayer.incrementStatistic(Statistic.ENDERCHEST_OPENED); - return true; - } - - ServerPlayer player = PlayerManager.getHandle(bukkitPlayer); - - final net.minecraft.world.level.Level level = player.level(); - final BlockPos blockPos = new BlockPos(bukkitBlock.getX(), bukkitBlock.getY(), bukkitBlock.getZ()); - final BlockEntity blockEntity = level.getBlockEntity(blockPos); - - if (blockEntity == null) { - return false; - } - - if (blockEntity instanceof EnderChestBlockEntity enderChestTile) { - // Anychest ender chest. See net.minecraft.world.level.block.EnderChestBlock - PlayerEnderChestContainer enderChest = player.getEnderChestInventory(); - enderChest.setActiveChest(enderChestTile); - player.openMenu(new SimpleMenuProvider((containerCounter, playerInventory, ignored) -> { - MenuType containers = PlayerManager.getContainers(enderChest.getContainerSize()); - int rows = enderChest.getContainerSize() / 9; - return new ChestMenu(containers, containerCounter, playerInventory, enderChest, rows); - }, Component.translatable("container.enderchest"))); - bukkitPlayer.incrementStatistic(Statistic.ENDERCHEST_OPENED); - return true; - } - - if (!(blockEntity instanceof MenuProvider menuProvider)) { - return false; - } - - BlockState blockState = level.getBlockState(blockPos); - Block block = blockState.getBlock(); - - if (block instanceof ChestBlock chestBlock) { - - // boolean flag: do not check if chest is blocked - menuProvider = chestBlock.getMenuProvider(blockState, level, blockPos, true); - - if (menuProvider == null) { - lang.sendSystemMessage(bukkitPlayer, "messages.error.lootNotGenerated"); - return false; - } - - if (block instanceof TrappedChestBlock) { - bukkitPlayer.incrementStatistic(Statistic.TRAPPED_CHEST_TRIGGERED); - } else { - bukkitPlayer.incrementStatistic(Statistic.CHEST_OPENED); - } - } - - if (block instanceof ShulkerBoxBlock) { - bukkitPlayer.incrementStatistic(Statistic.SHULKER_BOX_OPENED); - } - - if (block instanceof BarrelBlock) { - bukkitPlayer.incrementStatistic(Statistic.OPEN_BARREL); - } - - // AnyChest only - SilentChest not active, container unsupported, or unnecessary. - if (!silentchest || player.gameMode.getGameModeForPlayer() == GameType.SPECTATOR) { - player.openMenu(menuProvider); - return true; - } - - // SilentChest requires access to setting players' game mode directly. - if (this.serverPlayerGameModeGameType == null) { - return false; - } - - if (blockEntity instanceof RandomizableContainerBlockEntity lootable) { - if (lootable.lootTable != null) { - lang.sendSystemMessage(bukkitPlayer, "messages.error.lootNotGenerated"); - return false; - } - } - - GameType gameType = player.gameMode.getGameModeForPlayer(); - this.forceGameType(player, GameType.SPECTATOR); - player.openMenu(menuProvider); - this.forceGameType(player, gameType); - return true; - } - - @Override - public void deactivateContainer(@NotNull final Player bukkitPlayer) { - if (this.serverPlayerGameModeGameType == null || bukkitPlayer.getGameMode() == GameMode.SPECTATOR) { - return; - } - - ServerPlayer player = PlayerManager.getHandle(bukkitPlayer); - - // Force game mode change without informing plugins or players. - // Regular game mode set calls GameModeChangeEvent and is cancellable. - GameType gameType = player.gameMode.getGameModeForPlayer(); - this.forceGameType(player, GameType.SPECTATOR); - - // ServerPlayer#closeContainer cannot be called without entering an - // infinite loop because this method is called during inventory close. - // From ServerPlayer#closeContainer -> CraftEventFactory#handleInventoryCloseEvent - player.containerMenu.transferTo(player.inventoryMenu, player.getBukkitEntity()); - // From ServerPlayer#closeContainer - player.doCloseContainer(); - // Regular inventory close will handle the rest - packet sending, etc. - - // Revert forced game mode. - this.forceGameType(player, gameType); - } - - private void forceGameType(final ServerPlayer player, final GameType gameMode) { - if (this.serverPlayerGameModeGameType == null) { - // No need to warn repeatedly, error on startup and lack of function should be enough. - return; - } - try { - this.serverPlayerGameModeGameType.setAccessible(true); - this.serverPlayerGameModeGameType.set(player.gameMode, gameMode); - } catch (IllegalArgumentException | IllegalAccessException e) { - logger.log(java.util.logging.Level.WARNING, "Error bypassing GameModeChangeEvent", e); - } - } - -} diff --git a/internal/v1_20_R3/src/main/java/com/lishid/openinv/internal/v1_20_R3/InternalAccessor.java b/internal/v1_20_R3/src/main/java/com/lishid/openinv/internal/v1_20_R3/InternalAccessor.java deleted file mode 100644 index 47e199da..00000000 --- a/internal/v1_20_R3/src/main/java/com/lishid/openinv/internal/v1_20_R3/InternalAccessor.java +++ /dev/null @@ -1,64 +0,0 @@ -package com.lishid.openinv.internal.v1_20_R3; - -import com.lishid.openinv.internal.Accessor; -import com.lishid.openinv.internal.IAnySilentContainer; -import com.lishid.openinv.internal.ISpecialEnderChest; -import com.lishid.openinv.internal.ISpecialInventory; -import com.lishid.openinv.internal.ISpecialPlayerInventory; -import com.lishid.openinv.util.lang.LanguageManager; -import net.minecraft.world.Container; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.craftbukkit.v1_20_R3.inventory.CraftInventory; -import org.bukkit.entity.Player; -import org.bukkit.inventory.Inventory; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.logging.Logger; - -public class InternalAccessor implements Accessor { - - private final @NotNull PlayerManager manager; - private final @NotNull AnySilentContainer anySilentContainer; - - public InternalAccessor(@NotNull Logger logger, @NotNull LanguageManager lang) { - manager = new PlayerManager(logger, lang); - anySilentContainer = new AnySilentContainer(logger, lang); - } - - @Override - public @NotNull PlayerManager getPlayerManager() { - return manager; - } - - @Override - public @NotNull IAnySilentContainer getAnySilentContainer() { - return anySilentContainer; - } - - @Override - public @NotNull ISpecialPlayerInventory createPlayerInventory(@NotNull Player player) { - return new SpecialPlayerInventory(player, player.isOnline()); - } - - @Override - public @NotNull ISpecialEnderChest createEnderChest(@NotNull Player player) { - return new SpecialEnderChest(player, player.isOnline()); - } - - @Override - public @Nullable T get(@NotNull Inventory bukkitInventory, @NotNull Class clazz) { - if (!(bukkitInventory instanceof CraftInventory craftInventory)) { - return null; - } - Container container = craftInventory.getInventory(); - if (clazz.isInstance(container)) { - return clazz.cast(container); - } - return null; - } - - @Override - public void reload(@NotNull ConfigurationSection config) {} - -} diff --git a/internal/v1_20_R3/src/main/java/com/lishid/openinv/internal/v1_20_R3/OpenPlayer.java b/internal/v1_20_R3/src/main/java/com/lishid/openinv/internal/v1_20_R3/OpenPlayer.java deleted file mode 100644 index 52d553af..00000000 --- a/internal/v1_20_R3/src/main/java/com/lishid/openinv/internal/v1_20_R3/OpenPlayer.java +++ /dev/null @@ -1,184 +0,0 @@ -/* - * Copyright (C) 2011-2023 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.v1_20_R3; - -import com.lishid.openinv.event.OpenEvents; -import com.mojang.logging.LogUtils; -import net.minecraft.Util; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.nbt.NbtIo; -import net.minecraft.nbt.NumericTag; -import net.minecraft.nbt.Tag; -import net.minecraft.server.level.ServerPlayer; -import net.minecraft.world.level.storage.PlayerDataStorage; -import org.bukkit.craftbukkit.v1_20_R3.CraftServer; -import org.bukkit.craftbukkit.v1_20_R3.entity.CraftPlayer; -import org.jetbrains.annotations.Contract; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Set; - -public class OpenPlayer extends CraftPlayer { - - private static final Set RESET_TAGS = Set.of( - // net.minecraft.world.Entity#saveWithoutId(CompoundTag) - "CustomName", - "CustomNameVisible", - "Silent", - "NoGravity", - "Glowing", - "TicksFrozen", - "HasVisualFire", - "Tags", - "Passengers", - // net.minecraft.server.level.ServerPlayer#addAdditionalSaveData(CompoundTag) - // Intentional omissions to prevent mount loss: Attach, Entity, and RootVehicle - "warden_spawn_tracker", - "enteredNetherPosition", - "SpawnX", - "SpawnY", - "SpawnZ", - "SpawnForced", - "SpawnAngle", - "SpawnDimension", - // net.minecraft.world.entity.player.Player#addAdditionalSaveData(CompoundTag) - "ShoulderEntityLeft", - "ShoulderEntityRight", - "LastDeathLocation", - // net.minecraft.world.entity.LivingEntity#addAdditionalSaveData(CompoundTag) - "ActiveEffects", // Backwards compat: Renamed from 1.19 - "active_effects", - "SleepingX", - "SleepingY", - "SleepingZ", - "Brain" - ); - - private final PlayerManager manager; - - OpenPlayer(CraftServer server, ServerPlayer entity, PlayerManager manager) { - super(server, entity); - this.manager = manager; - } - - @Override - public void loadData() { - manager.loadData(getHandle()); - } - - @Override - public void saveData() { - if (OpenEvents.saveCancelled(this)) { - return; - } - - ServerPlayer player = this.getHandle(); - // See net.minecraft.world.level.storage.PlayerDataStorage#save(EntityHuman) - try { - PlayerDataStorage worldNBTStorage = player.server.getPlayerList().playerIo; - - CompoundTag oldData = isOnline() ? null : worldNBTStorage.getPlayerData(player.getStringUUID()); - CompoundTag playerData = getWritableTag(oldData); - playerData = player.saveWithoutId(playerData); - setExtraData(playerData); - - if (oldData != null) { - // Revert certain special data values when offline. - revertSpecialValues(playerData, oldData); - } - - Path playerDataDir = worldNBTStorage.getPlayerDir().toPath(); - Path file = Files.createTempFile(playerDataDir, player.getStringUUID() + "-", ".dat"); - NbtIo.writeCompressed(playerData, file); - Path dataFile = playerDataDir.resolve(player.getStringUUID() + ".dat"); - Path backupFile = playerDataDir.resolve(player.getStringUUID() + ".dat_old"); - Util.safeReplaceFile(dataFile, file, backupFile); - } catch (Exception e) { - LogUtils.getLogger().warn("Failed to save player data for {}: {}", player.getScoreboardName(), e); - } - } - - @Contract("null -> new") - private @NotNull CompoundTag getWritableTag(@Nullable CompoundTag oldData) { - if (oldData == null) { - return new CompoundTag(); - } - - // Copy old data. This is a deep clone, so operating on it should be safe. - oldData = oldData.copy(); - - // Remove vanilla/server data that is not written every time. - oldData.getAllKeys() - .removeIf(key -> RESET_TAGS.contains(key) || key.startsWith("Bukkit")); - - return oldData; - } - - private void revertSpecialValues(@NotNull CompoundTag newData, @NotNull CompoundTag oldData) { - // Revert automatic updates to play timestamps. - copyValue(oldData, newData, "bukkit", "lastPlayed", NumericTag.class); - copyValue(oldData, newData, "Paper", "LastSeen", NumericTag.class); - copyValue(oldData, newData, "Paper", "LastLogin", NumericTag.class); - } - - private void copyValue( - @NotNull CompoundTag source, - @NotNull CompoundTag target, - @NotNull String container, - @NotNull String key, - @NotNull Class tagType) { - CompoundTag oldContainer = getTag(source, container, CompoundTag.class); - CompoundTag newContainer = getTag(target, container, CompoundTag.class); - - // New container being null means the server implementation doesn't store this data. - if (newContainer == null) { - return; - } - - // If old tag exists, copy it to new location, removing otherwise. - setTag(newContainer, key, getTag(oldContainer, key, tagType)); - } - - private @Nullable T getTag( - @Nullable CompoundTag container, - @NotNull String key, - @NotNull Class dataType) { - if (container == null) { - return null; - } - Tag value = container.get(key); - if (value == null || !dataType.isAssignableFrom(value.getClass())) { - return null; - } - return dataType.cast(value); - } - - private void setTag( - @NotNull CompoundTag container, - @NotNull String key, - @Nullable T data) { - if (data == null) { - container.remove(key); - } else { - container.put(key, data); - } - } - -} diff --git a/internal/v1_20_R3/src/main/java/com/lishid/openinv/internal/v1_20_R3/PlayerManager.java b/internal/v1_20_R3/src/main/java/com/lishid/openinv/internal/v1_20_R3/PlayerManager.java deleted file mode 100644 index 0df4c729..00000000 --- a/internal/v1_20_R3/src/main/java/com/lishid/openinv/internal/v1_20_R3/PlayerManager.java +++ /dev/null @@ -1,292 +0,0 @@ -/* - * Copyright (C) 2011-2023 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.v1_20_R3; - -import com.lishid.openinv.internal.ISpecialInventory; -import com.lishid.openinv.internal.InventoryViewTitle; -import com.lishid.openinv.internal.OpenInventoryView; -import com.lishid.openinv.util.lang.LanguageManager; -import com.mojang.authlib.GameProfile; -import com.mojang.serialization.Dynamic; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.nbt.NbtOps; -import net.minecraft.network.chat.Component; -import net.minecraft.network.protocol.game.ClientboundOpenScreenPacket; -import net.minecraft.server.MinecraftServer; -import net.minecraft.server.level.ClientInformation; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.server.level.ServerPlayer; -import net.minecraft.world.entity.Entity; -import net.minecraft.world.entity.player.ChatVisiblity; -import net.minecraft.world.inventory.AbstractContainerMenu; -import net.minecraft.world.inventory.MenuType; -import net.minecraft.world.level.Level; -import net.minecraft.world.level.dimension.DimensionType; -import org.bukkit.Bukkit; -import org.bukkit.OfflinePlayer; -import org.bukkit.Server; -import org.bukkit.World; -import org.bukkit.craftbukkit.v1_20_R3.CraftServer; -import org.bukkit.craftbukkit.v1_20_R3.CraftWorld; -import org.bukkit.craftbukkit.v1_20_R3.entity.CraftPlayer; -import org.bukkit.craftbukkit.v1_20_R3.event.CraftEventFactory; -import org.bukkit.craftbukkit.v1_20_R3.inventory.CraftContainer; -import org.bukkit.entity.Player; -import org.bukkit.inventory.InventoryView; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.lang.reflect.Field; -import java.util.UUID; -import java.util.logging.Logger; - -public class PlayerManager implements com.lishid.openinv.internal.PlayerManager { - - private static boolean paper; - - static { - try { - Class.forName("io.papermc.paper.configuration.Configuration"); - paper = true; - } catch (ClassNotFoundException ignored) { - paper = false; - } - } - - private final @NotNull Logger logger; - private final @NotNull LanguageManager lang; - private @Nullable Field bukkitEntity; - - public PlayerManager(@NotNull Logger logger, @NotNull LanguageManager lang) { - this.logger = logger; - this.lang = lang; - try { - bukkitEntity = Entity.class.getDeclaredField("bukkitEntity"); - } catch (NoSuchFieldException e) { - logger.warning("Unable to obtain field to inject custom save process - certain player data may be lost when saving!"); - logger.log(java.util.logging.Level.WARNING, e.getMessage(), e); - bukkitEntity = null; - } - } - - public static @NotNull ServerPlayer getHandle(final Player player) { - if (player instanceof CraftPlayer) { - return ((CraftPlayer) player).getHandle(); - } - - Server server = player.getServer(); - ServerPlayer nmsPlayer = null; - - if (server instanceof CraftServer) { - nmsPlayer = ((CraftServer) server).getHandle().getPlayer(player.getUniqueId()); - } - - if (nmsPlayer == null) { - // Could use reflection to examine fields, but it's honestly not worth the bother. - throw new RuntimeException("Unable to fetch EntityPlayer from Player implementation " + player.getClass().getName()); - } - - return nmsPlayer; - } - - @Override - public @Nullable Player loadPlayer(@NotNull final OfflinePlayer offline) { - MinecraftServer server = ((CraftServer) Bukkit.getServer()).getServer(); - ServerLevel worldServer = server.getLevel(Level.OVERWORLD); - - if (worldServer == null) { - return null; - } - - // Create a new ServerPlayer. - ServerPlayer entity = createNewPlayer(server, worldServer, offline); - - // Stop listening for advancement progression - if this is not cleaned up, loading causes a memory leak. - entity.getAdvancements().stopListening(); - - // Try to load the player's data. - if (loadData(entity)) { - // If data is loaded successfully, return the Bukkit entity. - return entity.getBukkitEntity(); - } - - return null; - } - - private @NotNull ServerPlayer createNewPlayer( - @NotNull MinecraftServer server, - @NotNull ServerLevel worldServer, - @NotNull final OfflinePlayer offline) { - // See net.minecraft.server.players.PlayerList#canPlayerLogin(ServerLoginPacketListenerImpl, GameProfile) - // See net.minecraft.server.network.ServerLoginPacketListenerImpl#handleHello(ServerboundHelloPacket) - GameProfile profile = new GameProfile(offline.getUniqueId(), - offline.getName() != null ? offline.getName() : offline.getUniqueId().toString()); - - ClientInformation dummyInfo = new ClientInformation( - "en_us", - 1, // Reduce distance just in case. - ChatVisiblity.HIDDEN, // Don't accept chat. - false, - ServerPlayer.DEFAULT_MODEL_CUSTOMIZATION, - ServerPlayer.DEFAULT_MAIN_HAND, - true, - false // Don't list in player list (not that this player is in the list anyway). - ); - - ServerPlayer entity = new ServerPlayer(server, worldServer, profile, dummyInfo); - - try { - injectPlayer(entity); - } catch (IllegalAccessException e) { - logger.log( - java.util.logging.Level.WARNING, - e, - () -> "Unable to inject ServerPlayer, certain player data may be lost when saving!"); - } - - return entity; - } - - boolean loadData(@NotNull ServerPlayer player) { - // See CraftPlayer#loadData - CompoundTag loadedData = player.server.getPlayerList().playerIo.load(player); - - if (loadedData == null) { - // Exceptions with loading are logged by Mojang. - return false; - } - - // Read basic data into the player. - player.load(loadedData); - // Also read "extra" data. - player.readAdditionalSaveData(loadedData); - // Game type settings are also loaded separately. - player.loadGameTypes(loadedData); - - if (paper) { - // Paper: world is not loaded by ServerPlayer#load(CompoundTag). - parseWorld(player, loadedData); - } - - return true; - } - - private void parseWorld(@NotNull ServerPlayer player, @NotNull CompoundTag loadedData) { - // See PlayerList#placeNewPlayer - World bukkitWorld; - if (loadedData.contains("WorldUUIDMost") && loadedData.contains("WorldUUIDLeast")) { - // Modern Bukkit world. - bukkitWorld = Bukkit.getServer().getWorld(new UUID(loadedData.getLong("WorldUUIDMost"), loadedData.getLong("WorldUUIDLeast"))); - } else if (loadedData.contains("world", net.minecraft.nbt.Tag.TAG_STRING)) { - // Legacy Bukkit world. - bukkitWorld = Bukkit.getServer().getWorld(loadedData.getString("world")); - } else { - // Vanilla player data. - DimensionType.parseLegacy(new Dynamic<>(NbtOps.INSTANCE, loadedData.get("Dimension"))) - .resultOrPartial(logger::warning) - .map(player.server::getLevel) - // If ServerLevel exists, set, otherwise move to spawn. - .ifPresentOrElse(player::setServerLevel, () -> player.spawnIn(null)); - return; - } - if (bukkitWorld == null) { - player.spawnIn(null); - return; - } - player.setServerLevel(((CraftWorld) bukkitWorld).getHandle()); - } - - private void injectPlayer(ServerPlayer player) throws IllegalAccessException { - if (bukkitEntity == null) { - return; - } - - bukkitEntity.setAccessible(true); - - bukkitEntity.set(player, new OpenPlayer(player.server.server, player, this)); - } - - @Override - public @NotNull Player inject(@NotNull Player player) { - try { - ServerPlayer nmsPlayer = getHandle(player); - if (nmsPlayer.getBukkitEntity() instanceof OpenPlayer openPlayer) { - return openPlayer; - } - injectPlayer(nmsPlayer); - return nmsPlayer.getBukkitEntity(); - } catch (IllegalAccessException e) { - logger.log( - java.util.logging.Level.WARNING, - e, - () -> "Unable to inject ServerPlayer, certain player data may be lost when saving!"); - return player; - } - } - - @Override - public @Nullable InventoryView openInventory(@NotNull Player player, @NotNull ISpecialInventory inventory, boolean viewOnly) { - - ServerPlayer nmsPlayer = getHandle(player); - - if (nmsPlayer.connection == null) { - return null; - } - - InventoryViewTitle title = InventoryViewTitle.of(inventory); - - if (title == null) { - return player.openInventory(inventory.getBukkitInventory()); - } - - InventoryView view = new OpenInventoryView(player, inventory, title.getTitle(lang, player, inventory)); - AbstractContainerMenu container = new CraftContainer(view, nmsPlayer, nmsPlayer.nextContainerCounter()) { - @Override - public MenuType getType() { - return getContainers(inventory.getBukkitInventory().getSize()); - } - }; - - container.setTitle(Component.literal(view.getOriginalTitle())); - container = CraftEventFactory.callInventoryOpenEvent(nmsPlayer, container); - - if (container == null) { - return null; - } - - nmsPlayer.connection.send(new ClientboundOpenScreenPacket(container.containerId, container.getType(), - Component.literal(container.getBukkitView().getTitle()))); - nmsPlayer.containerMenu = container; - nmsPlayer.initMenu(container); - - return container.getBukkitView(); - - } - - static @NotNull MenuType getContainers(int inventorySize) { - - return switch (inventorySize) { - case 9 -> MenuType.GENERIC_9x1; - case 18 -> MenuType.GENERIC_9x2; - case 36 -> MenuType.GENERIC_9x4; // PLAYER - case 41, 45 -> MenuType.GENERIC_9x5; - case 54 -> MenuType.GENERIC_9x6; - default -> MenuType.GENERIC_9x3; // Default 27-slot inventory - }; - } - -} diff --git a/internal/v1_20_R3/src/main/java/com/lishid/openinv/internal/v1_20_R3/SpecialEnderChest.java b/internal/v1_20_R3/src/main/java/com/lishid/openinv/internal/v1_20_R3/SpecialEnderChest.java deleted file mode 100644 index 2b8b3f38..00000000 --- a/internal/v1_20_R3/src/main/java/com/lishid/openinv/internal/v1_20_R3/SpecialEnderChest.java +++ /dev/null @@ -1,351 +0,0 @@ -/* - * Copyright (C) 2011-2023 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.v1_20_R3; - -import com.lishid.openinv.internal.ISpecialEnderChest; -import net.minecraft.core.NonNullList; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.nbt.ListTag; -import net.minecraft.server.level.ServerPlayer; -import net.minecraft.world.ContainerHelper; -import net.minecraft.world.ContainerListener; -import net.minecraft.world.entity.player.Player; -import net.minecraft.world.entity.player.StackedContents; -import net.minecraft.world.inventory.PlayerEnderChestContainer; -import net.minecraft.world.item.Item; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.level.block.entity.EnderChestBlockEntity; -import org.bukkit.Location; -import org.bukkit.craftbukkit.v1_20_R3.entity.CraftHumanEntity; -import org.bukkit.craftbukkit.v1_20_R3.inventory.CraftInventory; -import org.bukkit.entity.HumanEntity; -import org.bukkit.inventory.InventoryHolder; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.List; -import java.util.function.Predicate; -import java.util.stream.Collectors; - -public class SpecialEnderChest extends PlayerEnderChestContainer implements ISpecialEnderChest { - - private final CraftInventory inventory; - private ServerPlayer owner; - private NonNullList items; - private boolean playerOnline; - - public SpecialEnderChest(final org.bukkit.entity.Player player, final Boolean online) { - super(PlayerManager.getHandle(player)); - this.inventory = new CraftInventory(this); - this.owner = PlayerManager.getHandle(player); - this.playerOnline = online; - this.items = this.owner.getEnderChestInventory().items; - } - - @Override - public @NotNull CraftInventory getBukkitInventory() { - return inventory; - } - - @Override - public void setPlayerOffline() { - this.playerOnline = false; - } - - @Override - public void setPlayerOnline(@NotNull final org.bukkit.entity.Player player) { - if (this.playerOnline) { - return; - } - - ServerPlayer offlinePlayer = this.owner; - ServerPlayer onlinePlayer = PlayerManager.getHandle(player); - - // Set owner to new player. - this.owner = onlinePlayer; - - // Set player's ender chest contents to our modified contents. - PlayerEnderChestContainer onlineEnderChest = onlinePlayer.getEnderChestInventory(); - for (int i = 0; i < onlineEnderChest.getContainerSize(); ++i) { - onlineEnderChest.setItem(i, this.items.get(i)); - } - - // Set our item array to the new inventory's array. - this.items = onlineEnderChest.items; - - // Add viewers to new inventory. - onlineEnderChest.transaction.addAll(offlinePlayer.getEnderChestInventory().transaction); - - this.playerOnline = true; - } - - @Override - public @NotNull org.bukkit.entity.Player getPlayer() { - return owner.getBukkitEntity(); - } - - @Override - public void setChanged() { - this.owner.getEnderChestInventory().setChanged(); - } - - @Override - public List getContents() { - return this.items; - } - - @Override - public void onOpen(CraftHumanEntity who) { - this.owner.getEnderChestInventory().onOpen(who); - } - - @Override - public void onClose(CraftHumanEntity who) { - this.owner.getEnderChestInventory().onClose(who); - } - - @Override - public List getViewers() { - return this.owner.getEnderChestInventory().getViewers(); - } - - @Override - public boolean stillValid(Player player) { - return true; - } - - @Override - public void setActiveChest(EnderChestBlockEntity enderChest) { - this.owner.getEnderChestInventory().setActiveChest(enderChest); - } - - @Override - public boolean isActiveChest(EnderChestBlockEntity enderChest) { - return this.owner.getEnderChestInventory().isActiveChest(enderChest); - } - - @Override - public int getMaxStackSize() { - return this.owner.getEnderChestInventory().getMaxStackSize(); - } - - @Override - public void setMaxStackSize(int i) { - this.owner.getEnderChestInventory().setMaxStackSize(i); - } - - @Override - public InventoryHolder getOwner() { - return this.owner.getEnderChestInventory().getOwner(); - } - - @Override - public @Nullable Location getLocation() { - return null; - } - - @Override - public void addListener(ContainerListener listener) { - this.owner.getEnderChestInventory().addListener(listener); - } - - @Override - public void removeListener(ContainerListener listener) { - this.owner.getEnderChestInventory().removeListener(listener); - } - - @Override - public ItemStack getItem(int i) { - return i >= 0 && i < this.items.size() ? this.items.get(i) : ItemStack.EMPTY; - } - - @Override - public ItemStack removeItem(int i, int j) { - ItemStack itemstack = ContainerHelper.removeItem(this.items, i, j); - if (!itemstack.isEmpty()) { - this.setChanged(); - } - - return itemstack; - } - - @Override - public ItemStack addItem(ItemStack itemstack) { - ItemStack localItem = itemstack.copy(); - this.moveItemToOccupiedSlotsWithSameType(localItem); - if (localItem.isEmpty()) { - return ItemStack.EMPTY; - } else { - this.moveItemToEmptySlots(localItem); - return localItem.isEmpty() ? ItemStack.EMPTY : localItem; - } - } - - @Override - public boolean canAddItem(ItemStack itemstack) { - for (ItemStack itemstack1 : this.items) { - if (itemstack1.isEmpty() || (ItemStack.isSameItemSameTags(itemstack1, itemstack) && itemstack1.getCount() < itemstack1.getMaxStackSize())) { - return true; - } - } - - return false; - } - - private void moveItemToEmptySlots(ItemStack itemstack) { - for(int i = 0; i < this.getContainerSize(); ++i) { - ItemStack localItem = this.getItem(i); - if (localItem.isEmpty()) { - this.setItem(i, itemstack.copy()); - itemstack.setCount(0); - return; - } - } - } - - private void moveItemToOccupiedSlotsWithSameType(ItemStack itemstack) { - for(int i = 0; i < this.getContainerSize(); ++i) { - ItemStack localItem = this.getItem(i); - if (ItemStack.isSameItemSameTags(localItem, itemstack)) { - this.moveItemsBetweenStacks(itemstack, localItem); - if (itemstack.isEmpty()) { - return; - } - } - } - } - - private void moveItemsBetweenStacks(ItemStack itemstack, ItemStack itemstack1) { - int i = Math.min(this.getMaxStackSize(), itemstack1.getMaxStackSize()); - int j = Math.min(itemstack.getCount(), i - itemstack1.getCount()); - if (j > 0) { - itemstack1.grow(j); - itemstack.shrink(j); - this.setChanged(); - } - } - - @Override - public ItemStack removeItemNoUpdate(int i) { - ItemStack itemstack = this.items.get(i); - if (itemstack.isEmpty()) { - return ItemStack.EMPTY; - } else { - this.items.set(i, ItemStack.EMPTY); - return itemstack; - } - } - - @Override - public void setItem(int i, ItemStack itemstack) { - this.items.set(i, itemstack); - if (!itemstack.isEmpty() && itemstack.getCount() > this.getMaxStackSize()) { - itemstack.setCount(this.getMaxStackSize()); - } - - this.setChanged(); - } - - @Override - public int getContainerSize() { - return this.owner.getEnderChestInventory().getContainerSize(); - } - - @Override - public boolean isEmpty() { - return this.items.stream().allMatch(ItemStack::isEmpty); - } - - @Override - public void startOpen(Player player) { - } - - @Override - public void stopOpen(Player player) { - } - - @Override - public boolean canPlaceItem(int i, ItemStack itemstack) { - return true; - } - - @Override - public void clearContent() { - this.items.clear(); - this.setChanged(); - } - - @Override - public void fillStackedContents(StackedContents stackedContents) { - for (ItemStack itemstack : this.items) { - stackedContents.accountStack(itemstack); - } - - } - - @Override - public List removeAllItems() { - List list = this.items.stream().filter(Predicate.not(ItemStack::isEmpty)).collect(Collectors.toList()); - this.clearContent(); - return list; - } - - @Override - public ItemStack removeItemType(Item item, int i) { - ItemStack itemstack = new ItemStack(item, 0); - - for(int j = this.getContainerSize() - 1; j >= 0; --j) { - ItemStack localItem = this.getItem(j); - if (localItem.getItem().equals(item)) { - int k = i - itemstack.getCount(); - ItemStack splitItem = localItem.split(k); - itemstack.grow(splitItem.getCount()); - if (itemstack.getCount() == i) { - break; - } - } - } - - if (!itemstack.isEmpty()) { - this.setChanged(); - } - - return itemstack; - } - - @Override - public String toString() { - return this.items.stream().filter((itemStack) -> !itemStack.isEmpty()).toList().toString(); - } - - @Override - public void fromTag(ListTag listTag) { - for (int i = 0; i < this.getContainerSize(); ++i) { - this.setItem(i, ItemStack.EMPTY); - } - - for (int i = 0; i < listTag.size(); ++i) { - CompoundTag compoundTag = listTag.getCompound(i); - int j = compoundTag.getByte("Slot") & 255; - if (j < this.getContainerSize()) { - this.setItem(j, ItemStack.of(compoundTag)); - } - } - - } - -} diff --git a/internal/v1_20_R3/src/main/java/com/lishid/openinv/internal/v1_20_R3/SpecialPlayerInventory.java b/internal/v1_20_R3/src/main/java/com/lishid/openinv/internal/v1_20_R3/SpecialPlayerInventory.java deleted file mode 100644 index e4c278b2..00000000 --- a/internal/v1_20_R3/src/main/java/com/lishid/openinv/internal/v1_20_R3/SpecialPlayerInventory.java +++ /dev/null @@ -1,807 +0,0 @@ -/* - * Copyright (C) 2011-2023 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.v1_20_R3; - -import com.google.common.collect.ImmutableList; -import com.lishid.openinv.internal.ISpecialPlayerInventory; -import net.minecraft.CrashReport; -import net.minecraft.CrashReportCategory; -import net.minecraft.ReportedException; -import net.minecraft.core.NonNullList; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.nbt.ListTag; -import net.minecraft.network.chat.Component; -import net.minecraft.network.protocol.game.ClientboundContainerSetSlotPacket; -import net.minecraft.server.level.ServerPlayer; -import net.minecraft.tags.DamageTypeTags; -import net.minecraft.tags.TagKey; -import net.minecraft.world.Container; -import net.minecraft.world.ContainerHelper; -import net.minecraft.world.damagesource.DamageSource; -import net.minecraft.world.entity.EquipmentSlot; -import net.minecraft.world.entity.player.Inventory; -import net.minecraft.world.entity.player.Player; -import net.minecraft.world.entity.player.StackedContents; -import net.minecraft.world.item.ArmorItem; -import net.minecraft.world.item.Item; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.level.block.state.BlockState; -import org.bukkit.Location; -import org.bukkit.craftbukkit.v1_20_R3.entity.CraftHumanEntity; -import org.bukkit.craftbukkit.v1_20_R3.inventory.CraftInventory; -import org.bukkit.entity.HumanEntity; -import org.bukkit.inventory.InventoryHolder; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.Collection; -import java.util.Iterator; -import java.util.List; -import java.util.function.Function; -import java.util.function.Predicate; -import java.util.stream.Collectors; - -public class SpecialPlayerInventory extends Inventory implements ISpecialPlayerInventory { - - private final CraftInventory inventory; - private boolean playerOnline; - private Player player; - private NonNullList items; - private NonNullList armor; - private NonNullList offhand; - private List> compartments; - - public SpecialPlayerInventory(@NotNull org.bukkit.entity.Player bukkitPlayer, @NotNull Boolean online) { - super(PlayerManager.getHandle(bukkitPlayer)); - this.inventory = new CraftInventory(this); - this.playerOnline = online; - this.player = super.player; - this.selected = player.getInventory().selected; - this.items = this.player.getInventory().items; - this.armor = this.player.getInventory().armor; - this.offhand = this.player.getInventory().offhand; - this.compartments = ImmutableList.of(this.items, this.armor, this.offhand); - } - - @Override - public void setPlayerOnline(@NotNull org.bukkit.entity.Player player) { - if (this.playerOnline) { - return; - } - - Player offlinePlayer = this.player; - Player onlinePlayer = PlayerManager.getHandle(player); - onlinePlayer.getInventory().transaction.addAll(this.transaction); - - // Set owner to new player. - this.player = onlinePlayer; - - // Set player's inventory contents to our modified contents. - Inventory onlineInventory = onlinePlayer.getInventory(); - for (int i = 0; i < getContainerSize(); ++i) { - onlineInventory.setItem(i, getRawItem(i)); - } - onlineInventory.selected = this.selected; - - // Set our item arrays to the new inventory's arrays. - this.items = onlineInventory.items; - this.armor = onlineInventory.armor; - this.offhand = onlineInventory.offhand; - this.compartments = ImmutableList.of(this.items, this.armor, this.offhand); - - // Add existing viewers to new viewer list. - Inventory offlineInventory = offlinePlayer.getInventory(); - // Remove self from listing - player is always a viewer of their own inventory, prevent duplicates. - offlineInventory.transaction.remove(offlinePlayer.getBukkitEntity()); - onlineInventory.transaction.addAll(offlineInventory.transaction); - - this.playerOnline = true; - } - - @Override - public @NotNull CraftInventory getBukkitInventory() { - return this.inventory; - } - - @Override - public void setPlayerOffline() { - this.playerOnline = false; - } - - @Override - public @NotNull HumanEntity getPlayer() { - return this.player.getBukkitEntity(); - } - - private @NotNull ItemStack getRawItem(int i) { - if (i < 0) { - return ItemStack.EMPTY; - } - - NonNullList list; - for (Iterator> iterator = this.compartments.iterator(); iterator.hasNext(); i -= list.size()) { - list = iterator.next(); - if (i < list.size()) { - return list.get(i); - } - } - - return ItemStack.EMPTY; - } - - private void setRawItem(int i, @NotNull ItemStack itemStack) { - if (i < 0) { - return; - } - - NonNullList list; - for (Iterator> iterator = this.compartments.iterator(); iterator.hasNext(); i -= list.size()) { - list = iterator.next(); - if (i < list.size()) { - list.set(i, itemStack); - } - } - } - - private record IndexedCompartment(@Nullable NonNullList compartment, int index) {} - - private @NotNull SpecialPlayerInventory.IndexedCompartment getIndexedContent(int index) { - if (index < items.size()) { - return new IndexedCompartment(items, getReversedItemSlotNum(index)); - } - - index -= items.size(); - - if (index < armor.size()) { - return new IndexedCompartment(armor, getReversedArmorSlotNum(index)); - } - - index -= armor.size(); - - if (index < offhand.size()) { - return new IndexedCompartment(offhand, index); - } - - index -= offhand.size(); - - return new IndexedCompartment(null, index); - } - - private int getReversedArmorSlotNum(final int i) { - if (i == 0) { - return 3; - } - if (i == 1) { - return 2; - } - if (i == 2) { - return 1; - } - if (i == 3) { - return 0; - } - return i; - } - - private int getReversedItemSlotNum(final int i) { - if (i >= 27) { - return i - 27; - } - return i + 9; - } - - private boolean contains(Predicate predicate) { - return this.compartments.stream().flatMap(NonNullList::stream).anyMatch(predicate); - } - - @Override - public List getArmorContents() { - return this.armor; - } - - @Override - public void onOpen(CraftHumanEntity who) { - this.player.getInventory().onOpen(who); - } - - @Override - public void onClose(CraftHumanEntity who) { - this.player.getInventory().onClose(who); - } - - @Override - public List getViewers() { - return this.player.getInventory().getViewers(); - } - - @Override - public InventoryHolder getOwner() { - return this.player.getBukkitEntity(); - } - - @Override - public int getMaxStackSize() { - return this.player.getInventory().getMaxStackSize(); - } - - @Override - public void setMaxStackSize(int size) { - this.player.getInventory().setMaxStackSize(size); - } - - @Override - public Location getLocation() { - return this.player.getBukkitEntity().getLocation(); - } - - @Override - public boolean hasCustomName() { - return false; - } - - @Override - public List getContents() { - return this.compartments.stream().flatMap(Collection::stream).collect(Collectors.toList()); - } - - @Override - public ItemStack getSelected() { - return isHotbarSlot(this.selected) ? this.items.get(this.selected) : ItemStack.EMPTY; - } - - private boolean hasRemainingSpaceForItem(ItemStack itemstack, ItemStack itemstack1) { - return !itemstack.isEmpty() && ItemStack.isSameItemSameTags(itemstack, itemstack1) && itemstack.isStackable() && itemstack.getCount() < itemstack.getMaxStackSize() && itemstack.getCount() < this.getMaxStackSize(); - } - - @Override - public int canHold(ItemStack itemstack) { - int remains = itemstack.getCount(); - - for (int i = 0; i < this.items.size(); ++i) { - ItemStack itemstack1 = this.getRawItem(i); - if (itemstack1.isEmpty()) { - return itemstack.getCount(); - } - - if (this.hasRemainingSpaceForItem(itemstack1, itemstack)) { - remains -= (itemstack1.getMaxStackSize() < this.getMaxStackSize() ? itemstack1.getMaxStackSize() : this.getMaxStackSize()) - itemstack1.getCount(); - } - - if (remains <= 0) { - return itemstack.getCount(); - } - } - - ItemStack offhandItemStack = this.getRawItem(this.items.size() + this.armor.size()); - if (this.hasRemainingSpaceForItem(offhandItemStack, itemstack)) { - remains -= (offhandItemStack.getMaxStackSize() < this.getMaxStackSize() ? offhandItemStack.getMaxStackSize() : this.getMaxStackSize()) - offhandItemStack.getCount(); - } - - return remains <= 0 ? itemstack.getCount() : itemstack.getCount() - remains; - } - - @Override - public int getFreeSlot() { - for(int i = 0; i < this.items.size(); ++i) { - if (this.items.get(i).isEmpty()) { - return i; - } - } - - return -1; - } - - @Override - public void setPickedItem(ItemStack itemstack) { - int i = this.findSlotMatchingItem(itemstack); - if (isHotbarSlot(i)) { - this.selected = i; - } else if (i == -1) { - this.selected = this.getSuitableHotbarSlot(); - if (!this.items.get(this.selected).isEmpty()) { - int j = this.getFreeSlot(); - if (j != -1) { - this.items.set(j, this.items.get(this.selected)); - } - } - - this.items.set(this.selected, itemstack); - } else { - this.pickSlot(i); - } - - } - - @Override - public void pickSlot(int i) { - this.selected = this.getSuitableHotbarSlot(); - ItemStack itemstack = this.items.get(this.selected); - this.items.set(this.selected, this.items.get(i)); - this.items.set(i, itemstack); - } - - @Override - public int findSlotMatchingItem(ItemStack itemstack) { - for(int i = 0; i < this.items.size(); ++i) { - if (!this.items.get(i).isEmpty() && ItemStack.isSameItemSameTags(itemstack, this.items.get(i))) { - return i; - } - } - - return -1; - } - - @Override - public int findSlotMatchingUnusedItem(ItemStack itemStack) { - for(int i = 0; i < this.items.size(); ++i) { - ItemStack localItem = this.items.get(i); - if (!this.items.get(i).isEmpty() && ItemStack.isSameItemSameTags(itemStack, this.items.get(i)) && !this.items.get(i).isDamaged() && !localItem.isEnchanted() && !localItem.hasCustomHoverName()) { - return i; - } - } - - return -1; - } - - @Override - public int getSuitableHotbarSlot() { - int i; - int j; - for(j = 0; j < 9; ++j) { - i = (this.selected + j) % 9; - if (this.items.get(i).isEmpty()) { - return i; - } - } - - for(j = 0; j < 9; ++j) { - i = (this.selected + j) % 9; - if (!this.items.get(i).isEnchanted()) { - return i; - } - } - - return this.selected; - } - - @Override - public void swapPaint(double d0) { - if (d0 > 0.0D) { - d0 = 1.0D; - } - - if (d0 < 0.0D) { - d0 = -1.0D; - } - - this.selected = (int) (this.selected - d0); - - while (this.selected < 0) { - this.selected += 9; - } - - while(this.selected >= 9) { - this.selected -= 9; - } - } - - @Override - public int clearOrCountMatchingItems(Predicate predicate, int i, Container container) { - byte b0 = 0; - boolean flag = i == 0; - int j = b0 + ContainerHelper.clearOrCountMatchingItems(this, predicate, i - b0, flag); - j += ContainerHelper.clearOrCountMatchingItems(container, predicate, i - j, flag); - ItemStack itemstack = this.player.containerMenu.getCarried(); - j += ContainerHelper.clearOrCountMatchingItems(itemstack, predicate, i - j, flag); - if (itemstack.isEmpty()) { - this.player.containerMenu.setCarried(ItemStack.EMPTY); - } - - return j; - } - - private int addResource(ItemStack itemstack) { - int i = this.getSlotWithRemainingSpace(itemstack); - if (i == -1) { - i = this.getFreeSlot(); - } - - return i == -1 ? itemstack.getCount() : this.addResource(i, itemstack); - } - - private int addResource(int i, ItemStack itemstack) { - Item item = itemstack.getItem(); - int j = itemstack.getCount(); - ItemStack localItemStack = this.getRawItem(i); - if (localItemStack.isEmpty()) { - localItemStack = new ItemStack(item, 0); - if (itemstack.hasTag()) { - // hasTag ensures tag not null - //noinspection ConstantConditions - localItemStack.setTag(itemstack.getTag().copy()); - } - - this.setRawItem(i, localItemStack); - } - - int k = Math.min(j, localItemStack.getMaxStackSize() - localItemStack.getCount()); - - if (k > this.getMaxStackSize() - localItemStack.getCount()) { - k = this.getMaxStackSize() - localItemStack.getCount(); - } - - if (k != 0) { - j -= k; - localItemStack.grow(k); - localItemStack.setPopTime(5); - } - - return j; - } - - @Override - public int getSlotWithRemainingSpace(ItemStack itemstack) { - if (this.hasRemainingSpaceForItem(this.getRawItem(this.selected), itemstack)) { - return this.selected; - } else if (this.hasRemainingSpaceForItem(this.getRawItem(40), itemstack)) { - return 40; - } else { - for(int i = 0; i < this.items.size(); ++i) { - if (this.hasRemainingSpaceForItem(this.items.get(i), itemstack)) { - return i; - } - } - - return -1; - } - } - - @Override - public void tick() { - for (NonNullList compartment : this.compartments) { - for (int i = 0; i < compartment.size(); ++i) { - if (!compartment.get(i).isEmpty()) { - compartment.get(i).inventoryTick(this.player.level(), this.player, i, this.selected == i); - } - } - } - - } - - @Override - public boolean add(ItemStack itemStack) { - return this.add(-1, itemStack); - } - - @Override - public boolean add(int i, ItemStack itemStack) { - if (itemStack.isEmpty()) { - return false; - } else { - try { - if (itemStack.isDamaged()) { - if (i == -1) { - i = this.getFreeSlot(); - } - - if (i >= 0) { - this.items.set(i, itemStack.copy()); - this.items.get(i).setPopTime(5); - itemStack.setCount(0); - return true; - } else if (this.player.getAbilities().instabuild) { - itemStack.setCount(0); - return true; - } else { - return false; - } - } else { - int j; - do { - j = itemStack.getCount(); - if (i == -1) { - itemStack.setCount(this.addResource(itemStack)); - } else { - itemStack.setCount(this.addResource(i, itemStack)); - } - } while(!itemStack.isEmpty() && itemStack.getCount() < j); - - if (itemStack.getCount() == j && this.player.getAbilities().instabuild) { - itemStack.setCount(0); - return true; - } else { - return itemStack.getCount() < j; - } - } - } catch (Throwable var6) { - CrashReport crashReport = CrashReport.forThrowable(var6, "Adding item to inventory"); - CrashReportCategory crashReportCategory = crashReport.addCategory("Item being added"); - crashReportCategory.setDetail("Item ID", Item.getId(itemStack.getItem())); - crashReportCategory.setDetail("Item data", itemStack.getDamageValue()); - crashReportCategory.setDetail("Item name", () -> itemStack.getHoverName().getString()); - throw new ReportedException(crashReport); - } - } - } - - @Override - public void placeItemBackInInventory(ItemStack itemStack) { - this.placeItemBackInInventory(itemStack, true); - } - - @Override - public void placeItemBackInInventory(ItemStack itemStack, boolean flag) { - while(true) { - if (!itemStack.isEmpty()) { - int i = this.getSlotWithRemainingSpace(itemStack); - if (i == -1) { - i = this.getFreeSlot(); - } - - if (i != -1) { - int j = itemStack.getMaxStackSize() - this.getRawItem(i).getCount(); - if (this.add(i, itemStack.split(j)) && flag && this.player instanceof ServerPlayer) { - ((ServerPlayer)this.player).connection.send(new ClientboundContainerSetSlotPacket(-2, 0, i, this.getRawItem(i))); - } - continue; - } - - this.player.drop(itemStack, false); - } - - return; - } - } - - @Override - public ItemStack removeItem(int rawIndex, final int j) { - IndexedCompartment indexedCompartment = getIndexedContent(rawIndex); - - if (indexedCompartment.compartment() == null - || indexedCompartment.compartment().get(indexedCompartment.index()).isEmpty()) { - return ItemStack.EMPTY; - } - - return ContainerHelper.removeItem(indexedCompartment.compartment(), indexedCompartment.index(), j); - } - - @Override - public void removeItem(ItemStack itemStack) { - for (NonNullList compartment : this.compartments) { - for (int i = 0; i < compartment.size(); ++i) { - if (compartment.get(i) == itemStack) { - compartment.set(i, ItemStack.EMPTY); - break; - } - } - } - } - - @Override - public ItemStack removeItemNoUpdate(int rawIndex) { - IndexedCompartment indexedCompartment = getIndexedContent(rawIndex); - - if (indexedCompartment.compartment() == null) { - return ItemStack.EMPTY; - } - - ItemStack removed = indexedCompartment.compartment().set(indexedCompartment.index(), ItemStack.EMPTY); - - if (removed.isEmpty()) { - return ItemStack.EMPTY; - } - - return removed; - } - - @Override - public void setItem(int rawIndex, final ItemStack itemStack) { - IndexedCompartment indexedCompartment = getIndexedContent(rawIndex); - - if (indexedCompartment.compartment() == null) { - this.player.drop(itemStack, true); - return; - } - - indexedCompartment.compartment().set(indexedCompartment.index(), itemStack); - } - - @Override - public float getDestroySpeed(BlockState blockState) { - return this.items.get(this.selected).getDestroySpeed(blockState); - } - - @Override - public ListTag save(ListTag listTag) { - for (int i = 0; i < this.items.size(); ++i) { - if (!this.items.get(i).isEmpty()) { - CompoundTag compoundTag = new CompoundTag(); - compoundTag.putByte("Slot", (byte)i); - this.items.get(i).save(compoundTag); - listTag.add(compoundTag); - } - } - - for (int i = 0; i < this.armor.size(); ++i) { - if (!this.armor.get(i).isEmpty()) { - CompoundTag compoundTag = new CompoundTag(); - compoundTag.putByte("Slot", (byte)(i + 100)); - this.armor.get(i).save(compoundTag); - listTag.add(compoundTag); - } - } - - for (int i = 0; i < this.offhand.size(); ++i) { - if (!this.offhand.get(i).isEmpty()) { - CompoundTag compoundTag = new CompoundTag(); - compoundTag.putByte("Slot", (byte)(i + 150)); - this.offhand.get(i).save(compoundTag); - listTag.add(compoundTag); - } - } - - return listTag; - } - - @Override - public void load(ListTag listTag) { - this.items.clear(); - this.armor.clear(); - this.offhand.clear(); - - for(int i = 0; i < listTag.size(); ++i) { - CompoundTag compoundTag = listTag.getCompound(i); - int j = compoundTag.getByte("Slot") & 255; - ItemStack itemstack = ItemStack.of(compoundTag); - if (!itemstack.isEmpty()) { - if (j < this.items.size()) { - this.items.set(j, itemstack); - } else if (j >= 100 && j < this.armor.size() + 100) { - this.armor.set(j - 100, itemstack); - } else if (j >= 150 && j < this.offhand.size() + 150) { - this.offhand.set(j - 150, itemstack); - } - } - } - - } - - @Override - public int getContainerSize() { - return 45; - } - - @Override - public boolean isEmpty() { - return !contains(itemStack -> !itemStack.isEmpty()); - } - - @Override - public ItemStack getItem(int rawIndex) { - IndexedCompartment indexedCompartment = getIndexedContent(rawIndex); - - if (indexedCompartment.compartment() == null) { - return ItemStack.EMPTY; - } - - return indexedCompartment.compartment().get(indexedCompartment.index()); - } - - @Override - public Component getName() { - return this.player.getName(); - } - - @Override - public ItemStack getArmor(int index) { - return this.armor.get(index); - } - - @Override - public void hurtArmor(DamageSource damagesource, float damage, int[] armorIndices) { - if (damage > 0.0F) { - damage /= 4.0F; - if (damage < 1.0F) { - damage = 1.0F; - } - - for (int index : armorIndices) { - ItemStack itemstack = this.armor.get(index); - if ((!damagesource.is(DamageTypeTags.IS_FIRE) || !itemstack.getItem().isFireResistant()) && itemstack.getItem() instanceof ArmorItem) { - itemstack.hurtAndBreak((int) damage, this.player, localPlayer -> localPlayer.broadcastBreakEvent(EquipmentSlot.byTypeAndIndex(EquipmentSlot.Type.ARMOR, index))); - } - } - } - } - - @Override - public void dropAll() { - for (NonNullList compartment : this.compartments) { - for (int i = 0; i < compartment.size(); ++i) { - ItemStack itemstack = compartment.get(i); - if (!itemstack.isEmpty()) { - this.player.drop(itemstack, true, false); - compartment.set(i, ItemStack.EMPTY); - } - } - } - } - - @Override - public void setChanged() { - super.setChanged(); - } - - @Override - public int getTimesChanged() { - return super.getTimesChanged(); - } - - @Override - public boolean stillValid(Player player) { - return true; - } - - @Override - public boolean contains(ItemStack itemstack) { - return contains(itemStack -> !itemStack.isEmpty() && itemStack.is(itemstack.getItem())); - } - - @Override - public boolean contains(TagKey tagKey) { - - return contains(itemStack -> !itemStack.isEmpty() && itemStack.is(tagKey)); - } - - @Override - public void replaceWith(Inventory inventory) { - Function getter; - - if (inventory instanceof SpecialPlayerInventory specialPlayerInventory) { - getter = specialPlayerInventory::getRawItem; - } else { - getter = inventory::getItem; - } - - for(int i = 0; i < this.getContainerSize(); ++i) { - this.setRawItem(i, getter.apply(i)); - } - - this.selected = inventory.selected; - } - - @Override - public void clearContent() { - for (NonNullList compartment : this.compartments) { - compartment.clear(); - } - } - - @Override - public void fillStackedContents(StackedContents stackedContents) { - for (ItemStack itemstack : this.items) { - stackedContents.accountSimpleStack(itemstack); - } - } - - @Override - public ItemStack removeFromSelected(boolean dropWholeStack) { - ItemStack itemstack = this.getSelected(); - return itemstack.isEmpty() ? ItemStack.EMPTY : this.removeItem(this.selected, dropWholeStack ? itemstack.getCount() : 1); - } - -} diff --git a/internal/v1_20_R4/src/main/java/com/lishid/openinv/internal/v1_20_R4/OpenPlayer.java b/internal/v1_20_R4/src/main/java/com/lishid/openinv/internal/v1_20_R4/OpenPlayer.java deleted file mode 100644 index 9223fa19..00000000 --- a/internal/v1_20_R4/src/main/java/com/lishid/openinv/internal/v1_20_R4/OpenPlayer.java +++ /dev/null @@ -1,194 +0,0 @@ -/* - * Copyright (C) 2011-2023 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.v1_20_R4; - -import com.lishid.openinv.event.OpenEvents; -import com.mojang.logging.LogUtils; -import net.minecraft.Util; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.nbt.NbtIo; -import net.minecraft.nbt.NumericTag; -import net.minecraft.nbt.Tag; -import net.minecraft.server.level.ServerPlayer; -import net.minecraft.world.level.storage.PlayerDataStorage; -import org.bukkit.craftbukkit.v1_20_R4.CraftServer; -import org.bukkit.craftbukkit.v1_20_R4.entity.CraftPlayer; -import org.jetbrains.annotations.Contract; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Set; - -public class OpenPlayer extends CraftPlayer { - - /** - * List of tags to always reset when saving. - * - * @see net.minecraft.world.entity.Entity#saveWithoutId(CompoundTag) - * @see net.minecraft.server.level.ServerPlayer#addAdditionalSaveData(CompoundTag) - * @see net.minecraft.world.entity.player.Player#addAdditionalSaveData(CompoundTag) - * @see net.minecraft.world.entity.LivingEntity#addAdditionalSaveData(CompoundTag) - */ - private static final Set RESET_TAGS = Set.of( - // Entity#saveWithoutId(CompoundTag) - "CustomName", - "CustomNameVisible", - "Silent", - "NoGravity", - "Glowing", - "TicksFrozen", - "HasVisualFire", - "Tags", - "Passengers", - // ServerPlayer#addAdditionalSaveData(CompoundTag) - // Intentional omissions to prevent mount loss: Attach, Entity, and RootVehicle - "warden_spawn_tracker", - "enteredNetherPosition", - "SpawnX", - "SpawnY", - "SpawnZ", - "SpawnForced", - "SpawnAngle", - "SpawnDimension", - "raid_omen_position", - // Player#addAdditionalSaveData(CompoundTag) - "ShoulderEntityLeft", - "ShoulderEntityRight", - "LastDeathLocation", - "current_explosion_impact_pos", - // LivingEntity#addAdditionalSaveData(CompoundTag) - "ActiveEffects", // Backwards compat: Renamed from 1.19 - "active_effects", - "SleepingX", - "SleepingY", - "SleepingZ", - "Brain" - ); - - private final PlayerManager manager; - - OpenPlayer(CraftServer server, ServerPlayer entity, PlayerManager manager) { - super(server, entity); - this.manager = manager; - } - - @Override - public void loadData() { - manager.loadData(getHandle()); - } - - @Override - public void saveData() { - if (OpenEvents.saveCancelled(this)) { - return; - } - - ServerPlayer player = this.getHandle(); - // See net.minecraft.world.level.storage.PlayerDataStorage#save(EntityHuman) - try { - PlayerDataStorage worldNBTStorage = player.server.getPlayerList().playerIo; - - CompoundTag oldData = isOnline() ? null : worldNBTStorage.load(player.getName().getString(), player.getStringUUID()).orElse(null); - CompoundTag playerData = getWritableTag(oldData); - playerData = player.saveWithoutId(playerData); - setExtraData(playerData); - - if (oldData != null) { - // Revert certain special data values when offline. - revertSpecialValues(playerData, oldData); - } - - Path playerDataDir = worldNBTStorage.getPlayerDir().toPath(); - Path tempFile = Files.createTempFile(playerDataDir, player.getStringUUID() + "-", ".dat"); - NbtIo.writeCompressed(playerData, tempFile); - Path dataFile = playerDataDir.resolve(player.getStringUUID() + ".dat"); - Path backupFile = playerDataDir.resolve(player.getStringUUID() + ".dat_old"); - Util.safeReplaceFile(dataFile, tempFile, backupFile); - } catch (Exception e) { - LogUtils.getLogger().warn("Failed to save player data for {}: {}", player.getScoreboardName(), e); - } - } - - @Contract("null -> new") - private @NotNull CompoundTag getWritableTag(@Nullable CompoundTag oldData) { - if (oldData == null) { - return new CompoundTag(); - } - - // Copy old data. This is a deep clone, so operating on it should be safe. - oldData = oldData.copy(); - - // Remove vanilla/server data that is not written every time. - oldData.getAllKeys() - .removeIf(key -> RESET_TAGS.contains(key) || key.startsWith("Bukkit")); - - return oldData; - } - - private void revertSpecialValues(@NotNull CompoundTag newData, @NotNull CompoundTag oldData) { - // Revert automatic updates to play timestamps. - copyValue(oldData, newData, "bukkit", "lastPlayed", NumericTag.class); - copyValue(oldData, newData, "Paper", "LastSeen", NumericTag.class); - copyValue(oldData, newData, "Paper", "LastLogin", NumericTag.class); - } - - private void copyValue( - @NotNull CompoundTag source, - @NotNull CompoundTag target, - @NotNull String container, - @NotNull String key, - @NotNull Class tagType) { - CompoundTag oldContainer = getTag(source, container, CompoundTag.class); - CompoundTag newContainer = getTag(target, container, CompoundTag.class); - - // New container being null means the server implementation doesn't store this data. - if (newContainer == null) { - return; - } - - // If old tag exists, copy it to new location, removing otherwise. - setTag(newContainer, key, getTag(oldContainer, key, tagType)); - } - - private @Nullable T getTag( - @Nullable CompoundTag container, - @NotNull String key, - @NotNull Class dataType) { - if (container == null) { - return null; - } - Tag value = container.get(key); - if (value == null || !dataType.isAssignableFrom(value.getClass())) { - return null; - } - return dataType.cast(value); - } - - private void setTag( - @NotNull CompoundTag container, - @NotNull String key, - @Nullable T data) { - if (data == null) { - container.remove(key); - } else { - container.put(key, data); - } - } - -} diff --git a/internal/v1_20_R4/src/main/java/com/lishid/openinv/internal/v1_20_R4/PlayerManager.java b/internal/v1_20_R4/src/main/java/com/lishid/openinv/internal/v1_20_R4/PlayerManager.java deleted file mode 100644 index 755500db..00000000 --- a/internal/v1_20_R4/src/main/java/com/lishid/openinv/internal/v1_20_R4/PlayerManager.java +++ /dev/null @@ -1,292 +0,0 @@ -/* - * Copyright (C) 2011-2023 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.v1_20_R4; - -import com.lishid.openinv.internal.ISpecialInventory; -import com.lishid.openinv.internal.InventoryViewTitle; -import com.lishid.openinv.internal.OpenInventoryView; -import com.lishid.openinv.util.lang.LanguageManager; -import com.mojang.authlib.GameProfile; -import com.mojang.serialization.Dynamic; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.nbt.NbtOps; -import net.minecraft.network.chat.Component; -import net.minecraft.network.protocol.game.ClientboundOpenScreenPacket; -import net.minecraft.server.MinecraftServer; -import net.minecraft.server.level.ClientInformation; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.server.level.ServerPlayer; -import net.minecraft.world.entity.Entity; -import net.minecraft.world.entity.player.ChatVisiblity; -import net.minecraft.world.inventory.AbstractContainerMenu; -import net.minecraft.world.inventory.MenuType; -import net.minecraft.world.level.Level; -import net.minecraft.world.level.dimension.DimensionType; -import org.bukkit.Bukkit; -import org.bukkit.OfflinePlayer; -import org.bukkit.Server; -import org.bukkit.World; -import org.bukkit.craftbukkit.v1_20_R4.CraftServer; -import org.bukkit.craftbukkit.v1_20_R4.CraftWorld; -import org.bukkit.craftbukkit.v1_20_R4.entity.CraftPlayer; -import org.bukkit.craftbukkit.v1_20_R4.event.CraftEventFactory; -import org.bukkit.craftbukkit.v1_20_R4.inventory.CraftContainer; -import org.bukkit.entity.Player; -import org.bukkit.inventory.InventoryView; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.lang.reflect.Field; -import java.util.UUID; -import java.util.logging.Logger; - -public class PlayerManager implements com.lishid.openinv.internal.PlayerManager { - - private static boolean paper; - - static { - try { - Class.forName("io.papermc.paper.configuration.Configuration"); - paper = true; - } catch (ClassNotFoundException ignored) { - paper = false; - } - } - - private final @NotNull Logger logger; - private final @NotNull LanguageManager lang; - private @Nullable Field bukkitEntity; - - public PlayerManager(@NotNull Logger logger, @NotNull LanguageManager lang) { - this.logger = logger; - this.lang = lang; - try { - bukkitEntity = Entity.class.getDeclaredField("bukkitEntity"); - } catch (NoSuchFieldException e) { - logger.warning("Unable to obtain field to inject custom save process - certain player data may be lost when saving!"); - logger.log(java.util.logging.Level.WARNING, e.getMessage(), e); - bukkitEntity = null; - } - } - - public static @NotNull ServerPlayer getHandle(final Player player) { - if (player instanceof CraftPlayer) { - return ((CraftPlayer) player).getHandle(); - } - - Server server = player.getServer(); - ServerPlayer nmsPlayer = null; - - if (server instanceof CraftServer) { - nmsPlayer = ((CraftServer) server).getHandle().getPlayer(player.getUniqueId()); - } - - if (nmsPlayer == null) { - // Could use reflection to examine fields, but it's honestly not worth the bother. - throw new RuntimeException("Unable to fetch EntityPlayer from Player implementation " + player.getClass().getName()); - } - - return nmsPlayer; - } - - @Override - public @Nullable Player loadPlayer(@NotNull final OfflinePlayer offline) { - MinecraftServer server = ((CraftServer) Bukkit.getServer()).getServer(); - ServerLevel worldServer = server.getLevel(Level.OVERWORLD); - - if (worldServer == null) { - return null; - } - - // Create a new ServerPlayer. - ServerPlayer entity = createNewPlayer(server, worldServer, offline); - - // Stop listening for advancement progression - if this is not cleaned up, loading causes a memory leak. - entity.getAdvancements().stopListening(); - - // Try to load the player's data. - if (loadData(entity)) { - // If data is loaded successfully, return the Bukkit entity. - return entity.getBukkitEntity(); - } - - return null; - } - - private @NotNull ServerPlayer createNewPlayer( - @NotNull MinecraftServer server, - @NotNull ServerLevel worldServer, - @NotNull final OfflinePlayer offline) { - // See net.minecraft.server.players.PlayerList#canPlayerLogin(ServerLoginPacketListenerImpl, GameProfile) - // See net.minecraft.server.network.ServerLoginPacketListenerImpl#handleHello(ServerboundHelloPacket) - GameProfile profile = new GameProfile(offline.getUniqueId(), - offline.getName() != null ? offline.getName() : offline.getUniqueId().toString()); - - ClientInformation dummyInfo = new ClientInformation( - "en_us", - 1, // Reduce distance just in case. - ChatVisiblity.HIDDEN, // Don't accept chat. - false, - ServerPlayer.DEFAULT_MODEL_CUSTOMIZATION, - ServerPlayer.DEFAULT_MAIN_HAND, - true, - false // Don't list in player list (not that this player is in the list anyway). - ); - - ServerPlayer entity = new ServerPlayer(server, worldServer, profile, dummyInfo); - - try { - injectPlayer(entity); - } catch (IllegalAccessException e) { - logger.log( - java.util.logging.Level.WARNING, - e, - () -> "Unable to inject ServerPlayer, certain player data may be lost when saving!"); - } - - return entity; - } - - boolean loadData(@NotNull ServerPlayer player) { - // See CraftPlayer#loadData - CompoundTag loadedData = player.server.getPlayerList().playerIo.load(player).orElse(null); - - if (loadedData == null) { - // Exceptions with loading are logged by Mojang. - return false; - } - - // Read basic data into the player. - player.load(loadedData); - // Also read "extra" data. - player.readAdditionalSaveData(loadedData); - // Game type settings are also loaded separately. - player.loadGameTypes(loadedData); - - if (paper) { - // Paper: world is not loaded by ServerPlayer#load(CompoundTag). - parseWorld(player, loadedData); - } - - return true; - } - - private void parseWorld(@NotNull ServerPlayer player, @NotNull CompoundTag loadedData) { - // See PlayerList#placeNewPlayer - World bukkitWorld; - if (loadedData.contains("WorldUUIDMost") && loadedData.contains("WorldUUIDLeast")) { - // Modern Bukkit world. - bukkitWorld = Bukkit.getServer().getWorld(new UUID(loadedData.getLong("WorldUUIDMost"), loadedData.getLong("WorldUUIDLeast"))); - } else if (loadedData.contains("world", net.minecraft.nbt.Tag.TAG_STRING)) { - // Legacy Bukkit world. - bukkitWorld = Bukkit.getServer().getWorld(loadedData.getString("world")); - } else { - // Vanilla player data. - DimensionType.parseLegacy(new Dynamic<>(NbtOps.INSTANCE, loadedData.get("Dimension"))) - .resultOrPartial(logger::warning) - .map(player.server::getLevel) - // If ServerLevel exists, set, otherwise move to spawn. - .ifPresentOrElse(player::setServerLevel, () -> player.spawnIn(null)); - return; - } - if (bukkitWorld == null) { - player.spawnIn(null); - return; - } - player.setServerLevel(((CraftWorld) bukkitWorld).getHandle()); - } - - private void injectPlayer(ServerPlayer player) throws IllegalAccessException { - if (bukkitEntity == null) { - return; - } - - bukkitEntity.setAccessible(true); - - bukkitEntity.set(player, new OpenPlayer(player.server.server, player, this)); - } - - @Override - public @NotNull Player inject(@NotNull Player player) { - try { - ServerPlayer nmsPlayer = getHandle(player); - if (nmsPlayer.getBukkitEntity() instanceof OpenPlayer openPlayer) { - return openPlayer; - } - injectPlayer(nmsPlayer); - return nmsPlayer.getBukkitEntity(); - } catch (IllegalAccessException e) { - logger.log( - java.util.logging.Level.WARNING, - e, - () -> "Unable to inject ServerPlayer, certain player data may be lost when saving!"); - return player; - } - } - - @Override - public @Nullable InventoryView openInventory(@NotNull Player player, @NotNull ISpecialInventory inventory, boolean viewOnly) { - - ServerPlayer nmsPlayer = getHandle(player); - - if (nmsPlayer.connection == null) { - return null; - } - - InventoryViewTitle title = InventoryViewTitle.of(inventory); - - if (title == null) { - return player.openInventory(inventory.getBukkitInventory()); - } - - InventoryView view = new OpenInventoryView(player, inventory, title.getTitle(lang, player, inventory)); - AbstractContainerMenu container = new CraftContainer(view, nmsPlayer, nmsPlayer.nextContainerCounter()) { - @Override - public MenuType getType() { - return getContainers(inventory.getBukkitInventory().getSize()); - } - }; - - container.setTitle(Component.literal(view.getOriginalTitle())); - container = CraftEventFactory.callInventoryOpenEvent(nmsPlayer, container); - - if (container == null) { - return null; - } - - nmsPlayer.connection.send(new ClientboundOpenScreenPacket(container.containerId, container.getType(), - Component.literal(container.getBukkitView().getTitle()))); - nmsPlayer.containerMenu = container; - nmsPlayer.initMenu(container); - - return container.getBukkitView(); - - } - - static @NotNull MenuType getContainers(int inventorySize) { - - return switch (inventorySize) { - case 9 -> MenuType.GENERIC_9x1; - case 18 -> MenuType.GENERIC_9x2; - case 36 -> MenuType.GENERIC_9x4; // PLAYER - case 41, 45 -> MenuType.GENERIC_9x5; - case 54 -> MenuType.GENERIC_9x6; - default -> MenuType.GENERIC_9x3; // Default 27-slot inventory - }; - } - -} diff --git a/internal/v1_20_R4/src/main/java/com/lishid/openinv/internal/v1_20_R4/SpecialEnderChest.java b/internal/v1_20_R4/src/main/java/com/lishid/openinv/internal/v1_20_R4/SpecialEnderChest.java deleted file mode 100644 index d38ac428..00000000 --- a/internal/v1_20_R4/src/main/java/com/lishid/openinv/internal/v1_20_R4/SpecialEnderChest.java +++ /dev/null @@ -1,352 +0,0 @@ -/* - * Copyright (C) 2011-2023 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.v1_20_R4; - -import com.lishid.openinv.internal.ISpecialEnderChest; -import net.minecraft.core.HolderLookup; -import net.minecraft.core.NonNullList; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.nbt.ListTag; -import net.minecraft.server.level.ServerPlayer; -import net.minecraft.world.ContainerHelper; -import net.minecraft.world.ContainerListener; -import net.minecraft.world.entity.player.Player; -import net.minecraft.world.entity.player.StackedContents; -import net.minecraft.world.inventory.PlayerEnderChestContainer; -import net.minecraft.world.item.Item; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.level.block.entity.EnderChestBlockEntity; -import org.bukkit.Location; -import org.bukkit.craftbukkit.v1_20_R4.entity.CraftHumanEntity; -import org.bukkit.craftbukkit.v1_20_R4.inventory.CraftInventory; -import org.bukkit.entity.HumanEntity; -import org.bukkit.inventory.InventoryHolder; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.List; -import java.util.function.Predicate; -import java.util.stream.Collectors; - -public class SpecialEnderChest extends PlayerEnderChestContainer implements ISpecialEnderChest { - - private final CraftInventory inventory; - private ServerPlayer owner; - private NonNullList items; - private boolean playerOnline; - - public SpecialEnderChest(final org.bukkit.entity.Player player, final Boolean online) { - super(PlayerManager.getHandle(player)); - this.inventory = new CraftInventory(this); - this.owner = PlayerManager.getHandle(player); - this.playerOnline = online; - this.items = this.owner.getEnderChestInventory().items; - } - - @Override - public @NotNull CraftInventory getBukkitInventory() { - return inventory; - } - - @Override - public void setPlayerOffline() { - this.playerOnline = false; - } - - @Override - public void setPlayerOnline(@NotNull final org.bukkit.entity.Player player) { - if (this.playerOnline) { - return; - } - - ServerPlayer offlinePlayer = this.owner; - ServerPlayer onlinePlayer = PlayerManager.getHandle(player); - - // Set owner to new player. - this.owner = onlinePlayer; - - // Set player's ender chest contents to our modified contents. - PlayerEnderChestContainer onlineEnderChest = onlinePlayer.getEnderChestInventory(); - for (int i = 0; i < onlineEnderChest.getContainerSize(); ++i) { - onlineEnderChest.setItem(i, this.items.get(i)); - } - - // Set our item array to the new inventory's array. - this.items = onlineEnderChest.items; - - // Add viewers to new inventory. - onlineEnderChest.transaction.addAll(offlinePlayer.getEnderChestInventory().transaction); - - this.playerOnline = true; - } - - @Override - public @NotNull org.bukkit.entity.Player getPlayer() { - return owner.getBukkitEntity(); - } - - @Override - public void setChanged() { - this.owner.getEnderChestInventory().setChanged(); - } - - @Override - public List getContents() { - return this.items; - } - - @Override - public void onOpen(CraftHumanEntity who) { - this.owner.getEnderChestInventory().onOpen(who); - } - - @Override - public void onClose(CraftHumanEntity who) { - this.owner.getEnderChestInventory().onClose(who); - } - - @Override - public List getViewers() { - return this.owner.getEnderChestInventory().getViewers(); - } - - @Override - public boolean stillValid(Player player) { - return true; - } - - @Override - public void setActiveChest(EnderChestBlockEntity enderChest) { - this.owner.getEnderChestInventory().setActiveChest(enderChest); - } - - @Override - public boolean isActiveChest(EnderChestBlockEntity enderChest) { - return this.owner.getEnderChestInventory().isActiveChest(enderChest); - } - - @Override - public int getMaxStackSize() { - return this.owner.getEnderChestInventory().getMaxStackSize(); - } - - @Override - public void setMaxStackSize(int i) { - this.owner.getEnderChestInventory().setMaxStackSize(i); - } - - @Override - public InventoryHolder getOwner() { - return this.owner.getEnderChestInventory().getOwner(); - } - - @Override - public @Nullable Location getLocation() { - return null; - } - - @Override - public void addListener(ContainerListener listener) { - this.owner.getEnderChestInventory().addListener(listener); - } - - @Override - public void removeListener(ContainerListener listener) { - this.owner.getEnderChestInventory().removeListener(listener); - } - - @Override - public ItemStack getItem(int i) { - return i >= 0 && i < this.items.size() ? this.items.get(i) : ItemStack.EMPTY; - } - - @Override - public ItemStack removeItem(int i, int j) { - ItemStack itemstack = ContainerHelper.removeItem(this.items, i, j); - if (!itemstack.isEmpty()) { - this.setChanged(); - } - - return itemstack; - } - - @Override - public ItemStack addItem(ItemStack itemstack) { - ItemStack localItem = itemstack.copy(); - this.moveItemToOccupiedSlotsWithSameType(localItem); - if (localItem.isEmpty()) { - return ItemStack.EMPTY; - } else { - this.moveItemToEmptySlots(localItem); - return localItem.isEmpty() ? ItemStack.EMPTY : localItem; - } - } - - @Override - public boolean canAddItem(ItemStack itemstack) { - for (ItemStack itemstack1 : this.items) { - if (itemstack1.isEmpty() || (ItemStack.isSameItemSameComponents(itemstack1, itemstack) && itemstack1.getCount() < itemstack1.getMaxStackSize())) { - return true; - } - } - - return false; - } - - private void moveItemToEmptySlots(ItemStack itemstack) { - for(int i = 0; i < this.getContainerSize(); ++i) { - ItemStack localItem = this.getItem(i); - if (localItem.isEmpty()) { - this.setItem(i, itemstack.copy()); - itemstack.setCount(0); - return; - } - } - } - - private void moveItemToOccupiedSlotsWithSameType(ItemStack itemstack) { - for(int i = 0; i < this.getContainerSize(); ++i) { - ItemStack localItem = this.getItem(i); - if (ItemStack.isSameItemSameComponents(localItem, itemstack)) { - this.moveItemsBetweenStacks(itemstack, localItem); - if (itemstack.isEmpty()) { - return; - } - } - } - } - - private void moveItemsBetweenStacks(ItemStack itemstack, ItemStack itemstack1) { - int i = Math.min(this.getMaxStackSize(), itemstack1.getMaxStackSize()); - int j = Math.min(itemstack.getCount(), i - itemstack1.getCount()); - if (j > 0) { - itemstack1.grow(j); - itemstack.shrink(j); - this.setChanged(); - } - } - - @Override - public ItemStack removeItemNoUpdate(int i) { - ItemStack itemstack = this.items.get(i); - if (itemstack.isEmpty()) { - return ItemStack.EMPTY; - } else { - this.items.set(i, ItemStack.EMPTY); - return itemstack; - } - } - - @Override - public void setItem(int i, ItemStack itemstack) { - this.items.set(i, itemstack); - if (!itemstack.isEmpty() && itemstack.getCount() > this.getMaxStackSize()) { - itemstack.setCount(this.getMaxStackSize()); - } - - this.setChanged(); - } - - @Override - public int getContainerSize() { - return this.owner.getEnderChestInventory().getContainerSize(); - } - - @Override - public boolean isEmpty() { - return this.items.stream().allMatch(ItemStack::isEmpty); - } - - @Override - public void startOpen(Player player) { - } - - @Override - public void stopOpen(Player player) { - } - - @Override - public boolean canPlaceItem(int i, ItemStack itemstack) { - return true; - } - - @Override - public void clearContent() { - this.items.clear(); - this.setChanged(); - } - - @Override - public void fillStackedContents(StackedContents stackedContents) { - for (ItemStack itemstack : this.items) { - stackedContents.accountStack(itemstack); - } - - } - - @Override - public List removeAllItems() { - List list = this.items.stream().filter(Predicate.not(ItemStack::isEmpty)).collect(Collectors.toList()); - this.clearContent(); - return list; - } - - @Override - public ItemStack removeItemType(Item item, int i) { - ItemStack itemstack = new ItemStack(item, 0); - - for(int j = this.getContainerSize() - 1; j >= 0; --j) { - ItemStack localItem = this.getItem(j); - if (localItem.getItem().equals(item)) { - int k = i - itemstack.getCount(); - ItemStack splitItem = localItem.split(k); - itemstack.grow(splitItem.getCount()); - if (itemstack.getCount() == i) { - break; - } - } - } - - if (!itemstack.isEmpty()) { - this.setChanged(); - } - - return itemstack; - } - - @Override - public String toString() { - return this.items.stream().filter((itemStack) -> !itemStack.isEmpty()).toList().toString(); - } - - @Override - public void fromTag(ListTag listTag, HolderLookup.Provider holderLookup) { - for (int i = 0; i < this.getContainerSize(); ++i) { - this.setItem(i, ItemStack.EMPTY); - } - - for (int i = 0; i < listTag.size(); ++i) { - CompoundTag compoundTag = listTag.getCompound(i); - int j = compoundTag.getByte("Slot") & 255; - if (j < this.getContainerSize()) { - this.setItem(j, ItemStack.parseOptional(holderLookup, compoundTag)); - } - } - - } - -} diff --git a/internal/v1_20_R4/src/main/java/com/lishid/openinv/internal/v1_20_R4/SpecialPlayerInventory.java b/internal/v1_20_R4/src/main/java/com/lishid/openinv/internal/v1_20_R4/SpecialPlayerInventory.java deleted file mode 100644 index 155f4224..00000000 --- a/internal/v1_20_R4/src/main/java/com/lishid/openinv/internal/v1_20_R4/SpecialPlayerInventory.java +++ /dev/null @@ -1,768 +0,0 @@ -/* - * Copyright (C) 2011-2023 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.v1_20_R4; - -import com.google.common.collect.ImmutableList; -import com.lishid.openinv.internal.ISpecialPlayerInventory; -import net.minecraft.CrashReport; -import net.minecraft.CrashReportCategory; -import net.minecraft.ReportedException; -import net.minecraft.core.NonNullList; -import net.minecraft.core.component.DataComponents; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.nbt.ListTag; -import net.minecraft.network.chat.Component; -import net.minecraft.network.protocol.game.ClientboundContainerSetSlotPacket; -import net.minecraft.server.level.ServerPlayer; -import net.minecraft.tags.TagKey; -import net.minecraft.world.Container; -import net.minecraft.world.ContainerHelper; -import net.minecraft.world.entity.player.Inventory; -import net.minecraft.world.entity.player.Player; -import net.minecraft.world.entity.player.StackedContents; -import net.minecraft.world.item.Item; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.level.block.state.BlockState; -import org.bukkit.Location; -import org.bukkit.craftbukkit.v1_20_R4.entity.CraftHumanEntity; -import org.bukkit.craftbukkit.v1_20_R4.inventory.CraftInventory; -import org.bukkit.entity.HumanEntity; -import org.bukkit.inventory.InventoryHolder; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.Collection; -import java.util.Iterator; -import java.util.List; -import java.util.function.Function; -import java.util.function.Predicate; -import java.util.stream.Collectors; - -public class SpecialPlayerInventory extends Inventory implements ISpecialPlayerInventory { - - private final CraftInventory inventory; - private boolean playerOnline; - private Player player; - private NonNullList items; - private NonNullList armor; - private NonNullList offhand; - private List> compartments; - - public SpecialPlayerInventory(@NotNull org.bukkit.entity.Player bukkitPlayer, @NotNull Boolean online) { - super(PlayerManager.getHandle(bukkitPlayer)); - this.inventory = new CraftInventory(this); - this.playerOnline = online; - this.player = super.player; - this.selected = player.getInventory().selected; - this.items = this.player.getInventory().items; - this.armor = this.player.getInventory().armor; - this.offhand = this.player.getInventory().offhand; - this.compartments = ImmutableList.of(this.items, this.armor, this.offhand); - } - - @Override - public void setPlayerOnline(@NotNull org.bukkit.entity.Player player) { - if (this.playerOnline) { - return; - } - - Player offlinePlayer = this.player; - Player onlinePlayer = PlayerManager.getHandle(player); - onlinePlayer.getInventory().transaction.addAll(this.transaction); - - // Set owner to new player. - this.player = onlinePlayer; - - // Set player's inventory contents to our modified contents. - Inventory onlineInventory = onlinePlayer.getInventory(); - for (int i = 0; i < getContainerSize(); ++i) { - onlineInventory.setItem(i, getRawItem(i)); - } - onlineInventory.selected = this.selected; - - // Set our item arrays to the new inventory's arrays. - this.items = onlineInventory.items; - this.armor = onlineInventory.armor; - this.offhand = onlineInventory.offhand; - this.compartments = ImmutableList.of(this.items, this.armor, this.offhand); - - // Add existing viewers to new viewer list. - Inventory offlineInventory = offlinePlayer.getInventory(); - // Remove self from listing - player is always a viewer of their own inventory, prevent duplicates. - offlineInventory.transaction.remove(offlinePlayer.getBukkitEntity()); - onlineInventory.transaction.addAll(offlineInventory.transaction); - - this.playerOnline = true; - } - - @Override - public @NotNull CraftInventory getBukkitInventory() { - return this.inventory; - } - - @Override - public void setPlayerOffline() { - this.playerOnline = false; - } - - @Override - public @NotNull HumanEntity getPlayer() { - return this.player.getBukkitEntity(); - } - - private @NotNull ItemStack getRawItem(int i) { - if (i < 0) { - return ItemStack.EMPTY; - } - - NonNullList list; - for (Iterator> iterator = this.compartments.iterator(); iterator.hasNext(); i -= list.size()) { - list = iterator.next(); - if (i < list.size()) { - return list.get(i); - } - } - - return ItemStack.EMPTY; - } - - private void setRawItem(int i, @NotNull ItemStack itemStack) { - if (i < 0) { - return; - } - - NonNullList list; - for (Iterator> iterator = this.compartments.iterator(); iterator.hasNext(); i -= list.size()) { - list = iterator.next(); - if (i < list.size()) { - list.set(i, itemStack); - } - } - } - - private record IndexedCompartment(@Nullable NonNullList compartment, int index) {} - - private @NotNull SpecialPlayerInventory.IndexedCompartment getIndexedContent(int index) { - if (index < items.size()) { - return new IndexedCompartment(items, getReversedItemSlotNum(index)); - } - - index -= items.size(); - - if (index < armor.size()) { - return new IndexedCompartment(armor, getReversedArmorSlotNum(index)); - } - - index -= armor.size(); - - if (index < offhand.size()) { - return new IndexedCompartment(offhand, index); - } - - index -= offhand.size(); - - return new IndexedCompartment(null, index); - } - - private int getReversedArmorSlotNum(final int i) { - if (i == 0) { - return 3; - } - if (i == 1) { - return 2; - } - if (i == 2) { - return 1; - } - if (i == 3) { - return 0; - } - return i; - } - - private int getReversedItemSlotNum(final int i) { - if (i >= 27) { - return i - 27; - } - return i + 9; - } - - @Override - public boolean contains(Predicate predicate) { - return this.compartments.stream().flatMap(NonNullList::stream).anyMatch(predicate); - } - - @Override - public List getArmorContents() { - return this.armor; - } - - @Override - public void onOpen(CraftHumanEntity who) { - this.player.getInventory().onOpen(who); - } - - @Override - public void onClose(CraftHumanEntity who) { - this.player.getInventory().onClose(who); - } - - @Override - public List getViewers() { - return this.player.getInventory().getViewers(); - } - - @Override - public InventoryHolder getOwner() { - return this.player.getBukkitEntity(); - } - - @Override - public int getMaxStackSize() { - return this.player.getInventory().getMaxStackSize(); - } - - @Override - public void setMaxStackSize(int size) { - this.player.getInventory().setMaxStackSize(size); - } - - @Override - public Location getLocation() { - return this.player.getBukkitEntity().getLocation(); - } - - @Override - public boolean hasCustomName() { - return false; - } - - @Override - public List getContents() { - return this.compartments.stream().flatMap(Collection::stream).collect(Collectors.toList()); - } - - @Override - public ItemStack getSelected() { - return isHotbarSlot(this.selected) ? this.items.get(this.selected) : ItemStack.EMPTY; - } - - private boolean hasRemainingSpaceForItem(ItemStack itemstack, ItemStack itemstack1) { - return !itemstack.isEmpty() && ItemStack.isSameItemSameComponents(itemstack, itemstack1) && itemstack.isStackable() && itemstack.getCount() < itemstack.getMaxStackSize() && itemstack.getCount() < this.getMaxStackSize(); - } - - @Override - public int canHold(ItemStack itemstack) { - int remains = itemstack.getCount(); - - for (int i = 0; i < this.items.size(); ++i) { - ItemStack itemstack1 = this.getRawItem(i); - if (itemstack1.isEmpty()) { - return itemstack.getCount(); - } - - if (this.hasRemainingSpaceForItem(itemstack1, itemstack)) { - remains -= (itemstack1.getMaxStackSize() < this.getMaxStackSize() ? itemstack1.getMaxStackSize() : this.getMaxStackSize()) - itemstack1.getCount(); - } - - if (remains <= 0) { - return itemstack.getCount(); - } - } - - ItemStack offhandItemStack = this.getRawItem(this.items.size() + this.armor.size()); - if (this.hasRemainingSpaceForItem(offhandItemStack, itemstack)) { - remains -= (offhandItemStack.getMaxStackSize() < this.getMaxStackSize() ? offhandItemStack.getMaxStackSize() : this.getMaxStackSize()) - offhandItemStack.getCount(); - } - - return remains <= 0 ? itemstack.getCount() : itemstack.getCount() - remains; - } - - @Override - public int getFreeSlot() { - for(int i = 0; i < this.items.size(); ++i) { - if (this.items.get(i).isEmpty()) { - return i; - } - } - - return -1; - } - - @Override - public void setPickedItem(ItemStack itemstack) { - int i = this.findSlotMatchingItem(itemstack); - if (isHotbarSlot(i)) { - this.selected = i; - } else if (i == -1) { - this.selected = this.getSuitableHotbarSlot(); - if (!this.items.get(this.selected).isEmpty()) { - int j = this.getFreeSlot(); - if (j != -1) { - this.items.set(j, this.items.get(this.selected)); - } - } - - this.items.set(this.selected, itemstack); - } else { - this.pickSlot(i); - } - - } - - @Override - public void pickSlot(int i) { - this.selected = this.getSuitableHotbarSlot(); - ItemStack itemstack = this.items.get(this.selected); - this.items.set(this.selected, this.items.get(i)); - this.items.set(i, itemstack); - } - - @Override - public int findSlotMatchingItem(ItemStack itemstack) { - for(int i = 0; i < this.items.size(); ++i) { - if (!this.items.get(i).isEmpty() && ItemStack.isSameItemSameComponents(itemstack, this.items.get(i))) { - return i; - } - } - - return -1; - } - - @Override - public int findSlotMatchingUnusedItem(ItemStack itemStack) { - for(int i = 0; i < this.items.size(); ++i) { - ItemStack localItem = this.items.get(i); - if (!this.items.get(i).isEmpty() && ItemStack.isSameItemSameComponents(itemStack, this.items.get(i)) && !this.items.get(i).isDamaged() && !localItem.isEnchanted() && !localItem.has(DataComponents.CUSTOM_NAME)) { - return i; - } - } - - return -1; - } - - @Override - public int getSuitableHotbarSlot() { - int i; - int j; - for(j = 0; j < 9; ++j) { - i = (this.selected + j) % 9; - if (this.items.get(i).isEmpty()) { - return i; - } - } - - for(j = 0; j < 9; ++j) { - i = (this.selected + j) % 9; - if (!this.items.get(i).isEnchanted()) { - return i; - } - } - - return this.selected; - } - - @Override - public void swapPaint(double d0) { - int i = (int) Math.signum(d0); - - this.selected -= i; - - while (this.selected < 0) { - this.selected += 9; - } - - while(this.selected >= 9) { - this.selected -= 9; - } - } - - @Override - public int clearOrCountMatchingItems(Predicate predicate, int i, Container container) { - byte b0 = 0; - boolean flag = i == 0; - int j = b0 + ContainerHelper.clearOrCountMatchingItems(this, predicate, i - b0, flag); - j += ContainerHelper.clearOrCountMatchingItems(container, predicate, i - j, flag); - ItemStack itemstack = this.player.containerMenu.getCarried(); - j += ContainerHelper.clearOrCountMatchingItems(itemstack, predicate, i - j, flag); - if (itemstack.isEmpty()) { - this.player.containerMenu.setCarried(ItemStack.EMPTY); - } - - return j; - } - - private int addResource(ItemStack itemstack) { - int i = this.getSlotWithRemainingSpace(itemstack); - if (i == -1) { - i = this.getFreeSlot(); - } - - return i == -1 ? itemstack.getCount() : this.addResource(i, itemstack); - } - - private int addResource(int i, ItemStack itemstack) { - int j = itemstack.getCount(); - ItemStack localItemStack = this.getRawItem(i); - if (localItemStack.isEmpty()) { - localItemStack = itemstack.copyWithCount(0); - this.setRawItem(i, localItemStack); - } - - int k = Math.min(j, this.getMaxStackSize(localItemStack) - localItemStack.getCount()); - - if (k != 0) { - j -= k; - localItemStack.grow(k); - localItemStack.setPopTime(5); - } - - return j; - } - - @Override - public int getSlotWithRemainingSpace(ItemStack itemstack) { - if (this.hasRemainingSpaceForItem(this.getRawItem(this.selected), itemstack)) { - return this.selected; - } else if (this.hasRemainingSpaceForItem(this.getRawItem(40), itemstack)) { - return 40; - } else { - for(int i = 0; i < this.items.size(); ++i) { - if (this.hasRemainingSpaceForItem(this.items.get(i), itemstack)) { - return i; - } - } - - return -1; - } - } - - @Override - public void tick() { - for (NonNullList compartment : this.compartments) { - for (int i = 0; i < compartment.size(); ++i) { - if (!compartment.get(i).isEmpty()) { - compartment.get(i).inventoryTick(this.player.level(), this.player, i, this.selected == i); - } - } - } - - } - - @Override - public boolean add(ItemStack itemStack) { - return this.add(-1, itemStack); - } - - @Override - public boolean add(int i, ItemStack itemStack) { - if (itemStack.isEmpty()) { - return false; - } else { - try { - if (itemStack.isDamaged()) { - if (i == -1) { - i = this.getFreeSlot(); - } - - if (i >= 0) { - this.items.set(i, itemStack.copy()); - this.items.get(i).setPopTime(5); - itemStack.setCount(0); - return true; - } else if (this.player.hasInfiniteMaterials()) { - itemStack.setCount(0); - return true; - } else { - return false; - } - } else { - int j; - do { - j = itemStack.getCount(); - if (i == -1) { - itemStack.setCount(this.addResource(itemStack)); - } else { - itemStack.setCount(this.addResource(i, itemStack)); - } - } while(!itemStack.isEmpty() && itemStack.getCount() < j); - - if (itemStack.getCount() == j && this.player.hasInfiniteMaterials()) { - itemStack.setCount(0); - return true; - } else { - return itemStack.getCount() < j; - } - } - } catch (Throwable var6) { - CrashReport crashReport = CrashReport.forThrowable(var6, "Adding item to inventory"); - CrashReportCategory crashReportCategory = crashReport.addCategory("Item being added"); - crashReportCategory.setDetail("Item ID", Item.getId(itemStack.getItem())); - crashReportCategory.setDetail("Item data", itemStack.getDamageValue()); - crashReportCategory.setDetail("Item name", () -> itemStack.getHoverName().getString()); - throw new ReportedException(crashReport); - } - } - } - - @Override - public void placeItemBackInInventory(ItemStack itemStack) { - this.placeItemBackInInventory(itemStack, true); - } - - @Override - public void placeItemBackInInventory(ItemStack itemStack, boolean flag) { - while(true) { - if (!itemStack.isEmpty()) { - int i = this.getSlotWithRemainingSpace(itemStack); - if (i == -1) { - i = this.getFreeSlot(); - } - - if (i != -1) { - int j = itemStack.getMaxStackSize() - this.getRawItem(i).getCount(); - if (this.add(i, itemStack.split(j)) && flag && this.player instanceof ServerPlayer) { - ((ServerPlayer)this.player).connection.send(new ClientboundContainerSetSlotPacket(-2, 0, i, this.getRawItem(i))); - } - continue; - } - - this.player.drop(itemStack, false); - } - - return; - } - } - - @Override - public ItemStack removeItem(int rawIndex, final int j) { - IndexedCompartment indexedCompartment = getIndexedContent(rawIndex); - - if (indexedCompartment.compartment() == null - || indexedCompartment.compartment().get(indexedCompartment.index()).isEmpty()) { - return ItemStack.EMPTY; - } - - return ContainerHelper.removeItem(indexedCompartment.compartment(), indexedCompartment.index(), j); - } - - @Override - public void removeItem(ItemStack itemStack) { - for (NonNullList compartment : this.compartments) { - for (int i = 0; i < compartment.size(); ++i) { - if (compartment.get(i) == itemStack) { - compartment.set(i, ItemStack.EMPTY); - break; - } - } - } - } - - @Override - public ItemStack removeItemNoUpdate(int rawIndex) { - IndexedCompartment indexedCompartment = getIndexedContent(rawIndex); - - if (indexedCompartment.compartment() == null) { - return ItemStack.EMPTY; - } - - ItemStack removed = indexedCompartment.compartment().set(indexedCompartment.index(), ItemStack.EMPTY); - - if (removed.isEmpty()) { - return ItemStack.EMPTY; - } - - return removed; - } - - @Override - public void setItem(int rawIndex, final ItemStack itemStack) { - IndexedCompartment indexedCompartment = getIndexedContent(rawIndex); - - if (indexedCompartment.compartment() == null) { - this.player.drop(itemStack, true); - return; - } - - indexedCompartment.compartment().set(indexedCompartment.index(), itemStack); - } - - @Override - public float getDestroySpeed(BlockState blockState) { - return this.items.get(this.selected).getDestroySpeed(blockState); - } - - @Override - public ListTag save(ListTag listTag) { - for (int i = 0; i < this.items.size(); ++i) { - if (!this.items.get(i).isEmpty()) { - CompoundTag compoundTag = new CompoundTag(); - compoundTag.putByte("Slot", (byte)i); - this.items.get(i).save(this.player.registryAccess(), compoundTag); - listTag.add(compoundTag); - } - } - - for (int i = 0; i < this.armor.size(); ++i) { - if (!this.armor.get(i).isEmpty()) { - CompoundTag compoundTag = new CompoundTag(); - compoundTag.putByte("Slot", (byte)(i + 100)); - this.armor.get(i).save(this.player.registryAccess(), compoundTag); - listTag.add(compoundTag); - } - } - - for (int i = 0; i < this.offhand.size(); ++i) { - if (!this.offhand.get(i).isEmpty()) { - CompoundTag compoundTag = new CompoundTag(); - compoundTag.putByte("Slot", (byte)(i + 150)); - this.offhand.get(i).save(this.player.registryAccess(), compoundTag); - listTag.add(compoundTag); - } - } - - return listTag; - } - - @Override - public void load(ListTag listTag) { - this.items.clear(); - this.armor.clear(); - this.offhand.clear(); - - for(int i = 0; i < listTag.size(); ++i) { - CompoundTag compoundTag = listTag.getCompound(i); - int j = compoundTag.getByte("Slot") & 255; - ItemStack itemstack = ItemStack.parse(this.player.registryAccess(), compoundTag).orElse(ItemStack.EMPTY); - if (j < this.items.size()) { - this.items.set(j, itemstack); - } else if (j >= 100 && j < this.armor.size() + 100) { - this.armor.set(j - 100, itemstack); - } else if (j >= 150 && j < this.offhand.size() + 150) { - this.offhand.set(j - 150, itemstack); - } - } - - } - - @Override - public int getContainerSize() { - return 45; - } - - @Override - public boolean isEmpty() { - return !contains(itemStack -> !itemStack.isEmpty()); - } - - @Override - public ItemStack getItem(int rawIndex) { - IndexedCompartment indexedCompartment = getIndexedContent(rawIndex); - - if (indexedCompartment.compartment() == null) { - return ItemStack.EMPTY; - } - - return indexedCompartment.compartment().get(indexedCompartment.index()); - } - - @Override - public Component getName() { - return this.player.getName(); - } - - @Override - public ItemStack getArmor(int index) { - return this.armor.get(index); - } - - @Override - public void dropAll() { - for (NonNullList compartment : this.compartments) { - for (int i = 0; i < compartment.size(); ++i) { - ItemStack itemstack = compartment.get(i); - if (!itemstack.isEmpty()) { - this.player.drop(itemstack, true, false); - compartment.set(i, ItemStack.EMPTY); - } - } - } - } - - @Override - public void setChanged() { - super.setChanged(); - } - - @Override - public int getTimesChanged() { - return super.getTimesChanged(); - } - - @Override - public boolean stillValid(Player player) { - return true; - } - - @Override - public boolean contains(ItemStack itemstack) { - return contains(itemStack -> !itemStack.isEmpty() && ItemStack.isSameItemSameComponents(itemstack, itemStack)); - } - - @Override - public boolean contains(TagKey tagKey) { - return contains(itemStack -> !itemStack.isEmpty() && itemStack.is(tagKey)); - } - - @Override - public void replaceWith(Inventory inventory) { - Function getter; - - if (inventory instanceof SpecialPlayerInventory specialPlayerInventory) { - getter = specialPlayerInventory::getRawItem; - } else { - getter = inventory::getItem; - } - - for(int i = 0; i < this.getContainerSize(); ++i) { - this.setRawItem(i, getter.apply(i)); - } - - this.selected = inventory.selected; - } - - @Override - public void clearContent() { - for (NonNullList compartment : this.compartments) { - compartment.clear(); - } - } - - @Override - public void fillStackedContents(StackedContents stackedContents) { - for (ItemStack itemstack : this.items) { - stackedContents.accountSimpleStack(itemstack); - } - } - - @Override - public ItemStack removeFromSelected(boolean dropWholeStack) { - ItemStack itemstack = this.getSelected(); - return itemstack.isEmpty() ? ItemStack.EMPTY : this.removeItem(this.selected, dropWholeStack ? itemstack.getCount() : 1); - } - -} diff --git a/internal/v1_21_R1/src/main/java/com/lishid/openinv/internal/v1_21_R1/container/menu/OpenChestMenu.java b/internal/v1_21_R1/src/main/java/com/lishid/openinv/internal/v1_21_R1/container/menu/OpenChestMenu.java index cf95822c..512ab769 100644 --- a/internal/v1_21_R1/src/main/java/com/lishid/openinv/internal/v1_21_R1/container/menu/OpenChestMenu.java +++ b/internal/v1_21_R1/src/main/java/com/lishid/openinv/internal/v1_21_R1/container/menu/OpenChestMenu.java @@ -45,7 +45,7 @@ public abstract class OpenChestMenu> bukkitEntity; + private CraftInventoryView, Inventory> bukkitEntity; // Syncher fields private @Nullable ContainerSynchronizer synchronizer; private final List dataSlots = new ArrayList<>(); @@ -132,7 +132,7 @@ protected void preSlotSetup() {} @Override - public final @NotNull CraftInventoryView> getBukkitView() { + public final @NotNull CraftInventoryView, Inventory> getBukkitView() { if (bukkitEntity == null) { bukkitEntity = createBukkitEntity(); } @@ -140,7 +140,7 @@ protected void preSlotSetup() {} return bukkitEntity; } - protected @NotNull CraftInventoryView> createBukkitEntity() { + protected @NotNull CraftInventoryView, Inventory> createBukkitEntity() { Inventory top; if (viewOnly) { top = new OpenDummyInventory(container); diff --git a/internal/v1_21_R1/src/main/java/com/lishid/openinv/internal/v1_21_R1/container/menu/OpenInventoryMenu.java b/internal/v1_21_R1/src/main/java/com/lishid/openinv/internal/v1_21_R1/container/menu/OpenInventoryMenu.java index d5567f62..f0c75a6f 100644 --- a/internal/v1_21_R1/src/main/java/com/lishid/openinv/internal/v1_21_R1/container/menu/OpenInventoryMenu.java +++ b/internal/v1_21_R1/src/main/java/com/lishid/openinv/internal/v1_21_R1/container/menu/OpenInventoryMenu.java @@ -99,7 +99,7 @@ protected void preSlotSetup() { } @Override - protected @NotNull CraftInventoryView> createBukkitEntity() { + protected @NotNull CraftInventoryView, Inventory> createBukkitEntity() { org.bukkit.inventory.Inventory bukkitInventory; if (viewOnly) { bukkitInventory = new OpenDummyInventory(container); diff --git a/internal/v1_20_R4/pom.xml b/internal/v1_21_R2/pom.xml similarity index 94% rename from internal/v1_20_R4/pom.xml rename to internal/v1_21_R2/pom.xml index 1741cc09..024f2ab8 100644 --- a/internal/v1_20_R4/pom.xml +++ b/internal/v1_21_R2/pom.xml @@ -26,13 +26,13 @@ 5.1.3-SNAPSHOT - openinvadapter1_20_R4 - OpenInvAdapter1_20_R4 + openinvadapter1_21_R2 + OpenInvAdapter1_21_R2 21 21 - 1.20.6-R0.1-SNAPSHOT + 1.21.3-R0.1-SNAPSHOT diff --git a/internal/v1_20_R4/src/main/java/com/lishid/openinv/internal/v1_20_R4/InternalAccessor.java b/internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/InternalAccessor.java similarity index 62% rename from internal/v1_20_R4/src/main/java/com/lishid/openinv/internal/v1_20_R4/InternalAccessor.java rename to internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/InternalAccessor.java index ff599644..3b873935 100644 --- a/internal/v1_20_R4/src/main/java/com/lishid/openinv/internal/v1_20_R4/InternalAccessor.java +++ b/internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/InternalAccessor.java @@ -1,28 +1,36 @@ -package com.lishid.openinv.internal.v1_20_R4; +package com.lishid.openinv.internal.v1_21_R2; import com.lishid.openinv.internal.Accessor; import com.lishid.openinv.internal.IAnySilentContainer; import com.lishid.openinv.internal.ISpecialEnderChest; import com.lishid.openinv.internal.ISpecialInventory; import com.lishid.openinv.internal.ISpecialPlayerInventory; +import com.lishid.openinv.internal.v1_21_R2.container.AnySilentContainer; +import com.lishid.openinv.internal.v1_21_R2.container.OpenEnderChest; +import com.lishid.openinv.internal.v1_21_R2.container.OpenInventory; +import com.lishid.openinv.internal.v1_21_R2.container.Placeholders; +import com.lishid.openinv.internal.v1_21_R2.player.PlayerManager; import com.lishid.openinv.util.lang.LanguageManager; import net.minecraft.world.Container; import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.craftbukkit.v1_20_R4.inventory.CraftInventory; +import org.bukkit.craftbukkit.v1_21_R2.inventory.CraftInventory; import org.bukkit.entity.Player; import org.bukkit.inventory.Inventory; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.logging.Level; import java.util.logging.Logger; public class InternalAccessor implements Accessor { + private final @NotNull Logger logger; private final @NotNull PlayerManager manager; private final @NotNull AnySilentContainer anySilentContainer; public InternalAccessor(@NotNull Logger logger, @NotNull LanguageManager lang) { - manager = new PlayerManager(logger, lang); + this.logger = logger; + manager = new PlayerManager(logger); anySilentContainer = new AnySilentContainer(logger, lang); } @@ -38,12 +46,12 @@ public InternalAccessor(@NotNull Logger logger, @NotNull LanguageManager lang) { @Override public @NotNull ISpecialPlayerInventory createPlayerInventory(@NotNull Player player) { - return new SpecialPlayerInventory(player, player.isOnline()); + return new OpenInventory(player); } @Override public @NotNull ISpecialEnderChest createEnderChest(@NotNull Player player) { - return new SpecialEnderChest(player, player.isOnline()); + return new OpenEnderChest(player); } @Override @@ -59,6 +67,15 @@ public InternalAccessor(@NotNull Logger logger, @NotNull LanguageManager lang) { } @Override - public void reload(@NotNull ConfigurationSection config) {} + public void reload(@NotNull ConfigurationSection config) { + ConfigurationSection placeholders = config.getConfigurationSection("placeholders"); + if (placeholders != null) { + try { + Placeholders.load(placeholders); + } catch (Exception e) { + logger.log(Level.WARNING, "Caught exception loading placeholder overrides!", e); + } + } + } } diff --git a/internal/v1_20_R4/src/main/java/com/lishid/openinv/internal/v1_20_R4/AnySilentContainer.java b/internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/container/AnySilentContainer.java similarity index 96% rename from internal/v1_20_R4/src/main/java/com/lishid/openinv/internal/v1_20_R4/AnySilentContainer.java rename to internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/container/AnySilentContainer.java index 9ea9065f..39b52b67 100644 --- a/internal/v1_20_R4/src/main/java/com/lishid/openinv/internal/v1_20_R4/AnySilentContainer.java +++ b/internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/container/AnySilentContainer.java @@ -14,9 +14,11 @@ * along with this program. If not, see . */ -package com.lishid.openinv.internal.v1_20_R4; +package com.lishid.openinv.internal.v1_21_R2.container; import com.lishid.openinv.internal.AnySilentContainerBase; +import com.lishid.openinv.internal.v1_21_R2.container.menu.OpenChestMenu; +import com.lishid.openinv.internal.v1_21_R2.player.PlayerManager; import com.lishid.openinv.util.ReflectionHelper; import com.lishid.openinv.util.lang.LanguageManager; import net.minecraft.core.BlockPos; @@ -102,7 +104,7 @@ public boolean activateContainer( PlayerEnderChestContainer enderChest = player.getEnderChestInventory(); enderChest.setActiveChest(enderChestTile); player.openMenu(new SimpleMenuProvider((containerCounter, playerInventory, ignored) -> { - MenuType containers = PlayerManager.getContainers(enderChest.getContainerSize()); + MenuType containers = OpenChestMenu.getChestMenuType(enderChest.getContainerSize()); int rows = enderChest.getContainerSize() / 9; return new ChestMenu(containers, containerCounter, playerInventory, enderChest, rows); }, Component.translatable("container.enderchest"))); diff --git a/internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/container/OpenEnderChest.java b/internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/container/OpenEnderChest.java new file mode 100644 index 00000000..2f0b3d97 --- /dev/null +++ b/internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/container/OpenEnderChest.java @@ -0,0 +1,196 @@ +package com.lishid.openinv.internal.v1_21_R2.container; + +import com.lishid.openinv.internal.ISpecialEnderChest; +import com.lishid.openinv.internal.InternalOwned; +import com.lishid.openinv.internal.v1_21_R2.container.menu.OpenEnderChestMenu; +import com.lishid.openinv.internal.v1_21_R2.player.PlayerManager; +import net.minecraft.core.NonNullList; +import net.minecraft.network.chat.Component; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.Container; +import net.minecraft.world.ContainerHelper; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.entity.player.StackedItemContents; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.StackedContentsCompatible; +import net.minecraft.world.item.ItemStack; +import org.bukkit.Location; +import org.bukkit.craftbukkit.v1_21_R2.entity.CraftHumanEntity; +import org.bukkit.craftbukkit.v1_21_R2.inventory.CraftInventory; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; + +public class OpenEnderChest implements Container, StackedContentsCompatible, InternalOwned, + ISpecialEnderChest { + + private CraftInventory inventory; + private @NotNull ServerPlayer owner; + private NonNullList items; + private int maxStack = 64; + private final List transaction = new ArrayList<>(); + + public OpenEnderChest(@NotNull org.bukkit.entity.Player player) { + this.owner = PlayerManager.getHandle(player); + this.items = owner.getEnderChestInventory().items; + } + + @Override + public @NotNull ServerPlayer getOwnerHandle() { + return owner; + } + + @Override + public @NotNull org.bukkit.inventory.Inventory getBukkitInventory() { + if (inventory == null) { + inventory = new CraftInventory(this) { + @Override + public @NotNull InventoryType getType() { + return InventoryType.ENDER_CHEST; + } + }; + } + return inventory; + } + + @Override + public void setPlayerOnline(@NotNull org.bukkit.entity.Player player) { + owner = PlayerManager.getHandle(player); + NonNullList activeItems = owner.getEnderChestInventory().items; + + // Guard against size changing. Theoretically on Purpur all row variations still have 6 rows internally. + int max = Math.min(items.size(), activeItems.size()); + for (int index = 0; index < max; ++index) { + activeItems.set(index, items.get(index)); + } + + items = activeItems; + } + + @Override + public void setPlayerOffline() {} + + @Override + public @NotNull org.bukkit.entity.Player getPlayer() { + return owner.getBukkitEntity(); + } + + @Override + public int getContainerSize() { + return items.size(); + } + + @Override + public boolean isEmpty() { + return items.stream().allMatch(ItemStack::isEmpty); + } + + @Override + public ItemStack getItem(int index) { + return index >= 0 && index < items.size() ? items.get(index) : ItemStack.EMPTY; + } + + @Override + public ItemStack removeItem(int index, int amount) { + ItemStack itemstack = ContainerHelper.removeItem(items, index, amount); + + if (!itemstack.isEmpty()) { + setChanged(); + } + + return itemstack; + } + + @Override + public ItemStack removeItemNoUpdate(int index) { + return index >= 0 && index < items.size() ? items.set(index, ItemStack.EMPTY) : ItemStack.EMPTY; + } + + @Override + public void setItem(int index, ItemStack itemStack) { + if (index >= 0 && index < items.size()) { + items.set(index, itemStack); + } + } + + @Override + public int getMaxStackSize() { + return maxStack; + } + + @Override + public void setChanged() { + this.owner.getEnderChestInventory().setChanged(); + } + + @Override + public boolean stillValid(Player player) { + return true; + } + + @Override + public List getContents() { + return items; + } + + @Override + public void onOpen(CraftHumanEntity craftHumanEntity) { + transaction.add(craftHumanEntity); + } + + @Override + public void onClose(CraftHumanEntity craftHumanEntity) { + transaction.remove(craftHumanEntity); + } + + @Override + public List getViewers() { + return transaction; + } + + @Override + public org.bukkit.entity.Player getOwner() { + return getPlayer(); + } + + @Override + public void setMaxStackSize(int size) { + maxStack = size; + } + + @Override + public @Nullable Location getLocation() { + return null; + } + + @Override + public void clearContent() { + items.clear(); + setChanged(); + } + + @Override + public void fillStackedContents(StackedItemContents stackedContents) { + for (ItemStack itemstack : items) { + stackedContents.accountStack(itemstack); + } + } + + public Component getTitle() { + return Component.translatableWithFallback("openinv.container.enderchest.prefix", "", owner.getName()) + .append(Component.translatable("container.enderchest")) + .append(Component.translatableWithFallback("openinv.container.enderchest.suffix", " - %s", owner.getName())); + } + + public @Nullable AbstractContainerMenu createMenu(Player player, int i, boolean viewOnly) { + if (player instanceof ServerPlayer serverPlayer) { + return new OpenEnderChestMenu(this, serverPlayer, i, viewOnly); + } + return null; + } + +} diff --git a/internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/container/OpenInventory.java b/internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/container/OpenInventory.java new file mode 100644 index 00000000..ef3113ff --- /dev/null +++ b/internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/container/OpenInventory.java @@ -0,0 +1,364 @@ +package com.lishid.openinv.internal.v1_21_R2.container; + +import com.lishid.openinv.internal.ISpecialPlayerInventory; +import com.lishid.openinv.internal.InternalOwned; +import com.lishid.openinv.internal.v1_21_R2.container.bukkit.OpenPlayerInventory; +import com.lishid.openinv.internal.v1_21_R2.container.menu.OpenInventoryMenu; +import com.lishid.openinv.internal.v1_21_R2.container.slot.Content; +import com.lishid.openinv.internal.v1_21_R2.container.slot.ContentCrafting; +import com.lishid.openinv.internal.v1_21_R2.container.slot.ContentCraftingResult; +import com.lishid.openinv.internal.v1_21_R2.container.slot.ContentCursor; +import com.lishid.openinv.internal.v1_21_R2.container.slot.ContentDrop; +import com.lishid.openinv.internal.v1_21_R2.container.slot.ContentEquipment; +import com.lishid.openinv.internal.v1_21_R2.container.slot.ContentList; +import com.lishid.openinv.internal.v1_21_R2.container.slot.ContentOffHand; +import com.lishid.openinv.internal.v1_21_R2.container.slot.ContentViewOnly; +import com.lishid.openinv.internal.v1_21_R2.container.slot.SlotViewOnly; +import com.lishid.openinv.internal.v1_21_R2.player.PlayerManager; +import net.minecraft.ChatFormatting; +import net.minecraft.core.NonNullList; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.Container; +import net.minecraft.world.entity.EquipmentSlot; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.ItemStack; +import org.bukkit.Location; +import org.bukkit.craftbukkit.v1_21_R2.entity.CraftHumanEntity; +import org.bukkit.craftbukkit.v1_21_R2.inventory.CraftInventory; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; + +public class OpenInventory implements Container, InternalOwned, ISpecialPlayerInventory { + + private final List slots; + private final int size; + private ServerPlayer owner; + private int maxStackSize = 99; + private CraftInventory bukkitEntity; + public List transaction = new ArrayList<>(); + + public OpenInventory(@NotNull org.bukkit.entity.Player bukkitPlayer) { + owner = PlayerManager.getHandle(bukkitPlayer); + + // Get total size, rounding up to nearest 9 for client compatibility. + int rawSize = owner.getInventory().getContainerSize() + owner.inventoryMenu.getCraftSlots().getContainerSize() + 1; + size = ((int) Math.ceil(rawSize / 9.0)) * 9; + + slots = NonNullList.withSize(size, new ContentViewOnly(owner)); + setupSlots(); + } + + private void setupSlots() { + // Top of inventory: Regular contents. + int nextIndex = addMainInventory(); + + // If inventory is expected size, we can arrange slots to be pretty. + Inventory ownerInv = owner.getInventory(); + if (ownerInv.items.size() == 36 + && ownerInv.armor.size() == 4 + && ownerInv.offhand.size() == 1 + && owner.inventoryMenu.getCraftSlots().getContainerSize() == 4) { + // Armor slots: Bottom left. + addArmor(36); + // Off-hand: Below chestplate. + addOffHand(46); + // Drop slot: Bottom right. + slots.set(53, new ContentDrop(owner)); + // Cursor slot: Above drop. + slots.set(44, new ContentCursor(owner)); + + // Crafting is displayed in the bottom right corner. + // As we're using the pretty view, this is a 3x2. + addCrafting(41, true); + return; + } + + // Otherwise we'll just add elements linearly. + nextIndex = addArmor(nextIndex); + nextIndex = addOffHand(nextIndex); + nextIndex = addCrafting(nextIndex, false); + slots.set(nextIndex, new ContentCursor(owner)); + // Drop slot last. + slots.set(slots.size() - 1, new ContentDrop(owner)); + } + + private int addMainInventory() { + int listSize = owner.getInventory().items.size(); + // Hotbar slots are 0-8. We want those to appear on the bottom of the inventory like a normal player inventory, + // so everything else needs to move up a row. + int hotbarDiff = listSize - 9; + for (int localIndex = 0; localIndex < listSize; ++localIndex) { + InventoryType.SlotType type; + int invIndex; + if (localIndex < hotbarDiff) { + invIndex = localIndex + 9; + type = InventoryType.SlotType.CONTAINER; + } else { + type = InventoryType.SlotType.QUICKBAR; + invIndex = localIndex - hotbarDiff; + } + + slots.set(localIndex, new ContentList(owner, invIndex, type) { + @Override + public void setHolder(@NotNull ServerPlayer holder) { + items = holder.getInventory().items; + } + }); + } + return listSize; + } + + private int addArmor(int startIndex) { + int listSize = owner.getInventory().armor.size(); + + for (int i = 0; i < listSize; ++i) { + // Armor slots go bottom to top; boots are slot 0, helmet is slot 3. + // Since we have to display horizontally due to space restrictions, + // making the left side the "top" is more user-friendly. + int armorIndex; + EquipmentSlot slot; + switch (i) { + case 3 -> { + armorIndex = 0; + slot = EquipmentSlot.FEET; + } + case 2 -> { + armorIndex = 1; + slot = EquipmentSlot.LEGS; + } + case 1 -> { + armorIndex = 2; + slot = EquipmentSlot.CHEST; + } + case 0 -> { + armorIndex = 3; + slot = EquipmentSlot.HEAD; + } + default -> { + // In the event that new armor slots are added, they can be placed at the end. + armorIndex = i; + slot = EquipmentSlot.MAINHAND; + } + } + + slots.set(startIndex + i, new ContentEquipment(owner, armorIndex, slot)); + } + + return startIndex + listSize; + } + + private int addOffHand(int startIndex) { + int listSize = owner.getInventory().offhand.size(); + for (int localIndex = 0; localIndex < listSize; ++localIndex) { + slots.set(startIndex + localIndex, new ContentOffHand(owner, localIndex)); + } + return startIndex + listSize; + } + + private int addCrafting(int startIndex, boolean pretty) { + int listSize = owner.inventoryMenu.getCraftSlots().getContents().size(); + pretty &= listSize == 4; + + for (int localIndex = 0; localIndex < listSize; ++localIndex) { + // Pretty display is a 2x2 rather than linear. + // If index is in top row, grid is not 2x2, or pretty is disabled, just use current index. + // Otherwise, subtract 2 and add 9 to start in the same position on the next row. + int modIndex = startIndex + (localIndex < 2 || !pretty ? localIndex : localIndex + 7); + + slots.set(modIndex, new ContentCrafting(owner, localIndex)); + } + + if (pretty) { + slots.set(startIndex + 2, new ContentViewOnly(owner) { + @Override + public Slot asSlot(Container container, int slot, int x, int y) { + return new SlotViewOnly(container, slot, x, y) { + @Override + public ItemStack getOrDefault() { + return Placeholders.craftingOutput; + } + }; + } + }); + slots.set(startIndex + 11, new ContentCraftingResult(owner)); + } + + return startIndex + listSize; + } + + public Slot getMenuSlot(int index, int x, int y) { + return slots.get(index).asSlot(this, index, x, y); + } + + public InventoryType.SlotType getSlotType(int index) { + return slots.get(index).getSlotType(); + } + + public @NotNull Component getTitle(@Nullable ServerPlayer viewer) { + MutableComponent component = Component.empty(); + // Prefix for use with custom bitmap image fonts. + if (owner.equals(viewer)) { + component.append( + Component.translatableWithFallback("openinv.container.inventory.self", "") + .withStyle(style -> style + .withFont(ResourceLocation.parse("openinv:font/inventory")) + .withColor(ChatFormatting.WHITE))); + } else { + component.append( + Component.translatableWithFallback("openinv.container.inventory.other", "") + .withStyle(style -> style + .withFont(ResourceLocation.parse("openinv:font/inventory")) + .withColor(ChatFormatting.WHITE))); + } + // Normal title: "Inventory - OwnerName" + component.append(Component.translatableWithFallback("openinv.container.inventory.prefix", "", owner.getName())) + .append(Component.translatable("container.inventory")) + .append(Component.translatableWithFallback("openinv.container.inventory.suffix", " - %s", owner.getName())); + return component; + } + + @Override + public ServerPlayer getOwnerHandle() { + return owner; + } + + @Override + public @NotNull org.bukkit.inventory.Inventory getBukkitInventory() { + if (bukkitEntity == null) { + bukkitEntity = new OpenPlayerInventory(this); + } + return bukkitEntity; + } + + @Override + public void setPlayerOnline(@NotNull org.bukkit.entity.Player player) { + ServerPlayer newOwner = PlayerManager.getHandle(player); + // Only transfer regular inventory - crafting and cursor slots are transient. + newOwner.getInventory().replaceWith(owner.getInventory()); + owner = newOwner; + // Update slots to point to new inventory. + slots.forEach(slot -> slot.setHolder(newOwner)); + } + + @Override + public void setPlayerOffline() {} + + @Override + public boolean isInUse() { + return !transaction.isEmpty(); + } + + @Override + public @NotNull org.bukkit.entity.Player getPlayer() { + return getOwner(); + } + + @Override + public int getContainerSize() { + return size; + } + + @Override + public boolean isEmpty() { + return slots.stream().map(Content::get).allMatch(ItemStack::isEmpty); + } + + @Override + public ItemStack getItem(int index) { + return slots.get(index).get(); + } + + @Override + public ItemStack removeItem(int index, int amount) { + return slots.get(index).removePartial(amount); + } + + @Override + public ItemStack removeItemNoUpdate(int index) { + return slots.get(index).remove(); + } + + @Override + public void setItem(int index, ItemStack itemStack) { + slots.get(index).set(itemStack); + } + + @Override + public int getMaxStackSize() { + return maxStackSize; + } + + @Override + public void setMaxStackSize(int maxStackSize) { + this.maxStackSize = maxStackSize; + } + + @Override + public void setChanged() {} + + @Override + public boolean stillValid(Player player) { + return true; + } + + @Override + public List getContents() { + NonNullList contents = NonNullList.withSize(getContainerSize(), ItemStack.EMPTY); + for (int i = 0; i < getContainerSize(); ++i) { + contents.set(i, getItem(i)); + } + return contents; + } + + @Override + public void onOpen(CraftHumanEntity viewer) { + transaction.add(viewer); + } + + @Override + public void onClose(CraftHumanEntity viewer) { + transaction.remove(viewer); + } + + @Override + public List getViewers() { + return transaction; + } + + @Override + public org.bukkit.entity.Player getOwner() { + return owner.getBukkitEntity(); + } + + @Override + public Location getLocation() { + return owner.getBukkitEntity().getLocation(); + } + + @Override + public void clearContent() { + owner.getInventory().clearContent(); + owner.inventoryMenu.getCraftSlots().clearContent(); + owner.inventoryMenu.slotsChanged(owner.inventoryMenu.getCraftSlots()); + owner.containerMenu.setCarried(ItemStack.EMPTY); + } + + public @Nullable AbstractContainerMenu createMenu(Player player, int i, boolean viewOnly) { + if (player instanceof ServerPlayer serverPlayer) { + return new OpenInventoryMenu(this, serverPlayer, i, viewOnly); + } + return null; + } + +} diff --git a/internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/container/Placeholders.java b/internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/container/Placeholders.java new file mode 100644 index 00000000..ec9422f5 --- /dev/null +++ b/internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/container/Placeholders.java @@ -0,0 +1,183 @@ +package com.lishid.openinv.internal.v1_21_R2.container; + +import net.minecraft.core.Registry; +import net.minecraft.core.RegistryAccess; +import net.minecraft.core.component.DataComponents; +import net.minecraft.core.registries.Registries; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.TagParser; +import net.minecraft.network.chat.Component; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.util.Unit; +import net.minecraft.world.item.DyeColor; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.item.component.CustomModelData; +import net.minecraft.world.item.component.DyedItemColor; +import net.minecraft.world.level.GameType; +import net.minecraft.world.level.ItemLike; +import net.minecraft.world.level.block.entity.BannerPattern; +import net.minecraft.world.level.block.entity.BannerPatternLayers; +import net.minecraft.world.level.block.entity.BannerPatterns; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.craftbukkit.v1_21_R2.CraftRegistry; +import org.jetbrains.annotations.NotNull; + +import java.util.EnumMap; +import java.util.List; +import java.util.Optional; + +public final class Placeholders { + + private static final CustomModelData DEFAULT_CUSTOM_MODEL_DATA = new CustomModelData(9999); + public static final @NotNull EnumMap BLOCKED_GAME_TYPE = new EnumMap<>(GameType.class); + public static @NotNull ItemStack craftingOutput = defaultCraftingOutput(); + public static @NotNull ItemStack cursor = defaultCursor(); + public static @NotNull ItemStack drop = defaultDrop(); + public static @NotNull ItemStack emptyHelmet = getEmptyArmor(Items.LEATHER_HELMET); + public static @NotNull ItemStack emptyChestplate = getEmptyArmor(Items.LEATHER_CHESTPLATE); + public static @NotNull ItemStack emptyLeggings = getEmptyArmor(Items.LEATHER_LEGGINGS); + public static @NotNull ItemStack emptyBoots = getEmptyArmor(Items.LEATHER_BOOTS); + public static @NotNull ItemStack emptyOffHand = getEmptyShield(); + public static @NotNull ItemStack notSlot = defaultNotSlot(); + public static @NotNull ItemStack blockedOffline = defaultBlockedOffline(); + + static { + for (GameType type : GameType.values()) { + // Barrier: "Not available - Creative" etc. + ItemStack typeItem = new ItemStack(Items.BARRIER); + typeItem.set( + DataComponents.ITEM_NAME, + Component.translatable("options.narrator.notavailable").append(" - ").append(type.getShortDisplayName())); + BLOCKED_GAME_TYPE.put(type, typeItem); + } + } + + public static void load(@NotNull ConfigurationSection section) throws Exception { + craftingOutput = parse(section, "crafting-output", craftingOutput); + cursor = parse(section, "cursor", cursor); + drop = parse(section, "drop", drop); + emptyHelmet = parse(section, "empty-helmet", emptyHelmet); + emptyChestplate = parse(section, "empty-chestplate", emptyChestplate); + emptyLeggings = parse(section, "empty-leggings", emptyLeggings); + emptyBoots = parse(section, "empty-boots", emptyBoots); + emptyOffHand = parse(section, "empty-off-hand", emptyOffHand); + notSlot = parse(section, "not-a-slot", notSlot); + blockedOffline = parse(section, "blocked.offline", blockedOffline); + BLOCKED_GAME_TYPE.put(GameType.CREATIVE, parse(section, "blocked.creative", BLOCKED_GAME_TYPE.get(GameType.CREATIVE))); + BLOCKED_GAME_TYPE.put(GameType.SPECTATOR, parse(section, "blocked.spectator", BLOCKED_GAME_TYPE.get(GameType.SPECTATOR))); + } + + private static @NotNull ItemStack parse( + @NotNull ConfigurationSection section, + @NotNull String path, + @NotNull ItemStack defaultStack) throws Exception { + String itemText = section.getString(path); + + if (itemText == null) { + return defaultStack; + } + + CompoundTag compoundTag = TagParser.parseTag(itemText); + Optional parsed = ItemStack.parse(CraftRegistry.getMinecraftRegistry(), compoundTag); + return parsed.filter(itemStack -> !itemStack.isEmpty()).orElse(defaultStack); + } + + public static ItemStack survivalOnly(@NotNull ServerPlayer serverPlayer) { + if (serverPlayer.connection == null || serverPlayer.connection.isDisconnected()) { + return blockedOffline; + } + + return BLOCKED_GAME_TYPE.getOrDefault(serverPlayer.gameMode.getGameModeForPlayer(), ItemStack.EMPTY); + } + + private static ItemStack defaultCraftingOutput() { + // Crafting table: "Crafting" + ItemStack itemStack = new ItemStack(Items.CRAFTING_TABLE); + itemStack.set(DataComponents.ITEM_NAME, Component.translatable("container.crafting")); + itemStack.set(DataComponents.CUSTOM_MODEL_DATA, DEFAULT_CUSTOM_MODEL_DATA); + return itemStack; + } + + private static ItemStack defaultCursor() { + // Cursor-like banner with no tooltip + ItemStack itemStack = new ItemStack(Items.WHITE_BANNER); + RegistryAccess minecraftRegistry = CraftRegistry.getMinecraftRegistry(); + Registry bannerPatterns = minecraftRegistry.lookupOrThrow(Registries.BANNER_PATTERN); + BannerPattern halfDiagBottomRight = bannerPatterns.getOrThrow(BannerPatterns.DIAGONAL_RIGHT).value(); + BannerPattern downRight = bannerPatterns.getOrThrow(BannerPatterns.STRIPE_DOWNRIGHT).value(); + BannerPattern border = bannerPatterns.getOrThrow(BannerPatterns.BORDER).value(); + itemStack.set(DataComponents.BANNER_PATTERNS, + new BannerPatternLayers(List.of( + new BannerPatternLayers.Layer(bannerPatterns.wrapAsHolder(halfDiagBottomRight), DyeColor.GRAY), + new BannerPatternLayers.Layer(bannerPatterns.wrapAsHolder(downRight), DyeColor.WHITE), + new BannerPatternLayers.Layer(bannerPatterns.wrapAsHolder(border), DyeColor.GRAY)))); + itemStack.set(DataComponents.CUSTOM_MODEL_DATA, DEFAULT_CUSTOM_MODEL_DATA); + itemStack.set(DataComponents.HIDE_TOOLTIP, Unit.INSTANCE); + return itemStack; + } + + private static ItemStack defaultDrop() { + // Dropper: "Drop Selected Item" + ItemStack itemStack = new ItemStack(Items.DROPPER); + // Note: translatable component, not keybind component! We want the text identifying the keybind, not the key. + itemStack.set(DataComponents.ITEM_NAME, Component.translatable("key.drop")); + itemStack.set(DataComponents.CUSTOM_MODEL_DATA, DEFAULT_CUSTOM_MODEL_DATA); + return itemStack; + } + + private static ItemStack getEmptyArmor(ItemLike item) { + // Inventory-background-grey-ish leather armor with no tooltip + ItemStack itemStack = new ItemStack(item); + DyedItemColor color = new DyedItemColor(0xC8C8C8, false); + itemStack.set(DataComponents.DYED_COLOR, color); + itemStack.set(DataComponents.HIDE_TOOLTIP, Unit.INSTANCE); + itemStack.set(DataComponents.CUSTOM_MODEL_DATA, DEFAULT_CUSTOM_MODEL_DATA); + return itemStack; + } + + private static ItemStack getEmptyShield() { + ItemStack itemStack = new ItemStack(Items.SHIELD); + itemStack.set(DataComponents.BASE_COLOR, DyeColor.MAGENTA); + RegistryAccess minecraftRegistry = CraftRegistry.getMinecraftRegistry(); + Registry bannerPatterns = minecraftRegistry.lookupOrThrow(Registries.BANNER_PATTERN); + BannerPattern halfLeft = bannerPatterns.getOrThrow(BannerPatterns.HALF_VERTICAL).value(); + BannerPattern topLeft = bannerPatterns.getOrThrow(BannerPatterns.SQUARE_TOP_LEFT).value(); + BannerPattern topRight = bannerPatterns.getOrThrow(BannerPatterns.SQUARE_TOP_RIGHT).value(); + BannerPattern bottomLeft = bannerPatterns.getOrThrow(BannerPatterns.SQUARE_BOTTOM_LEFT).value(); + BannerPattern bottomRight = bannerPatterns.getOrThrow(BannerPatterns.SQUARE_BOTTOM_RIGHT).value(); + itemStack.set(DataComponents.BANNER_PATTERNS, + new BannerPatternLayers(List.of( + new BannerPatternLayers.Layer(bannerPatterns.wrapAsHolder(halfLeft), DyeColor.BLACK), + new BannerPatternLayers.Layer(bannerPatterns.wrapAsHolder(topLeft), DyeColor.MAGENTA), + new BannerPatternLayers.Layer(bannerPatterns.wrapAsHolder(bottomLeft), DyeColor.MAGENTA), + new BannerPatternLayers.Layer(bannerPatterns.wrapAsHolder(topRight), DyeColor.BLACK), + new BannerPatternLayers.Layer(bannerPatterns.wrapAsHolder(bottomRight), DyeColor.BLACK)))); + itemStack.set(DataComponents.HIDE_TOOLTIP, Unit.INSTANCE); + itemStack.set(DataComponents.CUSTOM_MODEL_DATA, DEFAULT_CUSTOM_MODEL_DATA); + return itemStack; + } + + private static ItemStack defaultNotSlot() { + // White pane with no tooltip + ItemStack itemStack = new ItemStack(Items.WHITE_STAINED_GLASS_PANE); + itemStack.set(DataComponents.HIDE_TOOLTIP, Unit.INSTANCE); + itemStack.set(DataComponents.CUSTOM_MODEL_DATA, DEFAULT_CUSTOM_MODEL_DATA); + return itemStack; + } + + private static ItemStack defaultBlockedOffline() { + // Barrier: "Not available - Offline" + ItemStack itemStack = new ItemStack(Items.BARRIER); + itemStack.set(DataComponents.ITEM_NAME, + Component.translatable("options.narrator.notavailable") + .append(Component.literal(" - ")) + .append(Component.translatable("gui.socialInteractions.status_offline"))); + return itemStack; + } + + private Placeholders() { + throw new IllegalStateException("Cannot create instance of utility class."); + } + +} diff --git a/internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/container/bukkit/OpenDummyInventory.java b/internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/container/bukkit/OpenDummyInventory.java new file mode 100644 index 00000000..61b89810 --- /dev/null +++ b/internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/container/bukkit/OpenDummyInventory.java @@ -0,0 +1,163 @@ +package com.lishid.openinv.internal.v1_21_R2.container.bukkit; + +import com.lishid.openinv.internal.ViewOnly; +import net.minecraft.world.Container; +import org.bukkit.Material; +import org.bukkit.craftbukkit.v1_21_R2.inventory.CraftInventory; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collections; +import java.util.HashMap; +import java.util.ListIterator; + +/** + * A locked down "empty" inventory that rejects plugin interaction. + */ +public class OpenDummyInventory extends CraftInventory implements ViewOnly { + + public OpenDummyInventory(Container inventory) { + super(inventory); + } + + @Override + public @Nullable ItemStack getItem(int index) { + return null; + } + + @Override + public void setItem(int index, @Nullable ItemStack item) { + + } + + @SuppressWarnings("NonApiType") + @Override + public @NotNull HashMap addItem(@NotNull ItemStack... items) throws IllegalArgumentException { + return arrayToHashMap(items); + } + + @SuppressWarnings("NonApiType") + @Override + public @NotNull HashMap removeItem(@NotNull ItemStack... items) throws IllegalArgumentException { + return arrayToHashMap(items); + } + + @SuppressWarnings("NonApiType") + private static @NotNull HashMap arrayToHashMap(@NotNull ItemStack[] items) { + HashMap ignored = new HashMap<>(); + for (int index = 0; index < items.length; ++index) { + ignored.put(index, items[index]); + } + return ignored; + } + + @Override + public ItemStack[] getContents() { + return new ItemStack[getSize()]; + } + + @Override + public void setContents(@NotNull ItemStack[] items) throws IllegalArgumentException { + + } + + @Override + public @NotNull ItemStack[] getStorageContents() { + return new ItemStack[getSize()]; + } + + @Override + public void setStorageContents(@NotNull ItemStack[] items) throws IllegalArgumentException { + + } + + @Override + public boolean contains(@NotNull Material material) throws IllegalArgumentException { + return false; + } + + @Override + public boolean contains(@Nullable ItemStack item) { + return false; + } + + @Override + public boolean contains(@NotNull Material material, int amount) throws IllegalArgumentException { + return false; + } + + @Override + public boolean contains(@Nullable ItemStack item, int amount) { + return false; + } + + @Override + public boolean containsAtLeast(@Nullable ItemStack item, int amount) { + return false; + } + + @SuppressWarnings("NonApiType") + @Override + public @NotNull HashMap all( + @NotNull Material material) throws IllegalArgumentException { + return new HashMap<>(); + } + + @SuppressWarnings("NonApiType") + @Override + public @NotNull HashMap all(@Nullable ItemStack item) { + return new HashMap<>(); + } + + @Override + public int first(@NotNull Material material) throws IllegalArgumentException { + return -1; + } + + @Override + public int first(@NotNull ItemStack item) { + return -1; + } + + @Override + public int firstEmpty() { + return -1; + } + + @Override + public boolean isEmpty() { + return true; + } + + @Override + public void remove(@NotNull Material material) throws IllegalArgumentException { + + } + + @Override + public void remove(@NotNull ItemStack item) { + + } + + @Override + public void clear(int index) { + + } + + @Override + public void clear() { + + } + + @Override + public @NotNull ListIterator iterator() { + return Collections.emptyListIterator(); + } + + @Override + public @NotNull ListIterator iterator(int index) { + return Collections.emptyListIterator(); + } + +} diff --git a/internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/container/bukkit/OpenPlayerInventory.java b/internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/container/bukkit/OpenPlayerInventory.java new file mode 100644 index 00000000..01be5ba1 --- /dev/null +++ b/internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/container/bukkit/OpenPlayerInventory.java @@ -0,0 +1,221 @@ +package com.lishid.openinv.internal.v1_21_R2.container.bukkit; + +import com.google.common.base.Preconditions; +import com.lishid.openinv.internal.v1_21_R2.container.OpenInventory; +import net.minecraft.core.NonNullList; +import net.minecraft.world.entity.EquipmentSlot; +import net.minecraft.world.entity.player.Inventory; +import org.bukkit.craftbukkit.v1_21_R2.inventory.CraftInventory; +import org.bukkit.craftbukkit.v1_21_R2.inventory.CraftItemStack; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.PlayerInventory; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class OpenPlayerInventory extends CraftInventory implements PlayerInventory { + + public OpenPlayerInventory(@NotNull OpenInventory inventory) { + super(inventory); + } + + @Override + public @NotNull OpenInventory getInventory() { + return (OpenInventory) super.getInventory(); + } + + @Override + public ItemStack[] getContents() { + return asCraftMirror(getInventory().getOwnerHandle().getInventory().getContents()); + } + + @Override + public void setContents(ItemStack[] items) { + Inventory internal = getInventory().getOwnerHandle().getInventory(); + int size = internal.getContainerSize(); + Preconditions.checkArgument(items.length <= size, "items.length must be <= %s", size); + + for (int index = 0; index < size; ++index) { + if (index < items.length) { + internal.setItem(index, CraftItemStack.asNMSCopy(items[index])); + } else { + internal.setItem(index, net.minecraft.world.item.ItemStack.EMPTY); + } + } + } + + @Override + public ItemStack[] getStorageContents() { + return asCraftMirror(getInventory().getOwnerHandle().getInventory().items); + } + + @Override + public void setStorageContents(ItemStack[] items) throws IllegalArgumentException { + NonNullList list = getInventory().getOwnerHandle().getInventory().items; + int size = list.size(); + Preconditions.checkArgument(items.length <= size, "items.length must be <= %s", size); + for (int index = 0; index < items.length; ++index) { + list.set(index, CraftItemStack.asNMSCopy(items[index])); + } + } + + @Override + public @NotNull InventoryType getType() { + return InventoryType.PLAYER; + } + + @Override + public @NotNull Player getHolder() { + return getInventory().getOwner(); + } + + @Override + public @NotNull ItemStack[] getArmorContents() { + return asCraftMirror(getInventory().getOwnerHandle().getInventory().armor); + } + + @Override + public void setArmorContents(@Nullable ItemStack[] items) { + NonNullList list = getInventory().getOwnerHandle().getInventory().armor; + int size = list.size(); + Preconditions.checkArgument(items.length <= size, "items.length must be <= %s", size); + for (int index = 0; index < items.length; ++index) { + list.set(index, CraftItemStack.asNMSCopy(items[index])); + } + } + + @Override + public @NotNull ItemStack[] getExtraContents() { + return asCraftMirror(getInventory().getOwnerHandle().getInventory().offhand); + } + + @Override + public void setExtraContents(@Nullable ItemStack[] items) { + NonNullList list = getInventory().getOwnerHandle().getInventory().offhand; + int size = list.size(); + Preconditions.checkArgument(items.length <= size, "items.length must be <= %s", size); + for (int index = 0; index < items.length; ++index) { + list.set(index, CraftItemStack.asNMSCopy(items[index])); + } + } + + @Override + public @Nullable ItemStack getHelmet() { + return CraftItemStack.asCraftMirror(getInventory().getOwnerHandle().getInventory() + .getArmor(EquipmentSlot.HEAD.getIndex())); + } + + @Override + public void setHelmet(@Nullable ItemStack helmet) { + getInventory().getOwnerHandle().getInventory().armor + .set(EquipmentSlot.HEAD.getIndex(), CraftItemStack.asNMSCopy(helmet)); + } + + @Override + public @Nullable ItemStack getChestplate() { + return CraftItemStack.asCraftMirror(getInventory().getOwnerHandle().getInventory() + .getArmor(EquipmentSlot.HEAD.getIndex())); + } + + @Override + public void setChestplate(@Nullable ItemStack chestplate) { + getInventory().getOwnerHandle().getInventory().armor + .set(EquipmentSlot.CHEST.getIndex(), CraftItemStack.asNMSCopy(chestplate)); + } + + @Override + public @Nullable ItemStack getLeggings() { + return CraftItemStack.asCraftMirror(getInventory().getOwnerHandle().getInventory() + .getArmor(EquipmentSlot.LEGS.getIndex())); + } + + @Override + public void setLeggings(@Nullable ItemStack leggings) { + getInventory().getOwnerHandle().getInventory().armor + .set(EquipmentSlot.LEGS.getIndex(), CraftItemStack.asNMSCopy(leggings)); + } + + @Override + public @Nullable ItemStack getBoots() { + return CraftItemStack.asCraftMirror(getInventory().getOwnerHandle().getInventory() + .getArmor(EquipmentSlot.FEET.getIndex())); + } + + @Override + public void setBoots(@Nullable ItemStack boots) { + getInventory().getOwnerHandle().getInventory().armor + .set(EquipmentSlot.FEET.getIndex(), CraftItemStack.asNMSCopy(boots)); + } + + @Override + public @NotNull ItemStack getItemInMainHand() { + Inventory internal = getInventory().getOwnerHandle().getInventory(); + return CraftItemStack.asCraftMirror(internal.getItem(internal.selected)); + } + + @Override + public void setItemInMainHand(@Nullable ItemStack item) { + Inventory internal = getInventory().getOwnerHandle().getInventory(); + internal.setItem(internal.selected, CraftItemStack.asNMSCopy(item)); + } + + @Override + public @NotNull ItemStack getItemInOffHand() { + return CraftItemStack.asCraftMirror(getInventory().getOwnerHandle().getInventory().offhand.getFirst()); + } + + @Override + public void setItemInOffHand(@Nullable ItemStack item) { + getInventory().getOwnerHandle().getInventory().offhand.set(0, CraftItemStack.asNMSCopy(item)); + } + + @Override + public @NotNull ItemStack getItemInHand() { + return getItemInMainHand(); + } + + @Override + public void setItemInHand(@Nullable ItemStack stack) { + setItemInMainHand(stack); + } + + @Override + public int getHeldItemSlot() { + Inventory internal = getInventory().getOwnerHandle().getInventory(); + return internal.items.size() - 9 + internal.selected; + } + + @Override + public void setHeldItemSlot(int slot) { + slot %= 9; + getInventory().getOwnerHandle().getInventory().selected = slot; + } + + @Override + public @Nullable ItemStack getItem(@NotNull org.bukkit.inventory.EquipmentSlot slot) { + return switch (slot) { + case HAND -> getItemInMainHand(); + case OFF_HAND -> getItemInOffHand(); + case FEET -> getBoots(); + case LEGS -> getLeggings(); + case CHEST -> getChestplate(); + case HEAD -> getHelmet(); + default -> throw new IllegalArgumentException("Unsupported EquipmentSlot " + slot); + }; + } + + @Override + public void setItem(@NotNull org.bukkit.inventory.EquipmentSlot slot, @Nullable ItemStack item) { + switch (slot) { + case HAND -> setItemInMainHand(item); + case OFF_HAND -> setItemInOffHand(item); + case FEET -> setBoots(item); + case LEGS -> setLeggings(item); + case CHEST -> setChestplate(item); + case HEAD -> setHelmet(item); + default -> throw new IllegalArgumentException("Unsupported EquipmentSlot " + slot); + } + } + +} diff --git a/internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/container/bukkit/OpenPlayerInventorySelf.java b/internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/container/bukkit/OpenPlayerInventorySelf.java new file mode 100644 index 00000000..e44eb4c9 --- /dev/null +++ b/internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/container/bukkit/OpenPlayerInventorySelf.java @@ -0,0 +1,26 @@ +package com.lishid.openinv.internal.v1_21_R2.container.bukkit; + +import com.lishid.openinv.internal.v1_21_R2.container.OpenInventory; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; + +public class OpenPlayerInventorySelf extends OpenPlayerInventory { + + private final int offset; + + public OpenPlayerInventorySelf(@NotNull OpenInventory inventory, int offset) { + super(inventory); + this.offset = offset; + } + + @Override + public ItemStack getItem(int index) { + return super.getItem(offset + index); + } + + @Override + public void setItem(int index, ItemStack item) { + super.setItem(offset + index, item); + } + +} diff --git a/internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/container/menu/OpenChestMenu.java b/internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/container/menu/OpenChestMenu.java new file mode 100644 index 00000000..d12c373a --- /dev/null +++ b/internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/container/menu/OpenChestMenu.java @@ -0,0 +1,478 @@ +package com.lishid.openinv.internal.v1_21_R2.container.menu; + +import com.google.common.base.Suppliers; +import com.lishid.openinv.internal.ISpecialInventory; +import com.lishid.openinv.internal.InternalOwned; +import com.lishid.openinv.internal.v1_21_R2.container.bukkit.OpenDummyInventory; +import com.lishid.openinv.internal.v1_21_R2.container.slot.SlotPlaceholder; +import com.lishid.openinv.internal.v1_21_R2.container.slot.SlotViewOnly; +import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.ints.IntList; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.Container; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.ChestMenu; +import net.minecraft.world.inventory.ClickType; +import net.minecraft.world.inventory.ContainerData; +import net.minecraft.world.inventory.ContainerListener; +import net.minecraft.world.inventory.ContainerSynchronizer; +import net.minecraft.world.inventory.DataSlot; +import net.minecraft.world.inventory.MenuType; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.ItemStack; +import org.bukkit.craftbukkit.v1_21_R2.inventory.CraftInventoryView; +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; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Supplier; + +/** + * An extension of {@link AbstractContainerMenu} that supports {@link SlotPlaceholder placeholders}. + */ +public abstract class OpenChestMenu> + extends AbstractContainerMenu { + + protected static final int BOTTOM_INVENTORY_SIZE = 36; + + protected final T container; + protected final ServerPlayer viewer; + protected final boolean viewOnly; + protected final boolean ownContainer; + protected final int topSize; + private CraftInventoryView, Inventory> bukkitEntity; + // Syncher fields + private @Nullable ContainerSynchronizer synchronizer; + private final List dataSlots = new ArrayList<>(); + private final IntList remoteDataSlots = new IntArrayList(); + private final List containerListeners = new ArrayList<>(); + private ItemStack remoteCarried = ItemStack.EMPTY; + private boolean suppressRemoteUpdates; + + protected OpenChestMenu( + @NotNull MenuType type, + int containerCounter, + @NotNull T container, + @NotNull ServerPlayer viewer, + boolean viewOnly) { + super(type, containerCounter); + this.container = container; + this.viewer = viewer; + this.viewOnly = viewOnly; + ownContainer = container.getOwnerHandle().equals(viewer); + topSize = getTopSize(viewer); + + preSlotSetup(); + + int upperRows = topSize / 9; + // View's upper inventory - our container + for (int row = 0; row < upperRows; ++row) { + for (int col = 0; col < 9; ++col) { + // x and y for client purposes, but hey, we're thorough here. + // Adapted from net.minecraft.world.inventory.ChestMenu + int x = 8 + col * 18; + int y = 18 + row * 18; + int index = row * 9 + col; + + // Guard against weird inventory sizes. + if (index >= container.getContainerSize()) { + addSlot(new SlotViewOnly(container, index, x, y)); + continue; + } + + Slot slot = getUpperSlot(index, x, y); + + addSlot(slot); + } + } + + // View's lower inventory - viewer inventory + int playerInvPad = (upperRows - 4) * 18; + for (int row = 0; row < 3; ++row) { + for (int col = 0; col < 9; ++col) { + int x = 8 + col * 18; + int y = playerInvPad + row * 18 + 103; + addSlot(new Slot(viewer.getInventory(), row * 9 + col + 9, x, y)); + } + } + // Hotbar + for (int col = 0; col < 9; ++col) { + int x = 8 + col * 18; + int y = playerInvPad + 161; + addSlot(new Slot(viewer.getInventory(), col, x, y)); + } + } + + public static @NotNull MenuType getChestMenuType(int inventorySize) { + inventorySize = ((int) Math.ceil(inventorySize / 9.0)) * 9; + return switch (inventorySize) { + case 9 -> MenuType.GENERIC_9x1; + case 18 -> MenuType.GENERIC_9x2; + case 27 -> MenuType.GENERIC_9x3; + case 36 -> MenuType.GENERIC_9x4; + case 45 -> MenuType.GENERIC_9x5; + case 54 -> MenuType.GENERIC_9x6; + default -> throw new IllegalArgumentException("Inventory size unsupported: " + inventorySize); + }; + } + + protected void preSlotSetup() {} + + protected @NotNull Slot getUpperSlot(int index, int x, int y) { + if (viewOnly) { + return new SlotViewOnly(container, index, x, y); + } + return new Slot(container, index, x, y); + } + + + @Override + public final @NotNull CraftInventoryView, Inventory> getBukkitView() { + if (bukkitEntity == null) { + bukkitEntity = createBukkitEntity(); + } + + return bukkitEntity; + } + + protected @NotNull CraftInventoryView, Inventory> createBukkitEntity() { + Inventory top; + if (viewOnly) { + top = new OpenDummyInventory(container); + } else { + top = container.getBukkitInventory(); + } + return new CraftInventoryView<>(viewer.getBukkitEntity(), top, this) { + @Override + public @Nullable Inventory getInventory(int rawSlot) { + if (viewOnly) { + return null; + } + return super.getInventory(rawSlot); + } + + @Override + public int convertSlot(int rawSlot) { + if (viewOnly) { + return InventoryView.OUTSIDE; + } + return super.convertSlot(rawSlot); + } + + @Override + public @NotNull InventoryType.SlotType getSlotType(int slot) { + if (viewOnly) { + return InventoryType.SlotType.OUTSIDE; + } + return super.getSlotType(slot); + } + }; + } + + private int getTopSize(ServerPlayer viewer) { + MenuType menuType = getType(); + if (menuType == null) { + throw new IllegalStateException("MenuType cannot be null!"); + } else if (menuType == MenuType.GENERIC_9x1) { + return 9; + } else if (menuType == MenuType.GENERIC_9x2) { + return 18; + } else if (menuType == MenuType.GENERIC_9x3) { + return 27; + } else if (menuType == MenuType.GENERIC_9x4) { + return 36; + } else if (menuType == MenuType.GENERIC_9x5) { + return 45; + } else if (menuType == MenuType.GENERIC_9x6) { + return 54; + } + // This is a bit gross, but allows us a safe fallthrough. + return menuType.create(-1, viewer.getInventory()).slots.size() - BOTTOM_INVENTORY_SIZE; + } + + /** + * Reimplementation of {@link AbstractContainerMenu#moveItemStackTo(ItemStack, int, int, boolean)} that ignores fake + * slots and respects {@link Slot#hasItem()}. + * + * @param itemStack the stack to quick-move + * @param rangeLow the start of the range of slots that can be moved to, inclusive + * @param rangeHigh the end of the range of slots that can be moved to, exclusive + * @param topDown whether to start at the top of the range or bottom + * @return whether the stack was modified as a result of being quick-moved + */ + @Override + protected boolean moveItemStackTo(ItemStack itemStack, int rangeLow, int rangeHigh, boolean topDown) { + boolean modified = false; + boolean stackable = itemStack.isStackable(); + Slot firstEmpty = null; + + for (int index = topDown ? rangeHigh - 1 : rangeLow; + !itemStack.isEmpty() && (topDown ? index >= rangeLow : index < rangeHigh); + index += topDown ? -1 : 1 + ) { + Slot slot = slots.get(index); + // If the slot cannot be added to, check the next slot. + if (slot.isFake() || !slot.mayPlace(itemStack)) { + continue; + } + + if (slot.hasItem()) { + // If the item isn't stackable, check the next slot. + if (!stackable) { + continue; + } + // Otherwise, add as many as we can from our stack to the slot. + modified |= addToExistingStack(itemStack, slot); + } else { + // If this is the first empty slot, keep track of it for later use. + if (firstEmpty == null) { + firstEmpty = slot; + } + // If the item isn't stackable, we've located the slot we're adding it to, so we're done. + if (!stackable) { + break; + } + } + } + + // If the item hasn't been fully added yet, add as many as we can to the first open slot. + if (!itemStack.isEmpty() && firstEmpty != null) { + firstEmpty.setByPlayer(itemStack.split(Math.min(itemStack.getCount(), firstEmpty.getMaxStackSize(itemStack)))); + firstEmpty.setChanged(); + modified = true; + } + + return modified; + } + + private static boolean addToExistingStack(ItemStack itemStack, Slot slot) { + ItemStack existing = slot.getItem(); + + // If the items aren't the same, we can't add our item. + if (!ItemStack.isSameItemSameComponents(itemStack, existing)) { + return false; + } + + int max = slot.getMaxStackSize(existing); + int existingCount = existing.getCount(); + + // If the stack is already full, we can't add more. + if (existingCount >= max) { + return false; + } + + int total = existingCount + itemStack.getCount(); + + // If the existing item can accept the entirety of our item, we're done! + if (total <= max) { + itemStack.setCount(0); + existing.setCount(total); + slot.setChanged(); + return true; + } + + // Otherwise, add as many as we can. + itemStack.shrink(max - existingCount); + existing.setCount(max); + slot.setChanged(); + return true; + } + + @Override + public void clicked(int i, int j, ClickType clickType, Player player) { + if (viewOnly) { + if (clickType == ClickType.QUICK_CRAFT) { + sendAllDataToRemote(); + } + return; + } + super.clicked(i, j, clickType, player); + } + + @Override + public boolean stillValid(Player player) { + return true; + } + + // Overrides from here on are purely to modify the sync process to send placeholder items. + @Override + protected Slot addSlot(Slot slot) { + slot.index = this.slots.size(); + this.slots.add(slot); + this.lastSlots.add(ItemStack.EMPTY); + this.remoteSlots.add(ItemStack.EMPTY); + return slot; + } + + @Override + protected DataSlot addDataSlot(DataSlot dataSlot) { + this.dataSlots.add(dataSlot); + this.remoteDataSlots.add(0); + return dataSlot; + } + + @Override + protected void addDataSlots(ContainerData containerData) { + for (int i = 0; i < containerData.getCount(); i++) { + this.addDataSlot(DataSlot.forContainer(containerData, i)); + } + } + + @Override + public void addSlotListener(ContainerListener containerListener) { + if (!this.containerListeners.contains(containerListener)) { + this.containerListeners.add(containerListener); + this.broadcastChanges(); + } + } + + @Override + public void setSynchronizer(ContainerSynchronizer containerSynchronizer) { + this.synchronizer = containerSynchronizer; + this.sendAllDataToRemote(); + } + + @Override + public void sendAllDataToRemote() { + for (int index = 0; index < slots.size(); ++index) { + Slot slot = slots.get(index); + this.remoteSlots.set(index, (slot instanceof SlotPlaceholder placeholder ? placeholder.getOrDefault() : slot.getItem()).copy()); + } + + remoteCarried = getCarried().copy(); + + for (int index = 0; index < this.dataSlots.size(); ++index) { + this.remoteDataSlots.set(index, this.dataSlots.get(index).get()); + } + + if (this.synchronizer != null) { + this.synchronizer.sendInitialData(this, this.remoteSlots, this.remoteCarried, this.remoteDataSlots.toIntArray()); + } + } + + @Override + public void broadcastCarriedItem() { + this.remoteCarried = this.getCarried().copy(); + if (this.synchronizer != null) { + this.synchronizer.sendCarriedChange(this, this.remoteCarried); + } + } + + @Override + public void removeSlotListener(ContainerListener containerListener) { + this.containerListeners.remove(containerListener); + } + + @Override + public void broadcastChanges() { + for (int index = 0; index < this.slots.size(); ++index) { + Slot slot = this.slots.get(index); + ItemStack itemstack = slot instanceof SlotPlaceholder placeholder ? placeholder.getOrDefault() : slot.getItem(); + Supplier supplier = Suppliers.memoize(itemstack::copy); + this.triggerSlotListeners(index, itemstack, supplier); + this.synchronizeSlotToRemote(index, itemstack, supplier); + } + + this.synchronizeCarriedToRemote(); + + for (int index = 0; index < this.dataSlots.size(); ++index) { + DataSlot dataSlot = this.dataSlots.get(index); + int j = dataSlot.get(); + if (dataSlot.checkAndClearUpdateFlag()) { + this.updateDataSlotListeners(index, j); + } + + this.synchronizeDataSlotToRemote(index, j); + } + } + + @Override + public void broadcastFullState() { + for (int index = 0; index < this.slots.size(); ++index) { + ItemStack itemstack = this.slots.get(index).getItem(); + this.triggerSlotListeners(index, itemstack, itemstack::copy); + } + + for (int index = 0; index < this.dataSlots.size(); ++index) { + DataSlot containerproperty = this.dataSlots.get(index); + if (containerproperty.checkAndClearUpdateFlag()) { + this.updateDataSlotListeners(index, containerproperty.get()); + } + } + + this.sendAllDataToRemote(); + } + + private void updateDataSlotListeners(int i, int j) { + for (ContainerListener containerListener : this.containerListeners) { + containerListener.dataChanged(this, i, j); + } + } + + private void triggerSlotListeners(int index, ItemStack itemStack, Supplier supplier) { + ItemStack itemStack1 = this.lastSlots.get(index); + if (!ItemStack.matches(itemStack1, itemStack)) { + ItemStack itemStack2 = supplier.get(); + this.lastSlots.set(index, itemStack2); + + for (ContainerListener containerListener : this.containerListeners) { + containerListener.slotChanged(this, index, itemStack2); + } + } + } + + private void synchronizeSlotToRemote(int i, ItemStack itemStack, Supplier supplier) { + if (!this.suppressRemoteUpdates) { + ItemStack itemStack1 = this.remoteSlots.get(i); + if (!ItemStack.matches(itemStack1, itemStack)) { + ItemStack itemstack2 = supplier.get(); + this.remoteSlots.set(i, itemstack2); + if (this.synchronizer != null) { + this.synchronizer.sendSlotChange(this, i, itemstack2); + } + } + } + } + + private void synchronizeDataSlotToRemote(int index, int value) { + if (!this.suppressRemoteUpdates) { + int existing = this.remoteDataSlots.getInt(index); + if (existing != value) { + this.remoteDataSlots.set(index, value); + if (this.synchronizer != null) { + this.synchronizer.sendDataChange(this, index, value); + } + } + } + } + + private void synchronizeCarriedToRemote() { + if (!this.suppressRemoteUpdates && !ItemStack.matches(this.getCarried(), this.remoteCarried)) { + this.remoteCarried = this.getCarried().copy(); + if (this.synchronizer != null) { + this.synchronizer.sendCarriedChange(this, this.remoteCarried); + } + } + } + + @Override + public void setRemoteCarried(ItemStack itemstack) { + this.remoteCarried = itemstack.copy(); + } + + @Override + public void suppressRemoteUpdates() { + this.suppressRemoteUpdates = true; + } + + @Override + public void resumeRemoteUpdates() { + this.suppressRemoteUpdates = false; + } + +} diff --git a/internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/container/menu/OpenEnderChestMenu.java b/internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/container/menu/OpenEnderChestMenu.java new file mode 100644 index 00000000..832efa73 --- /dev/null +++ b/internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/container/menu/OpenEnderChestMenu.java @@ -0,0 +1,53 @@ +package com.lishid.openinv.internal.v1_21_R2.container.menu; + +import com.lishid.openinv.internal.v1_21_R2.container.OpenEnderChest; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.ItemStack; +import org.jetbrains.annotations.NotNull; + +public class OpenEnderChestMenu extends OpenChestMenu { + + public OpenEnderChestMenu( + @NotNull OpenEnderChest enderChest, + @NotNull ServerPlayer viewer, + int containerId, + boolean viewOnly) { + super(getChestMenuType(enderChest.getContainerSize()), containerId, enderChest, viewer, viewOnly); + } + + @Override + public ItemStack quickMoveStack(Player player, int index) { + if (viewOnly) { + return ItemStack.EMPTY; + } + + // See ChestMenu + Slot slot = this.slots.get(index); + + if (slot.isFake() || !slot.hasItem()) { + return ItemStack.EMPTY; + } + + ItemStack itemStack = slot.getItem(); + ItemStack original = itemStack.copy(); + + if (index < topSize) { + if (!this.moveItemStackTo(itemStack, topSize, this.slots.size(), true)) { + return ItemStack.EMPTY; + } + } else if (!this.moveItemStackTo(itemStack, 0, topSize, false)) { + return ItemStack.EMPTY; + } + + if (itemStack.isEmpty()) { + slot.setByPlayer(ItemStack.EMPTY); + } else { + slot.setChanged(); + } + + return original; + } + +} diff --git a/internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/container/menu/OpenInventoryMenu.java b/internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/container/menu/OpenInventoryMenu.java new file mode 100644 index 00000000..0d0f2a1e --- /dev/null +++ b/internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/container/menu/OpenInventoryMenu.java @@ -0,0 +1,262 @@ +package com.lishid.openinv.internal.v1_21_R2.container.menu; + +import com.google.common.base.Preconditions; +import com.lishid.openinv.internal.v1_21_R2.container.OpenInventory; +import com.lishid.openinv.internal.v1_21_R2.container.bukkit.OpenDummyInventory; +import com.lishid.openinv.internal.v1_21_R2.container.bukkit.OpenPlayerInventorySelf; +import com.lishid.openinv.internal.v1_21_R2.container.slot.ContentDrop; +import com.lishid.openinv.internal.v1_21_R2.container.slot.ContentEquipment; +import com.lishid.openinv.internal.v1_21_R2.container.slot.SlotViewOnly; +import com.lishid.openinv.util.Permissions; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.EquipmentSlot; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.ChestMenu; +import net.minecraft.world.inventory.MenuType; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.ItemStack; +import org.bukkit.craftbukkit.v1_21_R2.inventory.CraftInventoryView; +import org.bukkit.craftbukkit.v1_21_R2.inventory.CraftItemStack; +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; + +public class OpenInventoryMenu extends OpenChestMenu { + + private int offset; + + public OpenInventoryMenu(OpenInventory inventory, ServerPlayer viewer, int i, boolean viewOnly) { + super(getMenuType(inventory, viewer), i, inventory, viewer, viewOnly); + } + + private static MenuType getMenuType(OpenInventory inventory, ServerPlayer viewer) { + int size = inventory.getContainerSize(); + // Disallow duplicate access to own main inventory contents. + if (inventory.getOwnerHandle().equals(viewer)) { + size -= viewer.getInventory().items.size(); + size = ((int) Math.ceil(size / 9.0)) * 9; + } + + return getChestMenuType(size); + } + + @Override + protected void preSlotSetup() { + offset = ownContainer ? viewer.getInventory().items.size() : 0; + } + + @Override + protected @NotNull Slot getUpperSlot(int index, int x, int y) { + index += offset; + Slot slot = container.getMenuSlot(index, x, y); + + // If the slot cannot be interacted with there's nothing to configure. + if (slot.getClass().equals(SlotViewOnly.class)) { + return slot; + } + + // Remove drop slot if viewer is not allowed to use it. + if (slot instanceof ContentDrop.SlotDrop + && (viewOnly || !Permissions.INVENTORY_SLOT_DROP.hasPermission(viewer.getBukkitEntity()))) { + return new SlotViewOnly(container, index, x, y); + } + + if (slot instanceof ContentEquipment.SlotEquipment equipment) { + if (viewOnly) { + return SlotViewOnly.wrap(slot); + } + + Permissions perm = switch (equipment.getEquipmentSlot()) { + case HEAD -> Permissions.INVENTORY_SLOT_HEAD_ANY; + case CHEST -> Permissions.INVENTORY_SLOT_CHEST_ANY; + case LEGS -> Permissions.INVENTORY_SLOT_LEGS_ANY; + case FEET -> Permissions.INVENTORY_SLOT_FEET_ANY; + // Off-hand can hold anything, not just equipment. + default -> null; + }; + + // If the viewer doesn't have permission, only allow equipment the viewee can equip in the slot. + if (perm != null && !perm.hasPermission(viewer.getBukkitEntity())) { + equipment.onlyEquipmentFor(container.getOwnerHandle()); + } + + // Equipment slots are a core part of the inventory, so they will always be shown. + return slot; + } + + // When viewing own inventory, only allow access to equipment and drop slots (equipment allowed above). + if (ownContainer && !(slot instanceof ContentDrop.SlotDrop)) { + return new SlotViewOnly(container, index, x, y); + } + + if (viewOnly) { + return SlotViewOnly.wrap(slot); + } + + return slot; + } + + @Override + protected @NotNull CraftInventoryView, Inventory> createBukkitEntity() { + org.bukkit.inventory.Inventory bukkitInventory; + if (viewOnly) { + bukkitInventory = new OpenDummyInventory(container); + } else if (ownContainer) { + bukkitInventory = new OpenPlayerInventorySelf(container, offset); + } else { + bukkitInventory = container.getBukkitInventory(); + } + + return new CraftInventoryView<>(viewer.getBukkitEntity(), bukkitInventory, this) { + @Override + public org.bukkit.inventory.ItemStack getItem(int index) { + if (viewOnly || index < 0) { + return null; + } + + Slot slot = slots.get(index); + return CraftItemStack.asCraftMirror(slot.hasItem() ? slot.getItem() : ItemStack.EMPTY); + } + + @Override + public boolean isInTop(int rawSlot) { + return rawSlot < topSize; + } + + @Override + public @Nullable Inventory getInventory(int rawSlot) { + if (viewOnly) { + return null; + } + if (rawSlot == InventoryView.OUTSIDE || rawSlot == -1) { + return null; + } + Preconditions.checkArgument(rawSlot >= 0 && rawSlot < topSize + offset + BOTTOM_INVENTORY_SIZE, + "Slot %s outside of inventory", rawSlot); + if (rawSlot > topSize) { + return getBottomInventory(); + } + Slot slot = slots.get(rawSlot); + if (slot.isFake()) { + return null; + } + return getTopInventory(); + } + + @Override + public int convertSlot(int rawSlot) { + if (viewOnly) { + return InventoryView.OUTSIDE; + } + if (rawSlot < 0) { + return rawSlot; + } + if (rawSlot < topSize) { + Slot slot = slots.get(rawSlot); + if (slot.isFake()) { + return InventoryView.OUTSIDE; + } + return rawSlot; + } + + int slot = rawSlot - topSize; + + if (slot >= 27) { + slot -= 27; + } else { + slot += 9; + } + + return slot; + } + + @Override + public @NotNull InventoryType.SlotType getSlotType(int slot) { + if (viewOnly || slot < 0) { + return InventoryType.SlotType.OUTSIDE; + } + if (slot >= topSize) { + slot -= topSize; + if (slot >= 27) { + return InventoryType.SlotType.QUICKBAR; + } + return InventoryType.SlotType.CONTAINER; + } + return OpenInventoryMenu.this.container.getSlotType(offset + slot); + } + + @Override + public int countSlots() { + return topSize + BOTTOM_INVENTORY_SIZE; + } + }; + } + + @Override + public ItemStack quickMoveStack(Player player, int index) { + if (viewOnly) { + return ItemStack.EMPTY; + } + + // See ChestMenu and InventoryMenu + Slot slot = this.slots.get(index); + + if (!slot.hasItem() || slot.isFake()) { + return ItemStack.EMPTY; + } + + ItemStack itemStack = slot.getItem(); + ItemStack originalStack = itemStack.copy(); + + if (index < topSize) { + // If we're moving top to bottom, do a normal transfer. + if (!this.moveItemStackTo(itemStack, topSize, this.slots.size(), true)) { + return ItemStack.EMPTY; + } + } else { + EquipmentSlot equipmentSlot = player.getEquipmentSlotForItem(itemStack); + boolean movedGear = switch (equipmentSlot) { + // If this is gear, try to move it to the correct slot first. + case OFFHAND, FEET, LEGS, CHEST, HEAD -> { + // Locate the correct slot in the contents following the main inventory. + for (int extra = container.getOwnerHandle().getInventory().items.size() - offset; extra < topSize; ++extra) { + Slot extraSlot = getSlot(extra); + if (extraSlot instanceof ContentEquipment.SlotEquipment equipSlot + && equipSlot.getEquipmentSlot() == equipmentSlot) { + // If we've found a matching slot, try to move to it. + // If this succeeds, even partially, we will not attempt to move to other slots. + // Otherwise, armor is already occupied, so we'll fall through to main inventory. + yield this.moveItemStackTo(itemStack, extra, extra + 1, false); + } + } + yield false; + } + // Non-gear gets no special treatment. + default -> false; + }; + + // If main inventory is not available, there's nowhere else to move. + if (offset != 0) { + if (!movedGear) { + return ItemStack.EMPTY; + } + } else { + // If we didn't move to a gear slot, try to move to a main inventory slot. + if (!movedGear && !this.moveItemStackTo(itemStack, 0, container.getOwnerHandle().getInventory().items.size(), true)) { + return ItemStack.EMPTY; + } + } + } + + if (itemStack.isEmpty()) { + slot.setByPlayer(ItemStack.EMPTY); + } else { + slot.setChanged(); + } + + return originalStack; + } + +} diff --git a/internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/container/slot/Content.java b/internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/container/slot/Content.java new file mode 100644 index 00000000..15c04fef --- /dev/null +++ b/internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/container/slot/Content.java @@ -0,0 +1,69 @@ +package com.lishid.openinv.internal.v1_21_R2.container.slot; + +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.Container; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.ItemStack; +import org.jetbrains.annotations.NotNull; + +/** + * An interface defining behaviors for entries in a {@link Container}. Used to reduce duplicate content reordering. + */ +public interface Content { + + /** + * Update internal holder. + * + * @param holder the new holder + */ + void setHolder(@NotNull ServerPlayer holder); + + /** + * Get the current item. + * + * @return the current item + */ + ItemStack get(); + + /** + * Remove the current item. + * + * @return the current item + */ + ItemStack remove(); + + /** + * Remove some of the current item. + * + * @return the current item + */ + ItemStack removePartial(int amount); + + /** + * Set the current item. If slot is currently not usable, will drop item instead. + * + * @param itemStack the item to set + */ + void set(ItemStack itemStack); + + /** + * Get a {@link Slot} for use in a {@link net.minecraft.world.inventory.AbstractContainerMenu ContainerMenu}. Will + * impose any specific restrictions to insertion or removal. + * + * @param container the backing container + * @param slot the slot of the backing container represented + * @param x clientside x dimension from top left of inventory, not used + * @param y clientside y dimension from top left of inventory, not used + * @return a menu slot + */ + Slot asSlot(Container container, int slot, int x, int y); + + /** + * Get a loose Bukkit translation of what this slot stores. For example, any slot that drops items at the owner rather + * than insert them will report itself as being {@link org.bukkit.event.inventory.InventoryType.SlotType#OUTSIDE}. + * + * @return the closes Bukkit slot type + */ + org.bukkit.event.inventory.InventoryType.SlotType getSlotType(); + +} diff --git a/internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/container/slot/ContentCrafting.java b/internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/container/slot/ContentCrafting.java new file mode 100644 index 00000000..fb71cf40 --- /dev/null +++ b/internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/container/slot/ContentCrafting.java @@ -0,0 +1,131 @@ +package com.lishid.openinv.internal.v1_21_R2.container.slot; + +import com.lishid.openinv.internal.v1_21_R2.container.Placeholders; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.Container; +import net.minecraft.world.ContainerHelper; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.ItemStack; +import org.bukkit.event.inventory.InventoryType; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +/** + * A slot in a survival crafting inventory. Unavailable when not online in a survival mode. + */ +public class ContentCrafting implements Content { + + private final int index; + private ServerPlayer holder; + private List items; + + public ContentCrafting(@NotNull ServerPlayer holder, int index) { + setHolder(holder); + this.index = index; + } + + private boolean isAvailable() { + return isAvailable(holder); + } + + static boolean isAvailable(@NotNull ServerPlayer holder) { + // Player must be online and not in creative - since the creative client is (semi-)authoritative, + // it ignores changes without extra help, and will delete the item as a result. + // Spectator mode is technically possible but may cause the item to be dropped if the client opens an inventory. + return holder.connection != null && !holder.connection.isDisconnected() && holder.gameMode.isSurvival(); + } + + @Override + public void setHolder(@NotNull ServerPlayer holder) { + this.holder = holder; + // Note: CraftingContainer#getItems is immutable! Be careful with updates. + this.items = holder.inventoryMenu.getCraftSlots().getContents(); + } + + @Override + public ItemStack get() { + return isAvailable() ? items.get(index) : ItemStack.EMPTY; + } + + @Override + public ItemStack remove() { + if (!this.isAvailable()) { + return ItemStack.EMPTY; + } + ItemStack removed = items.remove(index); + if (removed.isEmpty()) { + return ItemStack.EMPTY; + } + holder.inventoryMenu.slotsChanged(holder.inventoryMenu.getCraftSlots()); + return removed; + } + + @Override + public ItemStack removePartial(int amount) { + if (!this.isAvailable()) { + return ItemStack.EMPTY; + } + ItemStack removed = ContainerHelper.removeItem(items, index, amount); + if (removed.isEmpty()) { + return ItemStack.EMPTY; + } + holder.inventoryMenu.slotsChanged(holder.inventoryMenu.getCraftSlots()); + return removed; + } + + @Override + public void set(ItemStack itemStack) { + if (isAvailable()) { + items.set(index, itemStack); + holder.inventoryMenu.slotsChanged(holder.inventoryMenu.getCraftSlots()); + } else { + holder.drop(itemStack, false); + } + } + + @Override + public Slot asSlot(Container container, int slot, int x, int y) { + return new SlotCrafting(container, slot, x, y); + } + + @Override + public InventoryType.SlotType getSlotType() { + return isAvailable() ? InventoryType.SlotType.CRAFTING : InventoryType.SlotType.OUTSIDE; + } + + public class SlotCrafting extends SlotPlaceholder { + + private SlotCrafting(Container container, int index, int x, int y) { + super(container, index, x, y); + } + + @Override + public ItemStack getOrDefault() { + return isAvailable() ? items.get(ContentCrafting.this.index) : Placeholders.survivalOnly(holder); + } + + @Override + public boolean mayPickup(Player player) { + return isAvailable(); + } + + @Override + public boolean mayPlace(ItemStack itemStack) { + return isAvailable(); + } + + @Override + public boolean hasItem() { + return isAvailable() && super.hasItem(); + } + + @Override + public boolean isFake() { + return !isAvailable(); + } + + } + +} diff --git a/internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/container/slot/ContentCraftingResult.java b/internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/container/slot/ContentCraftingResult.java new file mode 100644 index 00000000..62e9a62b --- /dev/null +++ b/internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/container/slot/ContentCraftingResult.java @@ -0,0 +1,48 @@ +package com.lishid.openinv.internal.v1_21_R2.container.slot; + +import com.lishid.openinv.internal.v1_21_R2.container.Placeholders; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.Container; +import net.minecraft.world.inventory.InventoryMenu; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.ItemStack; +import org.bukkit.event.inventory.InventoryType; +import org.jetbrains.annotations.NotNull; + +/** + * A slot allowing viewing of the crafting result. + * + *

Unmodifiable because I said so. Use your own crafting grid.

+ */ +public class ContentCraftingResult extends ContentViewOnly { + + public ContentCraftingResult(@NotNull ServerPlayer holder) { + super(holder); + } + + @Override + public ItemStack get() { + InventoryMenu inventoryMenu = holder.inventoryMenu; + return inventoryMenu.getResultSlot().getItem(); + } + + @Override + public Slot asSlot(Container container, int slot, int x, int y) { + return new SlotViewOnly(container, slot, x, y) { + @Override + public ItemStack getOrDefault() { + if (!ContentCrafting.isAvailable(holder)) { + return Placeholders.survivalOnly(holder); + } + InventoryMenu inventoryMenu = holder.inventoryMenu; + return inventoryMenu.getResultSlot().getItem(); + } + }; + } + + @Override + public InventoryType.SlotType getSlotType() { + return InventoryType.SlotType.RESULT; + } + +} diff --git a/internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/container/slot/ContentCursor.java b/internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/container/slot/ContentCursor.java new file mode 100644 index 00000000..41cbb3f7 --- /dev/null +++ b/internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/container/slot/ContentCursor.java @@ -0,0 +1,117 @@ +package com.lishid.openinv.internal.v1_21_R2.container.slot; + +import com.lishid.openinv.internal.v1_21_R2.container.Placeholders; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.Container; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.ItemStack; +import org.bukkit.event.inventory.InventoryType; +import org.jetbrains.annotations.NotNull; + +/** + * A slot wrapping the active menu's cursor. Unavailable when not online in a survival mode. + */ +public class ContentCursor implements Content { + + private @NotNull ServerPlayer holder; + + public ContentCursor(@NotNull ServerPlayer holder) { + this.holder = holder; + } + + @Override + public void setHolder(@NotNull ServerPlayer holder) { + this.holder = holder; + } + + @Override + public ItemStack get() { + return isAvailable() ? holder.containerMenu.getCarried() : ItemStack.EMPTY; + } + + @Override + public ItemStack remove() { + ItemStack carried = holder.containerMenu.getCarried(); + holder.containerMenu.setCarried(ItemStack.EMPTY); + return carried; + } + + @Override + public ItemStack removePartial(int amount) { + ItemStack carried = holder.containerMenu.getCarried(); + if (!carried.isEmpty() && carried.getCount() >= amount) { + ItemStack value = carried.split(amount); + if (carried.isEmpty()) { + holder.containerMenu.setCarried(ItemStack.EMPTY); + } + return value; + } + return ItemStack.EMPTY; + } + + @Override + public void set(ItemStack itemStack) { + if (isAvailable()) { + holder.containerMenu.setCarried(itemStack); + } else { + holder.drop(itemStack, false); + } + } + + private boolean isAvailable() { + // Player must be online and not in creative - since the creative client is (semi-)authoritative, + // it ignores changes without extra help, and will delete the item as a result. + // Spectator mode is technically possible but may cause the item to be dropped if the client opens an inventory. + return holder.connection != null && !holder.connection.isDisconnected() && holder.gameMode.isSurvival(); + } + + @Override + public Slot asSlot(Container container, int slot, int x, int y) { + return new SlotCursor(container, slot, x, y); + } + + @Override + public InventoryType.SlotType getSlotType() { + // As close as possible to "not real" + return InventoryType.SlotType.OUTSIDE; + } + + public class SlotCursor extends SlotPlaceholder { + + private SlotCursor(Container container, int index, int x, int y) { + super(container, index, x, y); + } + + @Override + public ItemStack getOrDefault() { + if (!isAvailable()) { + return Placeholders.survivalOnly(holder); + } + ItemStack carried = holder.containerMenu.getCarried(); + return carried.isEmpty() ? Placeholders.cursor : carried; + } + + @Override + public boolean mayPickup(Player player) { + return isAvailable(); + } + + @Override + public boolean mayPlace(ItemStack itemStack) { + return isAvailable(); + } + + @Override + public boolean hasItem() { + return isAvailable() && super.hasItem(); + } + + @Override + public boolean isFake() { + return true; + } + + } + +} diff --git a/internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/container/slot/ContentDrop.java b/internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/container/slot/ContentDrop.java new file mode 100644 index 00000000..ebe52541 --- /dev/null +++ b/internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/container/slot/ContentDrop.java @@ -0,0 +1,88 @@ +package com.lishid.openinv.internal.v1_21_R2.container.slot; + +import com.lishid.openinv.internal.v1_21_R2.container.Placeholders; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.Container; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.ItemStack; +import org.bukkit.event.inventory.InventoryType; +import org.jetbrains.annotations.NotNull; + +/** + * A fake slot used to drop items. Unavailable offline. + */ +public class ContentDrop implements Content { + + private ServerPlayer holder; + + public ContentDrop(@NotNull ServerPlayer holder) { + this.holder = holder; + } + + @Override + public void setHolder(@NotNull ServerPlayer holder) { + this.holder = holder; + } + + @Override + public ItemStack get() { + return ItemStack.EMPTY; + } + + @Override + public ItemStack remove() { + return ItemStack.EMPTY; + } + + @Override + public ItemStack removePartial(int amount) { + return ItemStack.EMPTY; + } + + @Override + public void set(ItemStack itemStack) { + holder.drop(itemStack, true); + } + + @Override + public Slot asSlot(Container container, int slot, int x, int y) { + return new SlotDrop(container, slot, x, y); + } + + @Override + public InventoryType.SlotType getSlotType() { + // Behaves like dropping an item outside the screen, just by the target player. + return InventoryType.SlotType.OUTSIDE; + } + + public class SlotDrop extends SlotPlaceholder { + + private SlotDrop(Container container, int index, int x, int y) { + super(container, index, x, y); + } + + @Override + public ItemStack getOrDefault() { + return holder.connection != null && !holder.connection.isDisconnected() + ? Placeholders.drop + : Placeholders.blockedOffline; + } + + @Override + public boolean mayPlace(ItemStack itemStack) { + return holder.connection != null && !holder.connection.isDisconnected(); + } + + @Override + public boolean hasItem() { + return false; + } + + @Override + public boolean isFake() { + return true; + } + + } + +} diff --git a/internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/container/slot/ContentEquipment.java b/internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/container/slot/ContentEquipment.java new file mode 100644 index 00000000..def3448b --- /dev/null +++ b/internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/container/slot/ContentEquipment.java @@ -0,0 +1,78 @@ +package com.lishid.openinv.internal.v1_21_R2.container.slot; + +import com.lishid.openinv.internal.v1_21_R2.container.Placeholders; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.Container; +import net.minecraft.world.entity.EquipmentSlot; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.ItemStack; +import org.bukkit.event.inventory.InventoryType; +import org.jetbrains.annotations.NotNull; + +/** + * A slot for equipment that displays placeholders if empty. + */ +public class ContentEquipment extends ContentList { + + private final ItemStack placeholder; + private final EquipmentSlot equipmentSlot; + + public ContentEquipment(ServerPlayer holder, int index, EquipmentSlot equipmentSlot) { + super(holder, index, InventoryType.SlotType.ARMOR); + placeholder = switch (equipmentSlot) { + case HEAD -> Placeholders.emptyHelmet; + case CHEST -> Placeholders.emptyChestplate; + case LEGS -> Placeholders.emptyLeggings; + case FEET -> Placeholders.emptyBoots; + default -> Placeholders.emptyOffHand; + }; + this.equipmentSlot = equipmentSlot; + } + + @Override + public void setHolder(@NotNull ServerPlayer holder) { + this.items = holder.getInventory().armor; + } + + @Override + public Slot asSlot(Container container, int slot, int x, int y) { + return new SlotEquipment(container, slot, x, y); + } + + public class SlotEquipment extends SlotPlaceholder { + + private ServerPlayer viewer; + + SlotEquipment(Container container, int index, int x, int y) { + super(container, index, x, y); + } + + @Override + public ItemStack getOrDefault() { + ItemStack itemStack = getItem(); + if (!itemStack.isEmpty()) { + return itemStack; + } + return placeholder; + } + + public EquipmentSlot getEquipmentSlot() { + return equipmentSlot; + } + + public void onlyEquipmentFor(ServerPlayer viewer) { + this.viewer = viewer; + } + + @Override + public boolean mayPlace(ItemStack var0) { + if (viewer == null) { + return true; + } + + return equipmentSlot == EquipmentSlot.OFFHAND || viewer.getEquipmentSlotForItem(var0) == equipmentSlot; + } + + } + +} diff --git a/internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/container/slot/ContentList.java b/internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/container/slot/ContentList.java new file mode 100644 index 00000000..27490a50 --- /dev/null +++ b/internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/container/slot/ContentList.java @@ -0,0 +1,58 @@ +package com.lishid.openinv.internal.v1_21_R2.container.slot; + +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.Container; +import net.minecraft.world.ContainerHelper; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.ItemStack; +import org.bukkit.event.inventory.InventoryType; + +import java.util.List; + +/** + * A normal slot backed by an item list. + */ +public abstract class ContentList implements Content { + + private final int index; + private final InventoryType.SlotType slotType; + protected List items; + + public ContentList(ServerPlayer holder, int index, InventoryType.SlotType slotType) { + this.index = index; + this.slotType = slotType; + setHolder(holder); + } + + @Override + public ItemStack get() { + return items.get(index); + } + + @Override + public ItemStack remove() { + ItemStack removed = items.remove(index); + return removed == null || removed.isEmpty() ? ItemStack.EMPTY : removed; + } + + @Override + public ItemStack removePartial(int amount) { + return ContainerHelper.removeItem(items, index, amount); + } + + @Override + public void set(ItemStack itemStack) { + items.set(index, itemStack); + } + + @Override + public Slot asSlot(Container container, int slot, int x, int y) { + return new Slot(container, slot, x, y); + } + + @Override + public InventoryType.SlotType getSlotType() { + return slotType; + } + +} diff --git a/internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/container/slot/ContentOffHand.java b/internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/container/slot/ContentOffHand.java new file mode 100644 index 00000000..b015bb8e --- /dev/null +++ b/internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/container/slot/ContentOffHand.java @@ -0,0 +1,46 @@ +package com.lishid.openinv.internal.v1_21_R2.container.slot; + +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.Container; +import net.minecraft.world.entity.EquipmentSlot; +import net.minecraft.world.inventory.Slot; +import org.bukkit.event.inventory.InventoryType; +import org.jetbrains.annotations.NotNull; + +/** + * A slot for equipment that updates held items if necessary. + */ +public class ContentOffHand extends ContentEquipment { + + private ServerPlayer holder; + + public ContentOffHand(ServerPlayer holder, int localIndex) { + super(holder, localIndex, EquipmentSlot.OFFHAND); + } + + @Override + public void setHolder(@NotNull ServerPlayer holder) { + this.items = holder.getInventory().offhand; + this.holder = holder; + } + + @Override + public InventoryType.SlotType getSlotType() { + return InventoryType.SlotType.QUICKBAR; + } + + @Override + public Slot asSlot(Container container, int slot, int x, int y) { + return new SlotEquipment(container, slot, x, y) { + @Override + public void setChanged() { + if (holder.connection != null + && !holder.connection.isDisconnected() + && holder.containerMenu != holder.inventoryMenu) { + holder.resendItemInHands(); + } + } + }; + } + +} diff --git a/internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/container/slot/ContentViewOnly.java b/internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/container/slot/ContentViewOnly.java new file mode 100644 index 00000000..37213db0 --- /dev/null +++ b/internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/container/slot/ContentViewOnly.java @@ -0,0 +1,56 @@ +package com.lishid.openinv.internal.v1_21_R2.container.slot; + +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.Container; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.ItemStack; +import org.bukkit.event.inventory.InventoryType; +import org.jetbrains.annotations.NotNull; + +/** + * A view-only slot that can't be interacted with. + */ +public class ContentViewOnly implements Content { + + @NotNull ServerPlayer holder; + + public ContentViewOnly(@NotNull ServerPlayer holder) { + this.holder = holder; + } + + @Override + public void setHolder(@NotNull ServerPlayer holder) { + this.holder = holder; + } + + @Override + public ItemStack get() { + return ItemStack.EMPTY; + } + + @Override + public ItemStack remove() { + return ItemStack.EMPTY; + } + + @Override + public ItemStack removePartial(int amount) { + return ItemStack.EMPTY; + } + + @Override + public void set(ItemStack itemStack) { + this.holder.drop(itemStack, false); + } + + @Override + public Slot asSlot(Container container, int slot, int x, int y) { + return new SlotViewOnly(container, slot, x, y); + } + + @Override + public InventoryType.SlotType getSlotType() { + return InventoryType.SlotType.OUTSIDE; + } + +} diff --git a/internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/container/slot/SlotPlaceholder.java b/internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/container/slot/SlotPlaceholder.java new file mode 100644 index 00000000..ef0bf7a0 --- /dev/null +++ b/internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/container/slot/SlotPlaceholder.java @@ -0,0 +1,20 @@ +package com.lishid.openinv.internal.v1_21_R2.container.slot; + +import net.minecraft.world.Container; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.ItemStack; + +/** + * An implementation of a slot as used by a menu that may have fake placeholder items. + * + *

Used to prevent plugins (particularly sorting plugins) from adding placeholders to inventories.

+ */ +public abstract class SlotPlaceholder extends Slot { + + public SlotPlaceholder(Container container, int index, int x, int y) { + super(container, index, x, y); + } + + public abstract ItemStack getOrDefault(); + +} diff --git a/internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/container/slot/SlotViewOnly.java b/internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/container/slot/SlotViewOnly.java new file mode 100644 index 00000000..7727775d --- /dev/null +++ b/internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/container/slot/SlotViewOnly.java @@ -0,0 +1,151 @@ +package com.lishid.openinv.internal.v1_21_R2.container.slot; + +import com.lishid.openinv.internal.v1_21_R2.container.Placeholders; +import net.minecraft.world.Container; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.ItemStack; +import org.jetbrains.annotations.NotNull; + +import java.util.Optional; + +/** + * A view-only {@link Slot}. "Blank" by default, but can wrap another slot to display its content. + */ +public class SlotViewOnly extends SlotPlaceholder { + + public static @NotNull SlotViewOnly wrap(@NotNull Slot wrapped) { + SlotViewOnly wrapper; + if (wrapped instanceof SlotPlaceholder placeholder) { + wrapper = new SlotViewOnly(wrapped.container, wrapped.slot, wrapped.x, wrapped.y) { + @Override + public ItemStack getOrDefault() { + return placeholder.getOrDefault(); + } + }; + } else { + wrapper = new SlotViewOnly(wrapped.container, wrapped.slot, wrapped.x, wrapped.y) { + @Override + public ItemStack getOrDefault() { + return wrapped.getItem(); + } + }; + } + wrapper.index = wrapped.index; + return wrapper; + } + + public SlotViewOnly(Container container, int index, int x, int y) { + super(container, index, x, y); + } + + @Override + public ItemStack getOrDefault() { + return Placeholders.notSlot; + } + + @Override + public void onQuickCraft(ItemStack var0, ItemStack var1) { + } + + @Override + public void onTake(Player var0, ItemStack var1) { + } + + @Override + public boolean mayPlace(ItemStack var0) { + return false; + } + + @Override + public ItemStack getItem() { + return ItemStack.EMPTY; + } + + @Override + public boolean hasItem() { + return false; + } + + @Override + public void setByPlayer(ItemStack newStack) { + } + + @Override + public void setByPlayer(ItemStack newStack, ItemStack oldStack) { + } + + @Override + public void set(ItemStack var0) { + } + + @Override + public void setChanged() { + } + + @Override + public int getMaxStackSize() { + return 0; + } + + @Override + public int getMaxStackSize(ItemStack itemStack) { + return 0; + } + + @Override + public ItemStack remove(int amount) { + return ItemStack.EMPTY; + } + + @Override + public boolean mayPickup(Player var0) { + return false; + } + + @Override + public boolean isActive() { + return false; + } + + @Override + public Optional tryRemove(int var0, int var1, Player var2) { + return Optional.empty(); + } + + @Override + public ItemStack safeTake(int var0, int var1, Player var2) { + return ItemStack.EMPTY; + } + + @Override + public ItemStack safeInsert(ItemStack itemStack) { + return itemStack; + } + + @Override + public ItemStack safeInsert(ItemStack itemStack, int amount) { + return itemStack; + } + + @Override + public boolean allowModification(Player var0) { + return false; + } + + @Override + public int getContainerSlot() { + return this.slot; + } + + @Override + public boolean isHighlightable() { + return false; + } + + @Override + public boolean isFake() { + return true; + } + +} diff --git a/internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/player/OpenPlayer.java b/internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/player/OpenPlayer.java new file mode 100644 index 00000000..4005359a --- /dev/null +++ b/internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/player/OpenPlayer.java @@ -0,0 +1,178 @@ +package com.lishid.openinv.internal.v1_21_R2.player; + +import com.lishid.openinv.event.OpenEvents; +import com.mojang.logging.LogUtils; +import net.minecraft.Util; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtIo; +import net.minecraft.nbt.NumericTag; +import net.minecraft.nbt.Tag; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.level.storage.PlayerDataStorage; +import org.bukkit.craftbukkit.v1_21_R2.CraftServer; +import org.bukkit.craftbukkit.v1_21_R2.entity.CraftPlayer; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Set; + +public class OpenPlayer extends CraftPlayer { + + /** + * List of tags to always reset when saving. + * + * @see net.minecraft.world.entity.Entity#saveWithoutId(CompoundTag) + * @see net.minecraft.server.level.ServerPlayer#addAdditionalSaveData(CompoundTag) + * @see net.minecraft.world.entity.player.Player#addAdditionalSaveData(CompoundTag) + * @see net.minecraft.world.entity.LivingEntity#addAdditionalSaveData(CompoundTag) + */ + private static final Set RESET_TAGS = Set.of( + // Entity#saveWithoutId(CompoundTag) + "CustomName", + "CustomNameVisible", + "Silent", + "NoGravity", + "Glowing", + "TicksFrozen", + "HasVisualFire", + "Tags", + "Passengers", + // ServerPlayer#addAdditionalSaveData(CompoundTag) + // Intentional omissions to prevent mount loss: Attach, Entity, and RootVehicle + "warden_spawn_tracker", + "enteredNetherPosition", + "SpawnX", + "SpawnY", + "SpawnZ", + "SpawnForced", + "SpawnAngle", + "SpawnDimension", + "raid_omen_position", + "ender_pearls", + // Player#addAdditionalSaveData(CompoundTag) + "ShoulderEntityLeft", + "ShoulderEntityRight", + "LastDeathLocation", + "current_explosion_impact_pos", + // LivingEntity#addAdditionalSaveData(CompoundTag) + "active_effects", + "SleepingX", + "SleepingY", + "SleepingZ", + "Brain" + ); + + private final PlayerManager manager; + + OpenPlayer(CraftServer server, ServerPlayer entity, PlayerManager manager) { + super(server, entity); + this.manager = manager; + } + + @Override + public void loadData() { + manager.loadData(getHandle()); + } + + @Override + public void saveData() { + if (OpenEvents.saveCancelled(this)) { + return; + } + + ServerPlayer player = this.getHandle(); + // See net.minecraft.world.level.storage.PlayerDataStorage#save(EntityHuman) + try { + PlayerDataStorage worldNBTStorage = player.server.getPlayerList().playerIo; + + CompoundTag oldData = isOnline() ? null : worldNBTStorage.load(player.getName().getString(), player.getStringUUID()).orElse(null); + CompoundTag playerData = getWritableTag(oldData); + playerData = player.saveWithoutId(playerData); + setExtraData(playerData); + + if (oldData != null) { + // Revert certain special data values when offline. + revertSpecialValues(playerData, oldData); + } + + Path playerDataDir = worldNBTStorage.getPlayerDir().toPath(); + Path tempFile = Files.createTempFile(playerDataDir, player.getStringUUID() + "-", ".dat"); + NbtIo.writeCompressed(playerData, tempFile); + Path dataFile = playerDataDir.resolve(player.getStringUUID() + ".dat"); + Path backupFile = playerDataDir.resolve(player.getStringUUID() + ".dat_old"); + Util.safeReplaceFile(dataFile, tempFile, backupFile); + } catch (Exception e) { + LogUtils.getLogger().warn("Failed to save player data for {}: {}", player.getScoreboardName(), e); + } + } + + @Contract("null -> new") + private @NotNull CompoundTag getWritableTag(@Nullable CompoundTag oldData) { + if (oldData == null) { + return new CompoundTag(); + } + + // Copy old data. This is a deep clone, so operating on it should be safe. + oldData = oldData.copy(); + + // Remove vanilla/server data that is not written every time. + oldData.getAllKeys() + .removeIf(key -> RESET_TAGS.contains(key) || key.startsWith("Bukkit")); + + return oldData; + } + + private void revertSpecialValues(@NotNull CompoundTag newData, @NotNull CompoundTag oldData) { + // Revert automatic updates to play timestamps. + copyValue(oldData, newData, "bukkit", "lastPlayed", NumericTag.class); + copyValue(oldData, newData, "Paper", "LastSeen", NumericTag.class); + copyValue(oldData, newData, "Paper", "LastLogin", NumericTag.class); + } + + private void copyValue( + @NotNull CompoundTag source, + @NotNull CompoundTag target, + @NotNull String container, + @NotNull String key, + @SuppressWarnings("SameParameterValue") @NotNull Class tagType) { + CompoundTag oldContainer = getTag(source, container, CompoundTag.class); + CompoundTag newContainer = getTag(target, container, CompoundTag.class); + + // New container being null means the server implementation doesn't store this data. + if (newContainer == null) { + return; + } + + // If old tag exists, copy it to new location, removing otherwise. + setTag(newContainer, key, getTag(oldContainer, key, tagType)); + } + + private @Nullable T getTag( + @Nullable CompoundTag container, + @NotNull String key, + @NotNull Class dataType) { + if (container == null) { + return null; + } + Tag value = container.get(key); + if (value == null || !dataType.isAssignableFrom(value.getClass())) { + return null; + } + return dataType.cast(value); + } + + private void setTag( + @NotNull CompoundTag container, + @NotNull String key, + @Nullable T data) { + if (data == null) { + container.remove(key); + } else { + container.put(key, data); + } + } + +} diff --git a/internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/player/PlayerManager.java b/internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/player/PlayerManager.java new file mode 100644 index 00000000..2bc534ec --- /dev/null +++ b/internal/v1_21_R2/src/main/java/com/lishid/openinv/internal/v1_21_R2/player/PlayerManager.java @@ -0,0 +1,267 @@ +package com.lishid.openinv.internal.v1_21_R2.player; + +import com.lishid.openinv.internal.ISpecialInventory; +import com.lishid.openinv.internal.v1_21_R2.container.OpenEnderChest; +import com.lishid.openinv.internal.v1_21_R2.container.OpenInventory; +import com.mojang.authlib.GameProfile; +import com.mojang.serialization.Dynamic; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtOps; +import net.minecraft.network.chat.Component; +import net.minecraft.network.protocol.game.ClientboundOpenScreenPacket; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ClientInformation; +import net.minecraft.server.level.ParticleStatus; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.player.ChatVisiblity; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.dimension.DimensionType; +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; +import org.bukkit.Server; +import org.bukkit.World; +import org.bukkit.craftbukkit.v1_21_R2.CraftServer; +import org.bukkit.craftbukkit.v1_21_R2.CraftWorld; +import org.bukkit.craftbukkit.v1_21_R2.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_21_R2.event.CraftEventFactory; +import org.bukkit.entity.Player; +import org.bukkit.inventory.InventoryView; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.lang.reflect.Field; +import java.util.UUID; +import java.util.logging.Logger; + +public class PlayerManager implements com.lishid.openinv.internal.PlayerManager { + + private static boolean paper; + + static { + try { + Class.forName("io.papermc.paper.configuration.Configuration"); + paper = true; + } catch (ClassNotFoundException ignored) { + paper = false; + } + } + + private final @NotNull Logger logger; + private @Nullable Field bukkitEntity; + + public PlayerManager(@NotNull Logger logger) { + this.logger = logger; + try { + bukkitEntity = Entity.class.getDeclaredField("bukkitEntity"); + } catch (NoSuchFieldException e) { + logger.warning("Unable to obtain field to inject custom save process - certain player data may be lost when saving!"); + logger.log(java.util.logging.Level.WARNING, e.getMessage(), e); + bukkitEntity = null; + } + } + + public static @NotNull ServerPlayer getHandle(final Player player) { + if (player instanceof CraftPlayer) { + return ((CraftPlayer) player).getHandle(); + } + + Server server = player.getServer(); + ServerPlayer nmsPlayer = null; + + if (server instanceof CraftServer) { + nmsPlayer = ((CraftServer) server).getHandle().getPlayer(player.getUniqueId()); + } + + if (nmsPlayer == null) { + // Could use reflection to examine fields, but it's honestly not worth the bother. + throw new RuntimeException("Unable to fetch EntityPlayer from Player implementation " + player.getClass().getName()); + } + + return nmsPlayer; + } + + @Override + public @Nullable Player loadPlayer(@NotNull final OfflinePlayer offline) { + MinecraftServer server = ((CraftServer) Bukkit.getServer()).getServer(); + ServerLevel worldServer = server.getLevel(Level.OVERWORLD); + + if (worldServer == null) { + return null; + } + + // Create a new ServerPlayer. + ServerPlayer entity = createNewPlayer(server, worldServer, offline); + + // Stop listening for advancement progression - if this is not cleaned up, loading causes a memory leak. + entity.getAdvancements().stopListening(); + + // Try to load the player's data. + if (loadData(entity)) { + // If data is loaded successfully, return the Bukkit entity. + return entity.getBukkitEntity(); + } + + return null; + } + + private @NotNull ServerPlayer createNewPlayer( + @NotNull MinecraftServer server, + @NotNull ServerLevel worldServer, + @NotNull final OfflinePlayer offline) { + // See net.minecraft.server.players.PlayerList#canPlayerLogin(ServerLoginPacketListenerImpl, GameProfile) + // See net.minecraft.server.network.ServerLoginPacketListenerImpl#handleHello(ServerboundHelloPacket) + GameProfile profile = new GameProfile(offline.getUniqueId(), + offline.getName() != null ? offline.getName() : offline.getUniqueId().toString()); + + ClientInformation dummyInfo = new ClientInformation( + "en_us", + 1, // Reduce distance just in case. + ChatVisiblity.HIDDEN, // Don't accept chat. + false, + ServerPlayer.DEFAULT_MODEL_CUSTOMIZATION, + ServerPlayer.DEFAULT_MAIN_HAND, + true, + false, // Don't list in player list (not that this player is in the list anyway). + ParticleStatus.MINIMAL + ); + + ServerPlayer entity = new ServerPlayer(server, worldServer, profile, dummyInfo); + + try { + injectPlayer(entity); + } catch (IllegalAccessException e) { + logger.log( + java.util.logging.Level.WARNING, + e, + () -> "Unable to inject ServerPlayer, certain player data may be lost when saving!"); + } + + return entity; + } + + boolean loadData(@NotNull ServerPlayer player) { + // See CraftPlayer#loadData + CompoundTag loadedData = player.server.getPlayerList().playerIo.load(player).orElse(null); + + if (loadedData == null) { + // Exceptions with loading are logged by Mojang. + return false; + } + + // Read basic data into the player. + player.load(loadedData); + // Also read "extra" data. + player.readAdditionalSaveData(loadedData); + // Game type settings are also loaded separately. + player.loadGameTypes(loadedData); + + if (paper) { + // Paper: world is not loaded by ServerPlayer#load(CompoundTag). + parseWorld(player, loadedData); + } + + return true; + } + + private void parseWorld(@NotNull ServerPlayer player, @NotNull CompoundTag loadedData) { + // See PlayerList#placeNewPlayer + World bukkitWorld; + if (loadedData.contains("WorldUUIDMost") && loadedData.contains("WorldUUIDLeast")) { + // Modern Bukkit world. + bukkitWorld = Bukkit.getServer().getWorld(new UUID(loadedData.getLong("WorldUUIDMost"), loadedData.getLong("WorldUUIDLeast"))); + } else if (loadedData.contains("world", net.minecraft.nbt.Tag.TAG_STRING)) { + // Legacy Bukkit world. + bukkitWorld = Bukkit.getServer().getWorld(loadedData.getString("world")); + } else { + // Vanilla player data. + DimensionType.parseLegacy(new Dynamic<>(NbtOps.INSTANCE, loadedData.get("Dimension"))) + .resultOrPartial(logger::warning) + .map(player.server::getLevel) + // If ServerLevel exists, set, otherwise move to spawn. + .ifPresentOrElse(player::setServerLevel, () -> player.spawnIn(null)); + return; + } + if (bukkitWorld == null) { + player.spawnIn(null); + return; + } + player.setServerLevel(((CraftWorld) bukkitWorld).getHandle()); + } + + private void injectPlayer(ServerPlayer player) throws IllegalAccessException { + if (bukkitEntity == null) { + return; + } + + bukkitEntity.setAccessible(true); + + bukkitEntity.set(player, new OpenPlayer(player.server.server, player, this)); + } + + @Override + public @NotNull Player inject(@NotNull Player player) { + try { + ServerPlayer nmsPlayer = getHandle(player); + if (nmsPlayer.getBukkitEntity() instanceof OpenPlayer openPlayer) { + return openPlayer; + } + injectPlayer(nmsPlayer); + return nmsPlayer.getBukkitEntity(); + } catch (IllegalAccessException e) { + logger.log( + java.util.logging.Level.WARNING, + e, + () -> "Unable to inject ServerPlayer, certain player data may be lost when saving!"); + return player; + } + } + + @Override + public @Nullable InventoryView openInventory(@NotNull Player bukkitPlayer, @NotNull ISpecialInventory inventory, boolean viewOnly) { + ServerPlayer player = getHandle(bukkitPlayer); + + if (player.connection == null) { + return null; + } + + // See net.minecraft.server.level.ServerPlayer#openMenu(MenuProvider) + AbstractContainerMenu menu; + Component title; + if (inventory instanceof OpenInventory playerInv) { + menu = playerInv.createMenu(player, player.nextContainerCounter(), viewOnly); + title = playerInv.getTitle(player); + } else if (inventory instanceof OpenEnderChest enderChest) { + menu = enderChest.createMenu(player, player.nextContainerCounter(), viewOnly); + title = enderChest.getTitle(); + } else { + return null; + } + + // Should never happen, player is a ServerPlayer with an active connection. + if (menu == null) { + return null; + } + + // Set up title. Title can only be set once for a menu, and is set during the open process. + // Further title changes are a hack where the client is sent a "new" inventory with the same ID, + // resulting in a title change but no other state modifications (like cursor position). + menu.setTitle(title); + + menu = CraftEventFactory.callInventoryOpenEvent(player, menu, false); + + // Menu is null if event is cancelled. + if (menu == null) { + return null; + } + + player.containerMenu = menu; + player.connection.send(new ClientboundOpenScreenPacket(menu.containerId, menu.getType(), menu.getTitle())); + player.initMenu(menu); + + return menu.getBukkitView(); + } + +} diff --git a/plugin/pom.xml b/plugin/pom.xml index 472cb848..43e33471 100644 --- a/plugin/pom.xml +++ b/plugin/pom.xml @@ -46,19 +46,13 @@ com.lishid - openinvadapter1_21_R1 + openinvadapter1_21_R2 5.1.3-SNAPSHOT compile com.lishid - openinvadapter1_20_R4 - 5.1.3-SNAPSHOT - compile - - - com.lishid - openinvadapter1_20_R3 + openinvadapter1_21_R1 5.1.3-SNAPSHOT compile diff --git a/plugin/src/main/java/com/lishid/openinv/OpenInv.java b/plugin/src/main/java/com/lishid/openinv/OpenInv.java index 45a2129f..3c5e59e1 100644 --- a/plugin/src/main/java/com/lishid/openinv/OpenInv.java +++ b/plugin/src/main/java/com/lishid/openinv/OpenInv.java @@ -16,8 +16,6 @@ package com.lishid.openinv; -import com.github.jikoo.planarwrappers.util.version.BukkitVersions; -import com.github.jikoo.planarwrappers.util.version.Version; import com.lishid.openinv.command.ContainerSettingCommand; import com.lishid.openinv.command.OpenInvCommand; import com.lishid.openinv.command.SearchContainerCommand; @@ -28,7 +26,6 @@ import com.lishid.openinv.internal.ISpecialInventory; import com.lishid.openinv.internal.ISpecialPlayerInventory; import com.lishid.openinv.listener.ContainerListener; -import com.lishid.openinv.listener.LegacyInventoryListener; import com.lishid.openinv.listener.ToggleListener; import com.lishid.openinv.util.AccessEqualMode; import com.lishid.openinv.util.InternalAccessor; @@ -144,10 +141,6 @@ public void onEnable() { private void registerEvents() { PluginManager pluginManager = this.getServer().getPluginManager(); - // Legacy: extra listener for permission handling and self-view issue prevention. - if (BukkitVersions.MINECRAFT.lessThan(Version.of(1, 21))) { - pluginManager.registerEvents(new LegacyInventoryListener(this, config), this); - } pluginManager.registerEvents(playerLoader, this); pluginManager.registerEvents(inventoryManager, this); pluginManager.registerEvents(new ContainerListener(accessor, languageManager), this); diff --git a/plugin/src/main/java/com/lishid/openinv/listener/LegacyInventoryListener.java b/plugin/src/main/java/com/lishid/openinv/listener/LegacyInventoryListener.java deleted file mode 100644 index 38728dad..00000000 --- a/plugin/src/main/java/com/lishid/openinv/listener/LegacyInventoryListener.java +++ /dev/null @@ -1,234 +0,0 @@ -package com.lishid.openinv.listener; - -import com.google.errorprone.annotations.Keep; -import com.lishid.openinv.internal.ISpecialEnderChest; -import com.lishid.openinv.internal.ISpecialInventory; -import com.lishid.openinv.internal.ISpecialPlayerInventory; -import com.lishid.openinv.util.AccessEqualMode; -import com.lishid.openinv.util.InventoryAccess; -import com.lishid.openinv.util.Permissions; -import com.lishid.openinv.util.config.Config; -import org.bukkit.entity.HumanEntity; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.Listener; -import org.bukkit.event.inventory.InventoryAction; -import org.bukkit.event.inventory.InventoryClickEvent; -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.bukkit.plugin.Plugin; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; - -/** - * A listener used to enable functionality and prevent issues on versions < 1.21. - */ -public class LegacyInventoryListener implements Listener { - - private final @NotNull Plugin plugin; - private final @NotNull Config config; - - public LegacyInventoryListener(@NotNull Plugin plugin, @NotNull Config config) { - this.plugin = plugin; - this.config = config; - } - - @Keep - @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) - private void onInventoryClick(@NotNull final InventoryClickEvent event) { - if (handleInventoryInteract(event)) { - return; - } - - // Safe cast - has to be a player to be the holder of a special player inventory. - Player player = (Player) event.getWhoClicked(); - - if (event.getAction() != InventoryAction.MOVE_TO_OTHER_INVENTORY) { - return; - } - - // Extra handling for MOVE_TO_OTHER_INVENTORY - apparently Mojang no longer removes the item from the target - // inventory prior to adding it to existing stacks. - ItemStack currentItem = event.getCurrentItem(); - if (currentItem == null) { - // Other plugin doing some sort of handling (would be NOTHING for null item otherwise), ignore. - return; - } - - ItemStack clone = currentItem.clone(); - event.setCurrentItem(null); - - // Complete add action in same tick after event completion. - this.plugin.getServer().getScheduler().runTask(this.plugin, () -> player.getInventory().addItem(clone)); - } - - @Keep - @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) - private void onInventoryDrag(@NotNull final InventoryDragEvent event) { - if (handleInventoryInteract(event)) { - return; - } - - InventoryView view = event.getView(); - - if (view.getCursor() == null) { - return; - } - - 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 -> 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 = 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; - } - - final ItemStack lost = view.getCursor().clone(); - lost.setAmount(overlapLosses); - - // Re-add the lost items in the same tick after the event has completed. - plugin.getServer().getScheduler().runTask(plugin, () -> { - InventoryView currentOpen = event.getWhoClicked().getOpenInventory(); - - if (!currentOpen.equals(view)) { - event.getWhoClicked().getWorld().dropItem(event.getWhoClicked().getLocation(), lost).setPickupDelay(0); - return; - } - - ItemStack cursor = currentOpen.getCursor(); - - if (cursor == null) { - currentOpen.setCursor(lost); - } else if (lost.isSimilar(cursor)) { - cursor.setAmount(cursor.getAmount() + lost.getAmount()); - currentOpen.setCursor(cursor); - } else { - event.getWhoClicked().getWorld().dropItem(event.getWhoClicked().getLocation(), lost).setPickupDelay(0); - } - }); - } - - 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 the holder's own inventory - */ - private boolean handleInventoryInteract(@NotNull final InventoryInteractEvent event) { - HumanEntity viewer = event.getWhoClicked(); - - Inventory inventory = event.getView().getTopInventory(); - ISpecialInventory backing = InventoryAccess.getInventory(inventory); - - if (backing == null) { - return true; - } - - Permissions edit; - HumanEntity target = backing.getPlayer(); - boolean ownContainer = viewer.equals(target); - if (backing instanceof ISpecialPlayerInventory) { - edit = ownContainer ? Permissions.INVENTORY_EDIT_SELF : Permissions.INVENTORY_EDIT_OTHER; - } else if (backing instanceof ISpecialEnderChest) { - edit = ownContainer ? Permissions.ENDERCHEST_EDIT_SELF : Permissions.ENDERCHEST_OPEN_OTHER; - } else { - // Unknown implementation. - return true; - } - - if (!edit.hasPermission(viewer)) { - event.setCancelled(true); - return true; - } - - // If access ties aren't view-only mode, don't bother with permission checks. - if (config.getAccessEqualMode() != AccessEqualMode.VIEW) { - return !ownContainer || !(backing instanceof ISpecialPlayerInventory); - } - - for (int level = 4; level > 0; --level) { - String permission = "openinv.access.level." + level; - // If the target doesn't have this access level... - if (!target.hasPermission(permission)) { - // If the viewer does have the access level, all good. - if (viewer.hasPermission(permission)) { - break; - } - // Otherwise check next access level. - continue; - } - - // Either the viewer lacks access (which shouldn't be possible) or this is a tie. View-only. - event.setCancelled(true); - return true; - } - - return !ownContainer || !(backing instanceof ISpecialPlayerInventory); - } - - private static 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; - } - -} diff --git a/plugin/src/main/java/com/lishid/openinv/util/InternalAccessor.java b/plugin/src/main/java/com/lishid/openinv/util/InternalAccessor.java index 114399ab..4214eb7b 100644 --- a/plugin/src/main/java/com/lishid/openinv/util/InternalAccessor.java +++ b/plugin/src/main/java/com/lishid/openinv/util/InternalAccessor.java @@ -40,13 +40,10 @@ public class InternalAccessor { public InternalAccessor(@NotNull Logger logger, @NotNull LanguageManager lang) { try { - if (BukkitVersions.MINECRAFT.equals(Version.of(1, 21, 1)) - || BukkitVersions.MINECRAFT.equals(Version.of(1, 21))) { + if (BukkitVersions.MINECRAFT.equals(Version.of(1, 21, 3))) { + internal = new com.lishid.openinv.internal.v1_21_R2.InternalAccessor(logger, lang); + } else if (BukkitVersions.MINECRAFT.equals(Version.of(1, 21, 1))) { internal = new com.lishid.openinv.internal.v1_21_R1.InternalAccessor(logger, lang); - } else if (BukkitVersions.MINECRAFT.equals(Version.of(1, 20, 4))) { - internal = new com.lishid.openinv.internal.v1_20_R3.InternalAccessor(logger, lang); - } else if (BukkitVersions.MINECRAFT.equals(Version.of(1, 20, 6))) { - internal = new com.lishid.openinv.internal.v1_20_R4.InternalAccessor(logger, lang); } if (internal != null) { InventoryAccess.setProvider(internal::get); @@ -121,6 +118,12 @@ public String getReleasesLink() { if (BukkitVersions.MINECRAFT.lessThanOrEqual(Version.of(1, 20, 3))) { // 1.20.2, 1.20.3 return "https://github.com/Jikoo/OpenInv/releases/tag/4.4.3"; } + if (BukkitVersions.MINECRAFT.equals(Version.of(1, 20, 5))) { // 1.20.5 + return "Unsupported; upgrade to 1.20.6: https://github.com/Jikoo/OpenInv/releases/tag/5.1.2"; + } + if (BukkitVersions.MINECRAFT.lessThanOrEqual(Version.of(1, 21))) { // 1.20.4, 1.20.6, 1.21 + return "https://github.com/Jikoo/OpenInv/releases/tag/5.1.2"; + } return "https://github.com/Jikoo/OpenInv/releases"; } diff --git a/pom.xml b/pom.xml index feb14671..7964b278 100644 --- a/pom.xml +++ b/pom.xml @@ -43,9 +43,8 @@ api addon/togglepersist common + internal/v1_21_R2 internal/v1_21_R1 - internal/v1_20_R4 - internal/v1_20_R3 plugin @@ -78,13 +77,13 @@ annotations org.jetbrains provided - 25.0.0 + 26.0.1 spigot-api org.spigotmc provided - 1.20.4-R0.1-SNAPSHOT + 1.21.1-R0.1-SNAPSHOT openinvapi