diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 00000000..5ab4dca3
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,10 @@
+version: 2
+updates:
+ - package-ecosystem: "github-actions"
+ directory: "/"
+ schedule:
+ interval: "monthly"
+ - package-ecosystem: "maven"
+ directory: "/"
+ schedule:
+ interval: "monthly"
diff --git a/.github/workflows/automerge_dependabot.yml b/.github/workflows/automerge_dependabot.yml
new file mode 100644
index 00000000..a79866cd
--- /dev/null
+++ b/.github/workflows/automerge_dependabot.yml
@@ -0,0 +1,55 @@
+name: Auto-merge Dependabot PRs
+
+on:
+ workflow_run:
+ workflows: [ "Pull Request" ]
+ types: [ completed ]
+
+jobs:
+ merge-dependabot:
+ if: "github.actor == 'dependabot[bot]'
+ && github.event.workflow_run.event == 'pull_request'
+ && github.event.workflow_run.conclusion == 'success'"
+ runs-on: ubuntu-latest
+ steps:
+ # Note: this is directly from GitHub's example for using data from a triggering workflow:
+ # https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#using-data-from-the-triggering-workflow
+ - name: 'Download artifact'
+ uses: actions/github-script@v6
+ with:
+ script: |
+ let allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ run_id: context.payload.workflow_run.id,
+ });
+ let matchArtifact = allArtifacts.data.artifacts.filter((artifact) => {
+ return artifact.name == "pr_number"
+ })[0];
+ let download = await github.rest.actions.downloadArtifact({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ artifact_id: matchArtifact.id,
+ archive_format: 'zip',
+ });
+ let fs = require('fs');
+ fs.writeFileSync(`${process.env.GITHUB_WORKSPACE}/pr_number.zip`, Buffer.from(download.data));
+
+ # This might be a useless use of cat, but I'm not sure what shell Actions is going to be running.
+ - name: Add Pull Number Variable
+ run: |-
+ unzip pr_number.zip
+ echo "PR_NUMBER=$(cat pr_number)" >> "$GITHUB_ENV"
+
+ - name: Approve
+ uses: hmarr/auto-approve-action@v3.2.1
+ with:
+ github-token: "${{ secrets.GITHUB_TOKEN }}"
+ pull-request-number: "${{ env.PR_NUMBER }}"
+ - name: Merge
+ uses: pascalgn/automerge-action@v0.15.6
+ env:
+ GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
+ MERGE_LABELS: "dependencies,java"
+ MERGE_METHOD: "squash"
+ PULL_REQUEST: "${{ env.PR_NUMBER }}"
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index a73e107f..b382ede8 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -2,32 +2,26 @@ name: OpenInv CI
on:
push:
- create:
- types: [tag]
- pull_request_target:
+ branches:
+ - '**'
+ tags-ignore:
+ - '**'
+ # Enable running CI via other Actions, i.e. for drafting releases and handling PRs.
+ workflow_call:
jobs:
build:
runs-on: ubuntu-latest
steps:
- - name: Checkout Code
- uses: actions/checkout@v2
+ - uses: actions/checkout@v4
- - name: Set Up Java
- uses: actions/setup-java@v1
+ - uses: actions/setup-java@v3
with:
- java-version: 1.8
+ distribution: 'temurin'
+ java-version: '17'
+ cache: 'maven'
- # Use cache to speed up build
- - name: Cache Maven Repo
- uses: actions/cache@v2
- id: cache
- with:
- path: ~/.m2/repository
- key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
-
- # Install Spigot dependencies.
- # This script uses Maven to check all required installations and ensure that they are present.
+ # Install Spigot dependencies if necessary.
- name: Install Spigot Dependencies
run: . scripts/install_spigot_dependencies.sh
@@ -37,51 +31,13 @@ jobs:
# Upload artifacts
- name: Upload Distributable Jar
id: upload-final
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v3
with:
name: dist
path: ./target/OpenInv.jar
- name: Upload API Jar
id: upload-api
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v3
with:
name: api
path: ./api/target/openinvapi*.jar
-
- release:
- name: Create Github Release
- needs: [ build ]
- if: github.event_name == 'create' && github.event.ref_type == 'tag'
- runs-on: ubuntu-latest
- steps:
- - name: Checkout Code
- uses: actions/checkout@v2
-
- - name: Download Artifacts
- uses: actions/download-artifact@v2
-
- - name: Generate changelog
- run: . scripts/generate_changelog.sh
-
- - name: Create Release
- id: create-release
- uses: actions/create-release@v1
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- with:
- tag_name: ${{ github.ref }}
- release_name: Release ${{ github.ref }}
- body: ${{ env.GENERATED_CHANGELOG }}
- draft: true
- prerelease: false
-
- - name: Upload Release Asset
- id: upload-release-asset
- uses: actions/upload-release-asset@v1.0.2
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- with:
- upload_url: ${{ steps.create-release.outputs.upload_url }}
- asset_path: ./OpenInv.jar
- asset_name: OpenInv.jar
- asset_content_type: application/java-archive
\ No newline at end of file
diff --git a/.github/workflows/draft_release.yml b/.github/workflows/draft_release.yml
new file mode 100644
index 00000000..d9b480f8
--- /dev/null
+++ b/.github/workflows/draft_release.yml
@@ -0,0 +1,39 @@
+name: Draft Github Release
+
+on:
+ push:
+ tags:
+ - '**'
+
+jobs:
+ run-ci:
+ uses: Jikoo/OpenInv/.github/workflows/ci.yml@master
+ draft-release:
+ needs: [ run-ci ]
+ runs-on: ubuntu-latest
+ steps:
+ # Fetch all history - used to assemble changelog.
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - name: Set Release Variables
+ run: bash ./scripts/set_release_env.sh
+
+ - name: Download Artifact
+ uses: actions/download-artifact@v3
+ with:
+ name: dist
+ path: dist
+
+ - name: Create Release
+ id: create-release
+ uses: softprops/action-gh-release@v0.1.15
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ with:
+ name: ${{ env.VERSIONED_NAME }}
+ body: ${{ env.GENERATED_CHANGELOG }}
+ draft: true
+ prerelease: false
+ files: ./dist/OpenInv.jar
diff --git a/.github/workflows/external_release.yml b/.github/workflows/external_release.yml
new file mode 100644
index 00000000..e748656b
--- /dev/null
+++ b/.github/workflows/external_release.yml
@@ -0,0 +1,37 @@
+name: Release to CurseForge
+
+on:
+ release:
+ types: [ released ]
+
+jobs:
+ curseforge_release:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout Code
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - name: Fetch Github Release Asset
+ uses: dsaltares/fetch-gh-release-asset@1.1.1
+ with:
+ token: ${{ secrets.GITHUB_TOKEN }}
+ version: ${{ github.event.release.id }}
+ file: OpenInv.jar
+
+ - name: Set CurseForge Variables
+ run: . scripts/set_curseforge_env.sh "${{ github.event.release.body }}"
+
+ - name: Create CurseForge Release
+ uses: itsmeow/curseforge-upload@v3
+ with:
+ token: "${{ secrets.CURSEFORGE_TOKEN }}"
+ project_id: 31432
+ game_endpoint: minecraft
+ file_path: ./OpenInv.jar
+ display_name: "${{ github.event.release.name }}"
+ game_versions: "${{ env.CURSEFORGE_MINECRAFT_VERSIONS }}"
+ release_type: release
+ changelog_type: markdown
+ changelog: "${{ env.CURSEFORGE_CHANGELOG }}"
diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml
new file mode 100644
index 00000000..869fb27b
--- /dev/null
+++ b/.github/workflows/pull_request.yml
@@ -0,0 +1,22 @@
+name: Pull Request
+
+on:
+ pull_request:
+
+jobs:
+ run-ci:
+ uses: Jikoo/OpenInv/.github/workflows/ci.yml@master
+ store-dependabot-pr-data:
+ if: "github.actor == 'dependabot[bot]' && github.event_name == 'pull_request'"
+ runs-on: ubuntu-latest
+ steps:
+ # Note: this is directly from GitHub's example for using data from a triggering workflow:
+ # https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#using-data-from-the-triggering-workflow
+ - name: Store Pull Number
+ run: |
+ mkdir -p ./pr
+ echo ${{ github.event.number }} > ./pr/pr_number
+ - uses: actions/upload-artifact@v3
+ with:
+ name: pr_number
+ path: pr/
diff --git a/README.MD b/README.MD
index 66c183ff..b8ffb02d 100644
--- a/README.MD
+++ b/README.MD
@@ -15,48 +15,7 @@ OpenInv is a [Bukkit plugin](https://dev.bukkit.org/bukkit-plugins/openinv/) whi
- **AnyContainer**: Open containers, even if blocked by ocelots or blocks.
## Commands
-
-
- Command |
- Aliases |
- Description |
-
-
- /openinv [player] |
- oi, inv, open |
- Open a player's inventory. If unspecified, will select last player opened or own if none opened previously. |
-
-
- /openender [player] |
- oe |
- Open a player's ender chest. If unspecified, will select last player opened or own if none opened previously. |
-
-
- /searchinv <item> [minAmount] |
- si |
- Lists all online players that have a certain item in their inventory. |
-
-
- /searchender <item> [minAmount] |
- se |
- Lists all online players that have a certain item in their ender chest. |
-
-
- /searchenchant <[enchantment] [MinLevel]> |
- searchenchants |
- Lists all online players with a specific enchantment. |
-
-
- /anycontainer [check] |
- ac, anychest |
- Check or toggle the AnyContainer function, allowing opening blocked containers. |
-
-
- /silentcontainer [check] |
- sc, silentchest |
- Check or toggle the SilentContainer function, allowing opening containers silently. |
-
-
+See [the wiki](https://github.com/Jikoo/OpenInv/wiki/Commands).
## Permissions
@@ -143,30 +102,38 @@ OpenInv is a [Bukkit plugin](https://dev.bukkit.org/bukkit-plugins/openinv/) whi
## For Developers
-To compile, the relevant Craftbukkit/Spigot jars must be installed in your local repository using the install plugin.
-Ex: `mvn install:install-file -Dpackaging=jar -Dfile=spigot-1.8-R0.1-SNAPSHOT.jar -DgroupId=org.spigotmc -DartifactId=spigot -Dversion=1.8-R0.1-SNAPSHOT`
-
-To compile for a single version, specify the NMS revision you are targeting: `mvn -pl -am clean install`
-
-To compile for a set of versions, you'll need to use a profile. The only provided profile is `all`. Select a profile using the `-P` argument: `mvn clean package -am -P all`
-For more information, check out the [official Maven guide](http://maven.apache.org/guides/introduction/introduction-to-profiles.html).
-
-The final file is `target/OpenInv.jar`
-
-## License
+### As a Dependency
+The OpenInv API is available via [JitPack](https://jitpack.io/).
+```xml
+
+
+ jitpack.io
+ https://jitpack.io
+
+
+```
+```xml
+
+
+ com.github.jikoo.OpenInv
+ openinvapi
+ ${openinv.version}
+
+
```
-Copyright (C) 2011-2020 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.
+### Compilation
+To compile, the relevant Spigot jars must be installed in the local repository.
+As OpenInv is compiled against Mojang's mappings, you must run BuildTools with the `--remapped` argument:
+`java -jar BuildTools.jar --remapped --rev $serverVersion`
+`$serverVersion` is the version of the server, i.e. `1.18.1`
-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.
+To compile for a single version, specify the module you are targeting:
+`mvn -pl $moduleName -am clean install`
+`$moduleName` is the name of the module, i.e. `internal/v1_18_R1`.
-You should have received a copy of the GNU General Public License
-along with this program. If not, see .
-```
+To compile for a set of versions, use a profile. Select a profile using the `-P` argument:
+`mvn clean package -am -P all`
+The only provided profile is `all`. The final file is `target/OpenInv.jar`
+For more information, check out the [official Maven guide](http://maven.apache.org/guides/introduction/introduction-to-profiles.html).
diff --git a/api/pom.xml b/api/pom.xml
index cc34012f..569ba295 100644
--- a/api/pom.xml
+++ b/api/pom.xml
@@ -1,5 +1,5 @@
-
+
reactor-uberjar
@@ -34,8 +34,18 @@
/
true
-
-
+
+
+
+ META-INF/**
+
+
+
+ false
diff --git a/internal/pom.xml b/internal/pom.xml
deleted file mode 100644
index fd28dce6..00000000
--- a/internal/pom.xml
+++ /dev/null
@@ -1,42 +0,0 @@
-
-
-
- 4.0.0
-
-
- com.lishid
- openinvparent
- 4.1.6-SNAPSHOT
-
-
- openinvinternal
- OpenInvInternal
-
- pom
-
-
-
-
- all
-
- v1_16_R3
-
-
-
-
-
-
diff --git a/internal/v1_16_R3/src/main/java/com/lishid/openinv/internal/v1_16_R3/AnySilentContainer.java b/internal/v1_16_R3/src/main/java/com/lishid/openinv/internal/v1_16_R3/AnySilentContainer.java
deleted file mode 100644
index 5d78617d..00000000
--- a/internal/v1_16_R3/src/main/java/com/lishid/openinv/internal/v1_16_R3/AnySilentContainer.java
+++ /dev/null
@@ -1,343 +0,0 @@
-/*
- * Copyright (C) 2011-2020 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_16_R3;
-
-import com.lishid.openinv.OpenInv;
-import com.lishid.openinv.internal.IAnySilentContainer;
-import java.lang.reflect.Field;
-import net.minecraft.server.v1_16_R3.Block;
-import net.minecraft.server.v1_16_R3.BlockBarrel;
-import net.minecraft.server.v1_16_R3.BlockChest;
-import net.minecraft.server.v1_16_R3.BlockChestTrapped;
-import net.minecraft.server.v1_16_R3.BlockPosition;
-import net.minecraft.server.v1_16_R3.BlockPropertyChestType;
-import net.minecraft.server.v1_16_R3.BlockShulkerBox;
-import net.minecraft.server.v1_16_R3.ChatMessage;
-import net.minecraft.server.v1_16_R3.Container;
-import net.minecraft.server.v1_16_R3.ContainerChest;
-import net.minecraft.server.v1_16_R3.Containers;
-import net.minecraft.server.v1_16_R3.EntityHuman;
-import net.minecraft.server.v1_16_R3.EntityPlayer;
-import net.minecraft.server.v1_16_R3.EnumGamemode;
-import net.minecraft.server.v1_16_R3.IBlockData;
-import net.minecraft.server.v1_16_R3.IChatBaseComponent;
-import net.minecraft.server.v1_16_R3.ITileInventory;
-import net.minecraft.server.v1_16_R3.InventoryEnderChest;
-import net.minecraft.server.v1_16_R3.InventoryLargeChest;
-import net.minecraft.server.v1_16_R3.PlayerInteractManager;
-import net.minecraft.server.v1_16_R3.PlayerInventory;
-import net.minecraft.server.v1_16_R3.TileEntity;
-import net.minecraft.server.v1_16_R3.TileEntityChest;
-import net.minecraft.server.v1_16_R3.TileEntityEnderChest;
-import net.minecraft.server.v1_16_R3.TileEntityLootable;
-import net.minecraft.server.v1_16_R3.TileInventory;
-import net.minecraft.server.v1_16_R3.World;
-import org.bukkit.Material;
-import org.bukkit.Statistic;
-import org.bukkit.block.Barrel;
-import org.bukkit.block.BlockFace;
-import org.bukkit.block.BlockState;
-import org.bukkit.block.EnderChest;
-import org.bukkit.block.ShulkerBox;
-import org.bukkit.block.data.BlockData;
-import org.bukkit.block.data.Directional;
-import org.bukkit.block.data.type.Chest;
-import org.bukkit.entity.Cat;
-import org.bukkit.entity.Player;
-import org.bukkit.inventory.InventoryView;
-import org.bukkit.util.BoundingBox;
-import org.jetbrains.annotations.NotNull;
-
-public class AnySilentContainer implements IAnySilentContainer {
-
- private Field playerInteractManagerGamemode;
-
- public AnySilentContainer() {
- try {
- this.playerInteractManagerGamemode = PlayerInteractManager.class.getDeclaredField("gamemode");
- this.playerInteractManagerGamemode.setAccessible(true);
- } catch (NoSuchFieldException | SecurityException e) {
- System.err.println("[OpenInv] Unable to directly write player gamemode! SilentChest will fail.");
- e.printStackTrace();
- }
- }
-
- @Override
- public boolean isAnySilentContainer(@NotNull final org.bukkit.block.Block bukkitBlock) {
- if (bukkitBlock.getType() == Material.ENDER_CHEST) {
- return true;
- }
- BlockState state = bukkitBlock.getState();
- return state instanceof org.bukkit.block.Chest
- || state instanceof org.bukkit.block.ShulkerBox
- || state instanceof org.bukkit.block.Barrel;
- }
-
- @Override
- public boolean isAnyContainerNeeded(@NotNull final Player p, @NotNull final org.bukkit.block.Block block) {
- BlockState blockState = block.getState();
-
- // Barrels do not require AnyContainer.
- if (blockState instanceof Barrel) {
- return false;
- }
-
- // Enderchests require a non-occluding block on top to open.
- if (blockState instanceof EnderChest) {
- return block.getRelative(0, 1, 0).getType().isOccluding();
- }
-
- // Shulker boxes require 1/2 a block clear in the direction they open.
- if (blockState instanceof ShulkerBox) {
- BoundingBox boundingBox = block.getBoundingBox();
- if (boundingBox.getVolume() > 1) {
- // Shulker box is already open.
- return false;
- }
-
- BlockData blockData = block.getBlockData();
- if (!(blockData instanceof Directional)) {
- // Shouldn't be possible. Just in case, demand AnyChest.
- return true;
- }
-
- Directional directional = (Directional) blockData;
- BlockFace face = directional.getFacing();
- boundingBox.shift(face.getDirection());
- // Return whether or not bounding boxes overlap.
- return block.getRelative(face, 1).getBoundingBox().overlaps(boundingBox);
- }
-
- if (!(blockState instanceof org.bukkit.block.Chest)) {
- return false;
- }
-
- if (isBlockedChest(block)) {
- return true;
- }
-
- BlockData blockData = block.getBlockData();
- if (!(blockData instanceof Chest) || ((Chest) blockData).getType() == Chest.Type.SINGLE) {
- return false;
- }
-
- Chest chest = (Chest) blockData;
- int ordinal = (chest.getFacing().ordinal() + 4 + (chest.getType() == Chest.Type.RIGHT ? -1 : 1)) % 4;
- BlockFace relativeFace = BlockFace.values()[ordinal];
- org.bukkit.block.Block relative = block.getRelative(relativeFace);
-
- if (relative.getType() != block.getType()) {
- return false;
- }
-
- BlockData relativeData = relative.getBlockData();
- if (!(relativeData instanceof Chest)) {
- return false;
- }
-
- Chest relativeChest = (Chest) relativeData;
- if (relativeChest.getFacing() != chest.getFacing()
- || relativeChest.getType() != (chest.getType() == Chest.Type.RIGHT ? Chest.Type.LEFT : Chest.Type.RIGHT)) {
- return false;
- }
-
- return isBlockedChest(relative);
- }
-
- private boolean isBlockedChest(org.bukkit.block.Block block) {
- org.bukkit.block.Block relative = block.getRelative(0, 1, 0);
- return relative.getType().isOccluding()
- || block.getWorld().getNearbyEntities(BoundingBox.of(relative), entity -> entity instanceof Cat).size() > 0;
- }
-
- @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;
- }
-
- EntityPlayer player = PlayerDataManager.getHandle(bukkitPlayer);
-
- final World world = player.world;
- final BlockPosition blockPosition = new BlockPosition(bukkitBlock.getX(), bukkitBlock.getY(), bukkitBlock.getZ());
- final TileEntity tile = world.getTileEntity(blockPosition);
-
- if (tile == null) {
- return false;
- }
-
- if (tile instanceof TileEntityEnderChest) {
- // Anychest ender chest. See net.minecraft.server.BlockEnderChest
- InventoryEnderChest enderChest = player.getEnderChest();
- enderChest.a((TileEntityEnderChest) tile);
- player.openContainer(new TileInventory((containerCounter, playerInventory, ignored) -> {
- Containers> containers = PlayerDataManager.getContainers(enderChest.getSize());
- int rows = enderChest.getSize() / 9;
- return new ContainerChest(containers, containerCounter, playerInventory, enderChest, rows);
- }, new ChatMessage("container.enderchest")));
- bukkitPlayer.incrementStatistic(Statistic.ENDERCHEST_OPENED);
- return true;
- }
-
- if (!(tile instanceof ITileInventory)) {
- return false;
- }
-
- ITileInventory tileInventory = (ITileInventory) tile;
- IBlockData blockData = world.getType(blockPosition);
- Block block = blockData.getBlock();
-
- if (block instanceof BlockChest) {
-
- BlockPropertyChestType chestType = blockData.get(BlockChest.c);
-
- if (chestType != BlockPropertyChestType.SINGLE) {
-
- BlockPosition adjacentBlockPosition = blockPosition.shift(BlockChest.h(blockData));
- IBlockData adjacentBlockData = world.getType(adjacentBlockPosition);
-
- if (adjacentBlockData.getBlock() == block) {
-
- BlockPropertyChestType adjacentChestType = adjacentBlockData.get(BlockChest.c);
-
- if (adjacentChestType != BlockPropertyChestType.SINGLE && chestType != adjacentChestType
- && adjacentBlockData.get(BlockChest.FACING) == blockData.get(BlockChest.FACING)) {
-
- TileEntity adjacentTile = world.getTileEntity(adjacentBlockPosition);
-
- if (adjacentTile instanceof TileEntityChest && tileInventory instanceof TileEntityChest) {
- TileEntityChest rightChest = chestType == BlockPropertyChestType.RIGHT ? ((TileEntityChest) tileInventory) : (TileEntityChest) adjacentTile;
- TileEntityChest leftChest = chestType == BlockPropertyChestType.RIGHT ? (TileEntityChest) adjacentTile : ((TileEntityChest) tileInventory);
-
- if (silentchest && (rightChest.lootTable != null || leftChest.lootTable != null)) {
- OpenInv.getPlugin(OpenInv.class).sendSystemMessage(bukkitPlayer, "messages.error.lootNotGenerated");
- return false;
- }
-
- tileInventory = new ITileInventory() {
- public Container createMenu(int containerCounter, PlayerInventory playerInventory, EntityHuman entityHuman) {
- leftChest.d(playerInventory.player);
- rightChest.d(playerInventory.player);
- return ContainerChest.b(containerCounter, playerInventory, new InventoryLargeChest(rightChest, leftChest));
- }
-
- public IChatBaseComponent getScoreboardDisplayName() {
- if (leftChest.hasCustomName()) {
- return leftChest.getScoreboardDisplayName();
- }
- if (rightChest.hasCustomName()) {
- return rightChest.getScoreboardDisplayName();
- }
- return new ChatMessage("container.chestDouble");
- }
- };
- }
- }
- }
- }
-
- if (block instanceof BlockChestTrapped) {
- bukkitPlayer.incrementStatistic(Statistic.TRAPPED_CHEST_TRIGGERED);
- } else {
- bukkitPlayer.incrementStatistic(Statistic.CHEST_OPENED);
- }
- }
-
- if (block instanceof BlockShulkerBox) {
- bukkitPlayer.incrementStatistic(Statistic.SHULKER_BOX_OPENED);
- }
-
- if (block instanceof BlockBarrel) {
- bukkitPlayer.incrementStatistic(Statistic.OPEN_BARREL);
- }
-
- // AnyChest only - SilentChest not active, container unsupported, or unnecessary.
- if (!silentchest || player.playerInteractManager.getGameMode() == EnumGamemode.SPECTATOR) {
- player.openContainer(tileInventory);
- return true;
- }
-
- // SilentChest requires access to setting players' gamemode directly.
- if (this.playerInteractManagerGamemode == null) {
- return false;
- }
-
- if (tile instanceof TileEntityLootable) {
- TileEntityLootable lootable = (TileEntityLootable) tile;
- if (lootable.lootTable != null) {
- OpenInv.getPlugin(OpenInv.class).sendSystemMessage(bukkitPlayer, "messages.error.lootNotGenerated");
- return false;
- }
- }
-
- EnumGamemode gamemode = player.playerInteractManager.getGameMode();
- this.forceGameMode(player, EnumGamemode.SPECTATOR);
- player.openContainer(tileInventory);
- this.forceGameMode(player, gamemode);
- return true;
- }
-
- @Override
- public void deactivateContainer(@NotNull final Player bukkitPlayer) {
- if (this.playerInteractManagerGamemode == null) {
- return;
- }
-
- InventoryView view = bukkitPlayer.getOpenInventory();
- switch (view.getType()) {
- case CHEST:
- case ENDER_CHEST:
- case SHULKER_BOX:
- case BARREL:
- break;
- default:
- return;
- }
-
- EntityPlayer player = PlayerDataManager.getHandle(bukkitPlayer);
-
- EnumGamemode gamemode = player.playerInteractManager.getGameMode();
- this.forceGameMode(player, EnumGamemode.SPECTATOR);
- player.activeContainer.b(player);
- player.activeContainer.a(player, false);
- player.activeContainer.transferTo(player.defaultContainer, player.getBukkitEntity());
- player.activeContainer = player.defaultContainer;
- this.forceGameMode(player, gamemode);
- }
-
- private void forceGameMode(final EntityPlayer player, final EnumGamemode gameMode) {
- if (this.playerInteractManagerGamemode == null) {
- // No need to warn repeatedly, error on startup and lack of function should be enough.
- return;
- }
- try {
- if (!this.playerInteractManagerGamemode.isAccessible()) {
- // Just in case, ensure accessible.
- this.playerInteractManagerGamemode.setAccessible(true);
- }
- this.playerInteractManagerGamemode.set(player.playerInteractManager, gameMode);
- } catch (IllegalArgumentException | IllegalAccessException e) {
- e.printStackTrace();
- }
- }
-
-}
diff --git a/internal/v1_16_R3/src/main/java/com/lishid/openinv/internal/v1_16_R3/OpenPlayer.java b/internal/v1_16_R3/src/main/java/com/lishid/openinv/internal/v1_16_R3/OpenPlayer.java
deleted file mode 100644
index def3f960..00000000
--- a/internal/v1_16_R3/src/main/java/com/lishid/openinv/internal/v1_16_R3/OpenPlayer.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (C) 2011-2021 lishid. All rights reserved.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, version 3.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package com.lishid.openinv.internal.v1_16_R3;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import net.minecraft.server.v1_16_R3.EntityPlayer;
-import net.minecraft.server.v1_16_R3.NBTCompressedStreamTools;
-import net.minecraft.server.v1_16_R3.NBTTagCompound;
-import net.minecraft.server.v1_16_R3.WorldNBTStorage;
-import org.apache.logging.log4j.LogManager;
-import org.bukkit.craftbukkit.v1_16_R3.CraftServer;
-import org.bukkit.craftbukkit.v1_16_R3.entity.CraftPlayer;
-
-public class OpenPlayer extends CraftPlayer {
-
- public OpenPlayer(CraftServer server, EntityPlayer entity) {
- super(server, entity);
- }
-
- @Override
- public void saveData() {
- super.saveData();
- EntityPlayer player = this.getHandle();
- // See net.minecraft.server.WorldNBTStorage#save(EntityPlayer)
- try {
- WorldNBTStorage worldNBTStorage = player.server.getPlayerList().playerFileData;
-
- NBTTagCompound playerData = player.save(new NBTTagCompound());
-
- if (!isOnline()) {
- // Special case: save old vehicle data
- NBTTagCompound oldData = worldNBTStorage.load(player);
-
- if (oldData != null && oldData.hasKeyOfType("RootVehicle", 10)) {
- // See net.minecraft.server.PlayerList#a(NetworkManager, EntityPlayer) and net.minecraft.server.EntityPlayer#b(NBTTagCompound)
- playerData.set("RootVehicle", oldData.getCompound("RootVehicle"));
- }
- }
-
- File file = new File(worldNBTStorage.getPlayerDir(), player.getUniqueIDString() + ".dat.tmp");
- File file1 = new File(worldNBTStorage.getPlayerDir(), player.getUniqueIDString() + ".dat");
-
- NBTCompressedStreamTools.a(playerData, new FileOutputStream(file));
-
- if (file1.exists() && !file1.delete() || !file.renameTo(file1)) {
- LogManager.getLogger().warn("Failed to save player data for {}", player.getDisplayName().getString());
- }
-
- } catch (Exception e) {
- LogManager.getLogger().warn("Failed to save player data for {}", player.getDisplayName().getString());
- }
- }
-
-}
diff --git a/internal/v1_16_R3/src/main/java/com/lishid/openinv/internal/v1_16_R3/SpecialEnderChest.java b/internal/v1_16_R3/src/main/java/com/lishid/openinv/internal/v1_16_R3/SpecialEnderChest.java
deleted file mode 100644
index 7fe8beea..00000000
--- a/internal/v1_16_R3/src/main/java/com/lishid/openinv/internal/v1_16_R3/SpecialEnderChest.java
+++ /dev/null
@@ -1,256 +0,0 @@
-/*
- * Copyright (C) 2011-2020 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_16_R3;
-
-import com.lishid.openinv.internal.ISpecialEnderChest;
-import java.util.List;
-import net.minecraft.server.v1_16_R3.AutoRecipeStackManager;
-import net.minecraft.server.v1_16_R3.ContainerUtil;
-import net.minecraft.server.v1_16_R3.EntityHuman;
-import net.minecraft.server.v1_16_R3.EntityPlayer;
-import net.minecraft.server.v1_16_R3.IInventoryListener;
-import net.minecraft.server.v1_16_R3.InventoryEnderChest;
-import net.minecraft.server.v1_16_R3.ItemStack;
-import net.minecraft.server.v1_16_R3.NonNullList;
-import org.bukkit.Location;
-import org.bukkit.craftbukkit.v1_16_R3.entity.CraftHumanEntity;
-import org.bukkit.craftbukkit.v1_16_R3.inventory.CraftInventory;
-import org.bukkit.entity.HumanEntity;
-import org.bukkit.entity.Player;
-import org.bukkit.inventory.InventoryHolder;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-public class SpecialEnderChest extends InventoryEnderChest implements ISpecialEnderChest {
-
- private final CraftInventory inventory;
- private EntityPlayer owner;
- private NonNullList items;
- private boolean playerOnline;
-
- public SpecialEnderChest(final Player player, final Boolean online) {
- super(PlayerDataManager.getHandle(player));
- this.inventory = new CraftInventory(this);
- this.owner = PlayerDataManager.getHandle(player);
- this.playerOnline = online;
- this.items = this.owner.getEnderChest().items;
- }
-
- @Override
- public @NotNull CraftInventory getBukkitInventory() {
- return inventory;
- }
-
- @Override
- public boolean isInUse() {
- return !this.getViewers().isEmpty();
- }
-
- @Override
- public void setPlayerOffline() {
- this.playerOnline = false;
- }
-
- @Override
- public void setPlayerOnline(@NotNull final Player player) {
- if (!this.playerOnline) {
- try {
- this.owner = PlayerDataManager.getHandle(player);
- InventoryEnderChest enderChest = owner.getEnderChest();
- for (int i = 0; i < enderChest.getSize(); ++i) {
- enderChest.setItem(i, this.items.get(i));
- }
- this.items = enderChest.items;
- } catch (Exception ignored) {}
- this.playerOnline = true;
- }
- }
-
- @Override
- public void update() {
- this.owner.getEnderChest().update();
- }
-
- @Override
- public List getContents() {
- return this.items;
- }
-
- @Override
- public void onOpen(CraftHumanEntity who) {
- this.owner.getEnderChest().onOpen(who);
- }
-
- @Override
- public void onClose(CraftHumanEntity who) {
- this.owner.getEnderChest().onClose(who);
- }
-
- @Override
- public List getViewers() {
- return this.owner.getEnderChest().getViewers();
- }
-
- @Override
- public void setMaxStackSize(int i) {
- this.owner.getEnderChest().setMaxStackSize(i);
- }
-
- @Override
- public InventoryHolder getOwner() {
- return this.owner.getEnderChest().getOwner();
- }
-
- @Override
- public @Nullable Location getLocation() {
- return null;
- }
-
- @Override
- public void a(IInventoryListener iinventorylistener) {
- this.owner.getEnderChest().a(iinventorylistener);
- }
-
- @Override
- public void b(IInventoryListener iinventorylistener) {
- this.owner.getEnderChest().b(iinventorylistener);
- }
-
- @Override
- public ItemStack getItem(int i) {
- return i >= 0 && i < this.items.size() ? this.items.get(i) : ItemStack.b;
- }
-
- @Override
- public ItemStack splitStack(int i, int j) {
- ItemStack itemstack = ContainerUtil.a(this.items, i, j);
- if (!itemstack.isEmpty()) {
- this.update();
- }
-
- return itemstack;
- }
-
- @Override
- public ItemStack a(ItemStack itemstack) {
- ItemStack itemstack1 = itemstack.cloneItemStack();
-
- for (int i = 0; i < this.getSize(); ++i) {
- ItemStack itemstack2 = this.getItem(i);
- if (itemstack2.isEmpty()) {
- this.setItem(i, itemstack1);
- this.update();
- return ItemStack.b;
- }
-
- if (ItemStack.c(itemstack2, itemstack1)) {
- int j = Math.min(this.getMaxStackSize(), itemstack2.getMaxStackSize());
- int k = Math.min(itemstack1.getCount(), j - itemstack2.getCount());
- if (k > 0) {
- itemstack2.add(k);
- itemstack1.subtract(k);
- if (itemstack1.isEmpty()) {
- this.update();
- return ItemStack.b;
- }
- }
- }
- }
-
- if (itemstack1.getCount() != itemstack.getCount()) {
- this.update();
- }
-
- return itemstack1;
- }
-
- @Override
- public ItemStack splitWithoutUpdate(int i) {
- ItemStack itemstack = this.items.get(i);
- if (itemstack.isEmpty()) {
- return ItemStack.b;
- } else {
- this.items.set(i, ItemStack.b);
- 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.update();
- }
-
- @Override
- public int getSize() {
- return this.owner.getEnderChest().getSize();
- }
-
- @Override
- public boolean isEmpty() {
-
- for (ItemStack itemstack : this.items) {
- if (!itemstack.isEmpty()) {
- return false;
- }
- }
-
- return true;
- }
-
- @Override
- public int getMaxStackSize() {
- return 64;
- }
-
- @Override
- public boolean a(EntityHuman entityhuman) {
- return true;
- }
-
- @Override
- public void startOpen(EntityHuman entityhuman) {
- }
-
- @Override
- public void closeContainer(EntityHuman entityhuman) {
- }
-
- @Override
- public boolean b(int i, ItemStack itemstack) {
- return true;
- }
-
- @Override
- public void clear() {
- this.items.clear();
- }
-
- @Override
- public void a(AutoRecipeStackManager autorecipestackmanager) {
-
- for (ItemStack itemstack : this.items) {
- autorecipestackmanager.b(itemstack);
- }
-
- }
-
-}
diff --git a/internal/v1_16_R3/src/main/java/com/lishid/openinv/internal/v1_16_R3/SpecialPlayerInventory.java b/internal/v1_16_R3/src/main/java/com/lishid/openinv/internal/v1_16_R3/SpecialPlayerInventory.java
deleted file mode 100644
index ada345c1..00000000
--- a/internal/v1_16_R3/src/main/java/com/lishid/openinv/internal/v1_16_R3/SpecialPlayerInventory.java
+++ /dev/null
@@ -1,733 +0,0 @@
-/*
- * Copyright (C) 2011-2020 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_16_R3;
-
-import com.google.common.collect.ImmutableList;
-import com.lishid.openinv.internal.ISpecialPlayerInventory;
-import java.util.Iterator;
-import java.util.List;
-import java.util.function.Predicate;
-import java.util.stream.Collectors;
-import net.minecraft.server.v1_16_R3.AutoRecipeStackManager;
-import net.minecraft.server.v1_16_R3.ChatMessage;
-import net.minecraft.server.v1_16_R3.ContainerUtil;
-import net.minecraft.server.v1_16_R3.CrashReport;
-import net.minecraft.server.v1_16_R3.CrashReportSystemDetails;
-import net.minecraft.server.v1_16_R3.DamageSource;
-import net.minecraft.server.v1_16_R3.EntityHuman;
-import net.minecraft.server.v1_16_R3.EntityPlayer;
-import net.minecraft.server.v1_16_R3.EnumItemSlot;
-import net.minecraft.server.v1_16_R3.IBlockData;
-import net.minecraft.server.v1_16_R3.IChatBaseComponent;
-import net.minecraft.server.v1_16_R3.IInventory;
-import net.minecraft.server.v1_16_R3.Item;
-import net.minecraft.server.v1_16_R3.ItemArmor;
-import net.minecraft.server.v1_16_R3.ItemStack;
-import net.minecraft.server.v1_16_R3.NBTTagCompound;
-import net.minecraft.server.v1_16_R3.NBTTagList;
-import net.minecraft.server.v1_16_R3.NonNullList;
-import net.minecraft.server.v1_16_R3.PacketPlayOutSetSlot;
-import net.minecraft.server.v1_16_R3.PlayerInventory;
-import net.minecraft.server.v1_16_R3.ReportedException;
-import net.minecraft.server.v1_16_R3.World;
-import org.bukkit.Location;
-import org.bukkit.craftbukkit.v1_16_R3.entity.CraftHumanEntity;
-import org.bukkit.craftbukkit.v1_16_R3.inventory.CraftInventory;
-import org.bukkit.entity.HumanEntity;
-import org.bukkit.entity.Player;
-import org.bukkit.inventory.InventoryHolder;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-public class SpecialPlayerInventory extends PlayerInventory implements ISpecialPlayerInventory {
-
- private final CraftInventory inventory;
- private boolean playerOnline;
- private EntityHuman player;
- private NonNullList items, armor, extraSlots;
- private List> f;
-
- public SpecialPlayerInventory(final Player bukkitPlayer, final Boolean online) {
- super(PlayerDataManager.getHandle(bukkitPlayer));
- this.inventory = new CraftInventory(this);
- this.playerOnline = online;
- this.player = super.player;
- this.items = this.player.inventory.items;
- this.armor = this.player.inventory.armor;
- this.extraSlots = this.player.inventory.extraSlots;
- this.f = ImmutableList.of(this.items, this.armor, this.extraSlots);
- }
-
- @Override
- public void setPlayerOnline(@NotNull final Player player) {
- if (!this.playerOnline) {
- EntityPlayer entityPlayer = PlayerDataManager.getHandle(player);
- entityPlayer.inventory.transaction.addAll(this.transaction);
- this.player = entityPlayer;
- for (int i = 0; i < getSize(); ++i) {
- this.player.inventory.setItem(i, getRawItem(i));
- }
- this.player.inventory.itemInHandIndex = this.itemInHandIndex;
- this.items = this.player.inventory.items;
- this.armor = this.player.inventory.armor;
- this.extraSlots = this.player.inventory.extraSlots;
- this.f = ImmutableList.of(this.items, this.armor, this.extraSlots);
- this.playerOnline = true;
- }
- }
-
- @Override
- public boolean a(final EntityHuman entityhuman) {
- return true;
- }
-
- @Override
- public @NotNull CraftInventory getBukkitInventory() {
- return this.inventory;
- }
-
- @Override
- public ItemStack getItem(int i) {
- List list = this.items;
-
- if (i >= list.size()) {
- i -= list.size();
- list = this.armor;
- } else {
- i = this.getReversedItemSlotNum(i);
- }
-
- if (i >= list.size()) {
- i -= list.size();
- list = this.extraSlots;
- } else if (list == this.armor) {
- i = this.getReversedArmorSlotNum(i);
- }
-
- if (i >= list.size()) {
- return ItemStack.b;
- }
-
- return list.get(i);
- }
-
- private ItemStack getRawItem(int i) {
- NonNullList list = null;
- for (NonNullList next : this.f) {
- if (i < next.size()) {
- list = next;
- break;
- }
- i -= next.size();
- }
-
- return list == null ? ItemStack.b : list.get(i);
- }
-
- @Override
- public IChatBaseComponent getDisplayName() {
- return new ChatMessage(this.player.getName());
- }
-
- @Override
- public boolean hasCustomName() {
- return false;
- }
-
- 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 int getSize() {
- return 45;
- }
-
- @Override
- public boolean isInUse() {
- return !this.getViewers().isEmpty();
- }
-
- @Override
- public void setItem(int i, final ItemStack itemstack) {
- List list = this.items;
-
- if (i >= list.size()) {
- i -= list.size();
- list = this.armor;
- } else {
- i = this.getReversedItemSlotNum(i);
- }
-
- if (i >= list.size()) {
- i -= list.size();
- list = this.extraSlots;
- } else if (list == this.armor) {
- i = this.getReversedArmorSlotNum(i);
- }
-
- if (i >= list.size()) {
- this.player.drop(itemstack, true);
- return;
- }
-
- list.set(i, itemstack);
- }
-
- @Override
- public void setPlayerOffline() {
- this.playerOnline = false;
- }
-
- @Override
- public ItemStack splitStack(int i, final int j) {
- List list = this.items;
-
- if (i >= list.size()) {
- i -= list.size();
- list = this.armor;
- } else {
- i = this.getReversedItemSlotNum(i);
- }
-
- if (i >= list.size()) {
- i -= list.size();
- list = this.extraSlots;
- } else if (list == this.armor) {
- i = this.getReversedArmorSlotNum(i);
- }
-
- if (i >= list.size()) {
- return ItemStack.b;
- }
-
- return list.get(i).isEmpty() ? ItemStack.b : ContainerUtil.a(list, i, j);
- }
-
- @Override
- public ItemStack splitWithoutUpdate(int i) {
- List list = this.items;
-
- if (i >= list.size()) {
- i -= list.size();
- list = this.armor;
- } else {
- i = this.getReversedItemSlotNum(i);
- }
-
- if (i >= list.size()) {
- i -= list.size();
- list = this.extraSlots;
- } else if (list == this.armor) {
- i = this.getReversedArmorSlotNum(i);
- }
-
- if (i >= list.size()) {
- return ItemStack.b;
- }
-
- if (!list.get(i).isEmpty()) {
- ItemStack itemstack = list.get(i);
-
- list.set(i, ItemStack.b);
- return itemstack;
- }
-
- return ItemStack.b;
- }
-
- @Override
- public List getContents() {
- return this.f.stream().flatMap(List::stream).collect(Collectors.toList());
- }
-
- @Override
- public List getArmorContents() {
- return this.armor;
- }
-
- @Override
- public void onOpen(CraftHumanEntity who) {
- this.transaction.add(who);
- }
-
- @Override
- public void onClose(CraftHumanEntity who) {
- this.transaction.remove(who);
- }
-
- @Override
- public List getViewers() {
- return this.transaction;
- }
-
- @Override
- public InventoryHolder getOwner() {
- return this.player.getBukkitEntity();
- }
-
- @Override
- public Location getLocation() {
- return this.player.getBukkitEntity().getLocation();
- }
-
- @Override
- public ItemStack getItemInHand() {
- return d(this.itemInHandIndex) ? this.items.get(this.itemInHandIndex) : ItemStack.b;
- }
-
- private boolean isSimilarAndNotFull(ItemStack itemstack, ItemStack itemstack1) {
- return !itemstack.isEmpty() && this.b(itemstack, itemstack1) && itemstack.isStackable() && itemstack.getCount() < itemstack.getMaxStackSize() && itemstack.getCount() < this.getMaxStackSize();
- }
-
- private boolean b(ItemStack itemstack, ItemStack itemstack1) {
- return itemstack.getItem() == itemstack1.getItem() && ItemStack.equals(itemstack, itemstack1);
- }
-
- @Override
- public int canHold(ItemStack itemstack) {
- int remains = itemstack.getCount();
-
- for (int i = 0; i < this.items.size(); ++i) {
- ItemStack itemstack1 = this.getItem(i);
- if (itemstack1.isEmpty()) {
- return itemstack.getCount();
- }
-
- if (!this.isSimilarAndNotFull(itemstack, itemstack1)) {
- remains -= Math.min(itemstack1.getMaxStackSize(), this.getMaxStackSize()) - itemstack1.getCount();
- }
-
- if (remains <= 0) {
- return itemstack.getCount();
- }
- }
-
- ItemStack offhandItemStack = this.getItem(this.items.size() + this.armor.size());
- if (this.isSimilarAndNotFull(offhandItemStack, itemstack)) {
- remains -= Math.min(offhandItemStack.getMaxStackSize(), this.getMaxStackSize()) - offhandItemStack.getCount();
- }
-
- return itemstack.getCount() - remains;
- }
-
- @Override
- public int getFirstEmptySlotIndex() {
- for (int i = 0; i < this.items.size(); ++i) {
- if (this.items.get(i).isEmpty()) {
- return i;
- }
- }
-
- return -1;
- }
-
- @Override
- public void c(int i) {
- this.itemInHandIndex = this.i();
- ItemStack itemstack = this.items.get(this.itemInHandIndex);
- this.items.set(this.itemInHandIndex, this.items.get(i));
- this.items.set(i, itemstack);
- }
-
- @Override
- public int c(ItemStack itemstack) {
- for (int i = 0; i < this.items.size(); ++i) {
- ItemStack itemstack1 = this.items.get(i);
- if (!this.items.get(i).isEmpty() && this.b(itemstack, this.items.get(i)) && !this.items.get(i).f() && !itemstack1.hasEnchantments() && !itemstack1.hasName()) {
- return i;
- }
- }
-
- return -1;
- }
-
- @Override
- public int i() {
- int i;
- int j;
- for (j = 0; j < 9; ++j) {
- i = (this.itemInHandIndex + j) % 9;
- if (this.items.get(i).isEmpty()) {
- return i;
- }
- }
-
- for (j = 0; j < 9; ++j) {
- i = (this.itemInHandIndex + j) % 9;
- if (!this.items.get(i).hasEnchantments()) {
- return i;
- }
- }
-
- return this.itemInHandIndex;
- }
-
- @Override
- public int a(Predicate predicate, int i, IInventory iinventory) {
- byte b0 = 0;
- boolean flag = i == 0;
- int j = b0 + ContainerUtil.a(this, predicate, i - b0, flag);
- j += ContainerUtil.a(iinventory, predicate, i - j, flag);
- j += ContainerUtil.a(this.getCarried(), predicate, i - j, flag);
- if (this.getCarried().isEmpty()) {
- this.setCarried(ItemStack.b);
- }
-
- return j;
- }
-
- private int i(ItemStack itemstack) {
- int i = this.firstPartial(itemstack);
- if (i == -1) {
- i = this.getFirstEmptySlotIndex();
- }
-
- return i == -1 ? itemstack.getCount() : this.d(i, itemstack);
- }
-
- private int d(int i, ItemStack itemstack) {
- Item item = itemstack.getItem();
- int j = itemstack.getCount();
- ItemStack itemstack1 = this.getItem(i);
- if (itemstack1.isEmpty()) {
- itemstack1 = new ItemStack(item, 0);
- NBTTagCompound tag = itemstack.getTag();
- if (tag != null) {
- itemstack1.setTag(tag.clone());
- }
-
- this.setItem(i, itemstack1);
- }
-
- int k = j;
- if (j > itemstack1.getMaxStackSize() - itemstack1.getCount()) {
- k = itemstack1.getMaxStackSize() - itemstack1.getCount();
- }
-
- if (k > this.getMaxStackSize() - itemstack1.getCount()) {
- k = this.getMaxStackSize() - itemstack1.getCount();
- }
-
- if (k != 0) {
- j -= k;
- itemstack1.add(k);
- itemstack1.d(5);
- }
- return j;
- }
-
- @Override
- public int firstPartial(ItemStack itemstack) {
- if (this.isSimilarAndNotFull(this.getItem(this.itemInHandIndex), itemstack)) {
- return this.itemInHandIndex;
- } else if (this.isSimilarAndNotFull(this.getItem(40), itemstack)) {
- return 40;
- } else {
- for (int i = 0; i < this.items.size(); ++i) {
- if (this.isSimilarAndNotFull(this.items.get(i), itemstack)) {
- return i;
- }
- }
-
- return -1;
- }
- }
-
- @Override
- public void j() {
-
- for (List itemStacks : this.f) {
- for (int i = 0; i < itemStacks.size(); ++i) {
- if (!itemStacks.get(i).isEmpty()) {
- itemStacks.get(i).a(this.player.world, this.player, i, this.itemInHandIndex == i);
- }
- }
- }
-
- }
-
- @Override
- public boolean pickup(ItemStack itemstack) {
- return this.c(-1, itemstack);
- }
-
- @Override
- public boolean c(int i, ItemStack itemstack) {
- if (itemstack.isEmpty()) {
- return false;
- } else {
- try {
- if (itemstack.f()) {
- if (i == -1) {
- i = this.getFirstEmptySlotIndex();
- }
-
- if (i >= 0) {
- this.items.set(i, itemstack.cloneItemStack());
- this.items.get(i).d(5);
- itemstack.setCount(0);
- return true;
- } else if (this.player.abilities.canInstantlyBuild) {
- itemstack.setCount(0);
- return true;
- } else {
- return false;
- }
- } else {
- int j;
- do {
- j = itemstack.getCount();
- if (i == -1) {
- itemstack.setCount(this.i(itemstack));
- } else {
- itemstack.setCount(this.d(i, itemstack));
- }
- } while(!itemstack.isEmpty() && itemstack.getCount() < j);
-
- if (itemstack.getCount() == j && this.player.abilities.canInstantlyBuild) {
- itemstack.setCount(0);
- return true;
- } else {
- return itemstack.getCount() < j;
- }
- }
- } catch (Throwable var6) {
- CrashReport crashreport = CrashReport.a(var6, "Adding item to inventory");
- CrashReportSystemDetails crashreportsystemdetails = crashreport.a("Item being added");
- crashreportsystemdetails.a("Item ID", Item.getId(itemstack.getItem()));
- crashreportsystemdetails.a("Item data", itemstack.getDamage());
- crashreportsystemdetails.a("Item name", () -> itemstack.getName().getString());
- throw new ReportedException(crashreport);
- }
- }
- }
-
- @Override
- public void a(World world, ItemStack itemstack) {
- if (!world.isClientSide) {
- while(!itemstack.isEmpty()) {
- int i = this.firstPartial(itemstack);
- if (i == -1) {
- i = this.getFirstEmptySlotIndex();
- }
-
- if (i == -1) {
- this.player.drop(itemstack, false);
- break;
- }
-
- int j = itemstack.getMaxStackSize() - this.getItem(i).getCount();
- if (this.c(i, itemstack.cloneAndSubtract(j))) {
- ((EntityPlayer)this.player).playerConnection.sendPacket(new PacketPlayOutSetSlot(-2, i, this.getItem(i)));
- }
- }
- }
-
- }
-
- @Override
- public void f(ItemStack itemstack) {
-
- for (List list : this.f) {
- for (int i = 0; i < list.size(); ++i) {
- if (list.get(i) == itemstack) {
- list.set(i, ItemStack.b);
- break;
- }
- }
- }
- }
-
- @Override
- public float a(IBlockData iblockdata) {
- return this.items.get(this.itemInHandIndex).a(iblockdata);
- }
-
- @Override
- public NBTTagList a(NBTTagList nbttaglist) {
- NBTTagCompound nbttagcompound;
- int i;
- for (i = 0; i < this.items.size(); ++i) {
- if (!this.items.get(i).isEmpty()) {
- nbttagcompound = new NBTTagCompound();
- nbttagcompound.setByte("Slot", (byte) i);
- this.items.get(i).save(nbttagcompound);
- nbttaglist.add(nbttagcompound);
- }
- }
-
- for (i = 0; i < this.armor.size(); ++i) {
- if (!this.armor.get(i).isEmpty()) {
- nbttagcompound = new NBTTagCompound();
- nbttagcompound.setByte("Slot", (byte) (i + 100));
- this.armor.get(i).save(nbttagcompound);
- nbttaglist.add(nbttagcompound);
- }
- }
-
- for (i = 0; i < this.extraSlots.size(); ++i) {
- if (!this.extraSlots.get(i).isEmpty()) {
- nbttagcompound = new NBTTagCompound();
- nbttagcompound.setByte("Slot", (byte) (i + 150));
- this.extraSlots.get(i).save(nbttagcompound);
- nbttaglist.add(nbttagcompound);
- }
- }
-
- return nbttaglist;
- }
-
- @Override
- public void b(NBTTagList nbttaglist) {
- this.items.clear();
- this.armor.clear();
- this.extraSlots.clear();
-
- for(int i = 0; i < nbttaglist.size(); ++i) {
- NBTTagCompound nbttagcompound = nbttaglist.getCompound(i);
- int j = nbttagcompound.getByte("Slot") & 255;
- ItemStack itemstack = ItemStack.a(nbttagcompound);
- 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.extraSlots.size() + 150) {
- this.extraSlots.set(j - 150, itemstack);
- }
- }
- }
-
- }
-
- @Override
- public boolean isEmpty() {
- Iterator iterator = this.items.iterator();
-
- ItemStack itemstack;
- while (iterator.hasNext()) {
- itemstack = iterator.next();
- if (!itemstack.isEmpty()) {
- return false;
- }
- }
-
- iterator = this.armor.iterator();
-
- while (iterator.hasNext()) {
- itemstack = iterator.next();
- if (!itemstack.isEmpty()) {
- return false;
- }
- }
-
- iterator = this.extraSlots.iterator();
-
- while (iterator.hasNext()) {
- itemstack = iterator.next();
- if (!itemstack.isEmpty()) {
- return false;
- }
- }
-
- return true;
- }
-
- @Nullable
- @Override
- public IChatBaseComponent getCustomName() {
- return null;
- }
-
- @Override
- public void a(DamageSource damagesource, float f) {
- if (f > 0.0F) {
- f /= 4.0F;
- if (f < 1.0F) {
- f = 1.0F;
- }
-
- for (int i = 0; i < this.armor.size(); ++i) {
- ItemStack itemstack = this.armor.get(0);
- int index = i;
- if ((!damagesource.isFire() || !itemstack.getItem().u()) && itemstack.getItem() instanceof ItemArmor) {
- itemstack.damage((int) f, this.player, (entityHuman) -> entityHuman.broadcastItemBreak(EnumItemSlot.a(EnumItemSlot.Function.ARMOR, index)));
- }
- }
- }
- }
-
- @Override
- public void dropContents() {
- for (List itemStacks : this.f) {
- for (int i = 0; i < itemStacks.size(); ++i) {
- ItemStack itemstack = itemStacks.get(i);
- if (!itemstack.isEmpty()) {
- itemStacks.set(i, ItemStack.b);
- this.player.a(itemstack, true, false);
- }
- }
- }
- }
-
- @Override
- public boolean h(ItemStack itemstack) {
- return this.f.stream().flatMap(List::stream).anyMatch(itemStack1 -> !itemStack1.isEmpty() && itemStack1.doMaterialsMatch(itemstack));
- }
-
- @Override
- public void a(PlayerInventory playerinventory) {
- for (int i = 0; i < playerinventory.getSize(); ++i) {
- this.setItem(i, playerinventory.getItem(i));
- }
-
- this.itemInHandIndex = playerinventory.itemInHandIndex;
- }
-
- @Override
- public void clear() {
- this.f.forEach(List::clear);
- }
-
- @Override
- public void a(AutoRecipeStackManager autorecipestackmanager) {
- for (ItemStack itemstack : this.items) {
- autorecipestackmanager.a(itemstack);
- }
- }
-
-}
diff --git a/internal/v1_16_R3/pom.xml b/internal/v1_19_R3/pom.xml
similarity index 57%
rename from internal/v1_16_R3/pom.xml
rename to internal/v1_19_R3/pom.xml
index 74905edd..5ad65229 100644
--- a/internal/v1_16_R3/pom.xml
+++ b/internal/v1_19_R3/pom.xml
@@ -1,6 +1,6 @@
+
+
+ 4.0.0
+
+
+ openinvparent
+ com.lishid
+ ../../pom.xml
+ 4.4.1-SNAPSHOT
+
+
+ openinvadapter1_20_R1
+ OpenInvAdapter1_20_R1
+
+
+ 17
+ 17
+ 1.20.1-R0.1-SNAPSHOT
+
+
+
+
+ org.spigotmc
+ spigot-api
+ ${spigot.version}
+
+
+ spigot
+ org.spigotmc
+ provided
+ ${spigot.version}
+ remapped-mojang
+
+
+ openinvapi
+ com.lishid
+ provided
+
+
+ openinvplugincore
+ com.lishid
+
+
+ annotations
+ org.jetbrains
+
+
+
+
+
+
+ maven-shade-plugin
+
+
+ maven-compiler-plugin
+
+
+ net.md-5
+ specialsource-maven-plugin
+
+
+
+
+
diff --git a/internal/v1_20_R1/src/main/java/com/lishid/openinv/internal/v1_20_R1/AnySilentContainer.java b/internal/v1_20_R1/src/main/java/com/lishid/openinv/internal/v1_20_R1/AnySilentContainer.java
new file mode 100644
index 00000000..459f9693
--- /dev/null
+++ b/internal/v1_20_R1/src/main/java/com/lishid/openinv/internal/v1_20_R1/AnySilentContainer.java
@@ -0,0 +1,243 @@
+/*
+ * 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_R1;
+
+import com.lishid.openinv.OpenInv;
+import com.lishid.openinv.internal.IAnySilentContainer;
+import com.lishid.openinv.util.ReflectionHelper;
+import java.lang.reflect.Field;
+import java.util.logging.Logger;
+import net.minecraft.core.BlockPos;
+import net.minecraft.network.chat.Component;
+import net.minecraft.server.level.ServerLevel;
+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.entity.monster.Shulker;
+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.entity.ShulkerBoxBlockEntity;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.phys.AABB;
+import org.bukkit.Bukkit;
+import org.bukkit.GameMode;
+import org.bukkit.Material;
+import org.bukkit.Statistic;
+import org.bukkit.block.ShulkerBox;
+import org.bukkit.craftbukkit.v1_20_R1.CraftWorld;
+import org.bukkit.entity.Player;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public class AnySilentContainer implements IAnySilentContainer {
+
+ private @Nullable Field serverPlayerGameModeGameType;
+
+ public AnySilentContainer() {
+ try {
+ try {
+ this.serverPlayerGameModeGameType = ServerPlayerGameMode.class.getDeclaredField("b");
+ this.serverPlayerGameModeGameType.setAccessible(true);
+ } catch (NoSuchFieldException e) {
+ Logger logger = OpenInv.getPlugin(OpenInv.class).getLogger();
+ 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 logger = OpenInv.getPlugin(OpenInv.class).getLogger();
+ 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 isShulkerBlocked(@NotNull ShulkerBox shulkerBox) {
+ org.bukkit.World bukkitWorld = shulkerBox.getWorld();
+ if (!(bukkitWorld instanceof CraftWorld)) {
+ bukkitWorld = Bukkit.getWorld(bukkitWorld.getUID());
+ }
+
+ if (!(bukkitWorld instanceof CraftWorld craftWorld)) {
+ Exception exception = new IllegalStateException("AnySilentContainer access attempted on an unknown world!");
+ OpenInv.getPlugin(OpenInv.class).getLogger().log(java.util.logging.Level.WARNING, exception.getMessage(), exception);
+ return false;
+ }
+
+ final ServerLevel world = craftWorld.getHandle();
+ final BlockPos blockPosition = new BlockPos(shulkerBox.getX(), shulkerBox.getY(), shulkerBox.getZ());
+ final BlockEntity tile = world.getBlockEntity(blockPosition);
+
+ if (!(tile instanceof ShulkerBoxBlockEntity shulkerBoxBlockEntity)
+ || shulkerBoxBlockEntity.getAnimationStatus() != ShulkerBoxBlockEntity.AnimationStatus.CLOSED) {
+ return false;
+ }
+
+ BlockState blockState = world.getBlockState(blockPosition);
+
+ // See net.minecraft.world.level.block.ShulkerBoxBlock#canOpen
+ AABB boundingBox = Shulker.getProgressDeltaAabb(blockState.getValue(ShulkerBoxBlock.FACING), 0.0F, 0.5F)
+ .move(blockPosition)
+ .deflate(1.0E-6D);
+ return !world.noCollision(boundingBox);
+ }
+
+ @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 = PlayerDataManager.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 = PlayerDataManager.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) {
+ OpenInv.getPlugin(OpenInv.class).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) {
+ OpenInv.getPlugin(OpenInv.class).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 = PlayerDataManager.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) {
+ e.printStackTrace();
+ }
+ }
+
+}
diff --git a/internal/v1_20_R1/src/main/java/com/lishid/openinv/internal/v1_20_R1/OpenPlayer.java b/internal/v1_20_R1/src/main/java/com/lishid/openinv/internal/v1_20_R1/OpenPlayer.java
new file mode 100644
index 00000000..9ebd79b2
--- /dev/null
+++ b/internal/v1_20_R1/src/main/java/com/lishid/openinv/internal/v1_20_R1/OpenPlayer.java
@@ -0,0 +1,74 @@
+/*
+ * 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_R1;
+
+import com.mojang.logging.LogUtils;
+import java.io.File;
+import net.minecraft.Util;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.nbt.NbtIo;
+import net.minecraft.server.level.ServerPlayer;
+import net.minecraft.world.level.storage.PlayerDataStorage;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftPlayer;
+
+public class OpenPlayer extends CraftPlayer {
+
+ public OpenPlayer(CraftServer server, ServerPlayer entity) {
+ super(server, entity);
+ }
+
+ @Override
+ public void loadData() {
+ // See CraftPlayer#loadData
+ CompoundTag loaded = this.server.getHandle().playerIo.load(this.getHandle());
+ if (loaded != null) {
+ getHandle().readAdditionalSaveData(loaded);
+ }
+ }
+
+ @Override
+ public void saveData() {
+ ServerPlayer player = this.getHandle();
+ // See net.minecraft.world.level.storage.PlayerDataStorage#save(EntityHuman)
+ try {
+ PlayerDataStorage worldNBTStorage = player.server.getPlayerList().playerIo;
+
+ CompoundTag playerData = player.saveWithoutId(new CompoundTag());
+ setExtraData(playerData);
+
+ if (!isOnline()) {
+ // Special case: save old vehicle data
+ CompoundTag oldData = worldNBTStorage.load(player);
+
+ if (oldData != null && oldData.contains("RootVehicle", 10)) {
+ // See net.minecraft.server.PlayerList#a(NetworkManager, EntityPlayer) and net.minecraft.server.EntityPlayer#b(NBTTagCompound)
+ playerData.put("RootVehicle", oldData.getCompound("RootVehicle"));
+ }
+ }
+
+ File file = File.createTempFile(player.getStringUUID() + "-", ".dat", worldNBTStorage.getPlayerDir());
+ NbtIo.writeCompressed(playerData, file);
+ File file1 = new File(worldNBTStorage.getPlayerDir(), player.getStringUUID() + ".dat");
+ File file2 = new File(worldNBTStorage.getPlayerDir(), player.getStringUUID() + ".dat_old");
+ Util.safeReplaceFile(file1, file, file2);
+ } catch (Exception e) {
+ LogUtils.getLogger().warn("Failed to save player data for {}: {}", player.getScoreboardName(), e);
+ }
+ }
+
+}
diff --git a/internal/v1_16_R3/src/main/java/com/lishid/openinv/internal/v1_16_R3/PlayerDataManager.java b/internal/v1_20_R1/src/main/java/com/lishid/openinv/internal/v1_20_R1/PlayerDataManager.java
similarity index 59%
rename from internal/v1_16_R3/src/main/java/com/lishid/openinv/internal/v1_16_R3/PlayerDataManager.java
rename to internal/v1_20_R1/src/main/java/com/lishid/openinv/internal/v1_20_R1/PlayerDataManager.java
index 5a29ceb5..710e1987 100644
--- a/internal/v1_16_R3/src/main/java/com/lishid/openinv/internal/v1_16_R3/PlayerDataManager.java
+++ b/internal/v1_20_R1/src/main/java/com/lishid/openinv/internal/v1_20_R1/PlayerDataManager.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2011-2020 lishid. All rights reserved.
+ * 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
@@ -14,30 +14,32 @@
* along with this program. If not, see .
*/
-package com.lishid.openinv.internal.v1_16_R3;
+package com.lishid.openinv.internal.v1_20_R1;
+import com.lishid.openinv.OpenInv;
import com.lishid.openinv.internal.IPlayerDataManager;
import com.lishid.openinv.internal.ISpecialInventory;
import com.lishid.openinv.internal.OpenInventoryView;
import com.mojang.authlib.GameProfile;
import java.lang.reflect.Field;
-import net.minecraft.server.v1_16_R3.ChatComponentText;
-import net.minecraft.server.v1_16_R3.Container;
-import net.minecraft.server.v1_16_R3.Containers;
-import net.minecraft.server.v1_16_R3.Entity;
-import net.minecraft.server.v1_16_R3.EntityPlayer;
-import net.minecraft.server.v1_16_R3.MinecraftServer;
-import net.minecraft.server.v1_16_R3.PacketPlayOutOpenWindow;
-import net.minecraft.server.v1_16_R3.PlayerInteractManager;
-import net.minecraft.server.v1_16_R3.World;
-import net.minecraft.server.v1_16_R3.WorldServer;
+import java.util.logging.Logger;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.network.chat.Component;
+import net.minecraft.network.protocol.game.ClientboundOpenScreenPacket;
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.server.level.ServerPlayer;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.inventory.AbstractContainerMenu;
+import net.minecraft.world.inventory.MenuType;
+import net.minecraft.world.level.Level;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.Server;
-import org.bukkit.craftbukkit.v1_16_R3.CraftServer;
-import org.bukkit.craftbukkit.v1_16_R3.entity.CraftPlayer;
-import org.bukkit.craftbukkit.v1_16_R3.event.CraftEventFactory;
-import org.bukkit.craftbukkit.v1_16_R3.inventory.CraftContainer;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftPlayer;
+import org.bukkit.craftbukkit.v1_20_R1.event.CraftEventFactory;
+import org.bukkit.craftbukkit.v1_20_R1.inventory.CraftContainer;
import org.bukkit.entity.Player;
import org.bukkit.inventory.InventoryView;
import org.jetbrains.annotations.NotNull;
@@ -51,23 +53,23 @@ public PlayerDataManager() {
try {
bukkitEntity = Entity.class.getDeclaredField("bukkitEntity");
} catch (NoSuchFieldException e) {
- System.out.println("Unable to obtain field to inject custom save process - players' mounts may be deleted when loaded.");
- e.printStackTrace();
+ Logger logger = OpenInv.getPlugin(OpenInv.class).getLogger();
+ logger.warning("Unable to obtain field to inject custom save process - players' mounts may be deleted when loaded.");
+ logger.log(java.util.logging.Level.WARNING, e.getMessage(), e);
bukkitEntity = null;
}
}
- @NotNull
- public static EntityPlayer getHandle(final Player player) {
+ public static @NotNull ServerPlayer getHandle(final Player player) {
if (player instanceof CraftPlayer) {
return ((CraftPlayer) player).getHandle();
}
Server server = player.getServer();
- EntityPlayer nmsPlayer = null;
+ ServerPlayer nmsPlayer = null;
if (server instanceof CraftServer) {
- nmsPlayer = ((CraftServer) server).getHandle().getPlayer(player.getName());
+ nmsPlayer = ((CraftServer) server).getHandle().getPlayer(player.getUniqueId());
}
if (nmsPlayer == null) {
@@ -78,26 +80,29 @@ public static EntityPlayer getHandle(final Player player) {
return nmsPlayer;
}
- @Nullable
@Override
- public Player loadPlayer(@NotNull final OfflinePlayer offline) {
+ public @Nullable Player loadPlayer(@NotNull final OfflinePlayer offline) {
// Ensure player has data
if (!offline.hasPlayedBefore()) {
return null;
}
// Create a profile and entity to load the player data
- // See net.minecraft.server.PlayerList#attemptLogin
+ // See net.minecraft.server.players.PlayerList#canPlayerLogin
+ // and net.minecraft.server.network.ServerLoginPacketListenerImpl#handleHello
GameProfile profile = new GameProfile(offline.getUniqueId(),
offline.getName() != null ? offline.getName() : offline.getUniqueId().toString());
MinecraftServer server = ((CraftServer) Bukkit.getServer()).getServer();
- WorldServer worldServer = server.getWorldServer(World.OVERWORLD);
+ ServerLevel worldServer = server.getLevel(Level.OVERWORLD);
if (worldServer == null) {
return null;
}
- EntityPlayer entity = new EntityPlayer(server, worldServer, profile, new PlayerInteractManager(worldServer));
+ ServerPlayer entity = new ServerPlayer(server, worldServer, profile);
+
+ // Stop listening for advancement progression - if this is not cleaned up, loading causes a memory leak.
+ entity.getAdvancements().stopListening();
try {
injectPlayer(entity);
@@ -105,17 +110,28 @@ public Player loadPlayer(@NotNull final OfflinePlayer offline) {
e.printStackTrace();
}
- // Get the bukkit entity
- Player target = entity.getBukkitEntity();
- if (target != null) {
- // Load data
- target.loadData();
+ // Load data. This also reads basic data into the player.
+ // See CraftPlayer#loadData
+ CompoundTag loadedData = server.getPlayerList().playerIo.load(entity);
+
+ if (loadedData == null) {
+ // Exceptions with loading are logged by Mojang.
+ return null;
+ }
+
+ // Also read "extra" data.
+ entity.readAdditionalSaveData(loadedData);
+
+ if (entity.level() == null) {
+ // Paper: Move player to spawn
+ entity.spawnIn(null);
}
- // Return the entity
- return target;
+
+ // Return the Bukkit entity.
+ return entity.getBukkitEntity();
}
- void injectPlayer(EntityPlayer player) throws IllegalAccessException {
+ void injectPlayer(ServerPlayer player) throws IllegalAccessException {
if (bukkitEntity == null) {
return;
}
@@ -129,7 +145,10 @@ void injectPlayer(EntityPlayer player) throws IllegalAccessException {
@Override
public Player inject(@NotNull Player player) {
try {
- EntityPlayer nmsPlayer = getHandle(player);
+ ServerPlayer nmsPlayer = getHandle(player);
+ if (nmsPlayer.getBukkitEntity() instanceof OpenPlayer openPlayer) {
+ return openPlayer;
+ }
injectPlayer(nmsPlayer);
return nmsPlayer.getBukkitEntity();
} catch (IllegalAccessException e) {
@@ -142,9 +161,9 @@ public Player inject(@NotNull Player player) {
@Override
public InventoryView openInventory(@NotNull Player player, @NotNull ISpecialInventory inventory) {
- EntityPlayer nmsPlayer = getHandle(player);
+ ServerPlayer nmsPlayer = getHandle(player);
- if (nmsPlayer.playerConnection == null) {
+ if (nmsPlayer.connection == null) {
return null;
}
@@ -154,24 +173,24 @@ public InventoryView openInventory(@NotNull Player player, @NotNull ISpecialInve
return player.openInventory(inventory.getBukkitInventory());
}
- Container container = new CraftContainer(view, nmsPlayer, nmsPlayer.nextContainerCounter()) {
+ AbstractContainerMenu container = new CraftContainer(view, nmsPlayer, nmsPlayer.nextContainerCounter()) {
@Override
- public Containers> getType() {
+ public MenuType> getType() {
return getContainers(inventory.getBukkitInventory().getSize());
}
};
- container.setTitle(new ChatComponentText(view.getTitle()));
+ container.setTitle(Component.literal(view.getTitle()));
container = CraftEventFactory.callInventoryOpenEvent(nmsPlayer, container);
if (container == null) {
return null;
}
- nmsPlayer.playerConnection.sendPacket(new PacketPlayOutOpenWindow(container.windowId, container.getType(),
- new ChatComponentText(container.getBukkitView().getTitle())));
- nmsPlayer.activeContainer = container;
- container.addSlotListener(nmsPlayer);
+ nmsPlayer.connection.send(new ClientboundOpenScreenPacket(container.containerId, container.getType(),
+ Component.literal(container.getBukkitView().getTitle())));
+ nmsPlayer.containerMenu = container;
+ nmsPlayer.initMenu(container);
return container.getBukkitView();
@@ -187,23 +206,16 @@ public Containers> getType() {
}
}
- static @NotNull Containers> getContainers(int inventorySize) {
- switch (inventorySize) {
- case 9:
- return Containers.GENERIC_9X1;
- case 18:
- return Containers.GENERIC_9X2;
- case 36:
- return Containers.GENERIC_9X4;
- case 41: // PLAYER
- case 45:
- return Containers.GENERIC_9X5;
- case 54:
- return Containers.GENERIC_9X6;
- case 27:
- default:
- return Containers.GENERIC_9X3;
- }
+ 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
+ };
}
@Override
diff --git a/internal/v1_20_R1/src/main/java/com/lishid/openinv/internal/v1_20_R1/SpecialEnderChest.java b/internal/v1_20_R1/src/main/java/com/lishid/openinv/internal/v1_20_R1/SpecialEnderChest.java
new file mode 100644
index 00000000..3c66f9cd
--- /dev/null
+++ b/internal/v1_20_R1/src/main/java/com/lishid/openinv/internal/v1_20_R1/SpecialEnderChest.java
@@ -0,0 +1,350 @@
+/*
+ * 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_R1;
+
+import com.lishid.openinv.internal.ISpecialEnderChest;
+import java.util.List;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+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_R1.entity.CraftHumanEntity;
+import org.bukkit.craftbukkit.v1_20_R1.inventory.CraftInventory;
+import org.bukkit.entity.HumanEntity;
+import org.bukkit.inventory.InventoryHolder;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+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(PlayerDataManager.getHandle(player));
+ this.inventory = new CraftInventory(this);
+ this.owner = PlayerDataManager.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 = PlayerDataManager.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_R1/src/main/java/com/lishid/openinv/internal/v1_20_R1/SpecialPlayerInventory.java b/internal/v1_20_R1/src/main/java/com/lishid/openinv/internal/v1_20_R1/SpecialPlayerInventory.java
new file mode 100644
index 00000000..aef0914a
--- /dev/null
+++ b/internal/v1_20_R1/src/main/java/com/lishid/openinv/internal/v1_20_R1/SpecialPlayerInventory.java
@@ -0,0 +1,806 @@
+/*
+ * 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_R1;
+
+import com.google.common.collect.ImmutableList;
+import com.lishid.openinv.internal.ISpecialPlayerInventory;
+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;
+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_R1.entity.CraftHumanEntity;
+import org.bukkit.craftbukkit.v1_20_R1.inventory.CraftInventory;
+import org.bukkit.entity.HumanEntity;
+import org.bukkit.inventory.InventoryHolder;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+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(PlayerDataManager.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 = PlayerDataManager.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_R2/pom.xml b/internal/v1_20_R2/pom.xml
new file mode 100644
index 00000000..e63be778
--- /dev/null
+++ b/internal/v1_20_R2/pom.xml
@@ -0,0 +1,81 @@
+
+
+
+
+ 4.0.0
+
+
+ openinvparent
+ com.lishid
+ ../../pom.xml
+ 4.4.1-SNAPSHOT
+
+
+ openinvadapter1_20_R2
+ OpenInvAdapter1_20_R2
+
+
+ 17
+ 17
+ 1.20.2-R0.1-SNAPSHOT
+
+
+
+
+ org.spigotmc
+ spigot-api
+ ${spigot.version}
+
+
+ spigot
+ org.spigotmc
+ provided
+ ${spigot.version}
+ remapped-mojang
+
+
+ openinvapi
+ com.lishid
+ provided
+
+
+ openinvplugincore
+ com.lishid
+
+
+ annotations
+ org.jetbrains
+
+
+
+
+
+
+ maven-shade-plugin
+
+
+ maven-compiler-plugin
+
+
+ net.md-5
+ specialsource-maven-plugin
+
+
+
+
+
diff --git a/internal/v1_20_R2/src/main/java/com/lishid/openinv/internal/v1_20_R2/AnySilentContainer.java b/internal/v1_20_R2/src/main/java/com/lishid/openinv/internal/v1_20_R2/AnySilentContainer.java
new file mode 100644
index 00000000..c3de0eba
--- /dev/null
+++ b/internal/v1_20_R2/src/main/java/com/lishid/openinv/internal/v1_20_R2/AnySilentContainer.java
@@ -0,0 +1,243 @@
+/*
+ * 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_R2;
+
+import com.lishid.openinv.OpenInv;
+import com.lishid.openinv.internal.IAnySilentContainer;
+import com.lishid.openinv.util.ReflectionHelper;
+import java.lang.reflect.Field;
+import java.util.logging.Logger;
+import net.minecraft.core.BlockPos;
+import net.minecraft.network.chat.Component;
+import net.minecraft.server.level.ServerLevel;
+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.entity.monster.Shulker;
+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.entity.ShulkerBoxBlockEntity;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.phys.AABB;
+import org.bukkit.Bukkit;
+import org.bukkit.GameMode;
+import org.bukkit.Material;
+import org.bukkit.Statistic;
+import org.bukkit.block.ShulkerBox;
+import org.bukkit.craftbukkit.v1_20_R2.CraftWorld;
+import org.bukkit.entity.Player;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public class AnySilentContainer implements IAnySilentContainer {
+
+ private @Nullable Field serverPlayerGameModeGameType;
+
+ public AnySilentContainer() {
+ try {
+ try {
+ this.serverPlayerGameModeGameType = ServerPlayerGameMode.class.getDeclaredField("b");
+ this.serverPlayerGameModeGameType.setAccessible(true);
+ } catch (NoSuchFieldException e) {
+ Logger logger = OpenInv.getPlugin(OpenInv.class).getLogger();
+ 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 logger = OpenInv.getPlugin(OpenInv.class).getLogger();
+ 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 isShulkerBlocked(@NotNull ShulkerBox shulkerBox) {
+ org.bukkit.World bukkitWorld = shulkerBox.getWorld();
+ if (!(bukkitWorld instanceof CraftWorld)) {
+ bukkitWorld = Bukkit.getWorld(bukkitWorld.getUID());
+ }
+
+ if (!(bukkitWorld instanceof CraftWorld craftWorld)) {
+ Exception exception = new IllegalStateException("AnySilentContainer access attempted on an unknown world!");
+ OpenInv.getPlugin(OpenInv.class).getLogger().log(java.util.logging.Level.WARNING, exception.getMessage(), exception);
+ return false;
+ }
+
+ final ServerLevel world = craftWorld.getHandle();
+ final BlockPos blockPosition = new BlockPos(shulkerBox.getX(), shulkerBox.getY(), shulkerBox.getZ());
+ final BlockEntity tile = world.getBlockEntity(blockPosition);
+
+ if (!(tile instanceof ShulkerBoxBlockEntity shulkerBoxBlockEntity)
+ || shulkerBoxBlockEntity.getAnimationStatus() != ShulkerBoxBlockEntity.AnimationStatus.CLOSED) {
+ return false;
+ }
+
+ BlockState blockState = world.getBlockState(blockPosition);
+
+ // See net.minecraft.world.level.block.ShulkerBoxBlock#canOpen
+ AABB boundingBox = Shulker.getProgressDeltaAabb(blockState.getValue(ShulkerBoxBlock.FACING), 0.0F, 0.5F)
+ .move(blockPosition)
+ .deflate(1.0E-6D);
+ return !world.noCollision(boundingBox);
+ }
+
+ @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 = PlayerDataManager.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 = PlayerDataManager.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) {
+ OpenInv.getPlugin(OpenInv.class).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) {
+ OpenInv.getPlugin(OpenInv.class).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 = PlayerDataManager.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) {
+ e.printStackTrace();
+ }
+ }
+
+}
diff --git a/internal/v1_20_R2/src/main/java/com/lishid/openinv/internal/v1_20_R2/OpenPlayer.java b/internal/v1_20_R2/src/main/java/com/lishid/openinv/internal/v1_20_R2/OpenPlayer.java
new file mode 100644
index 00000000..fc70decb
--- /dev/null
+++ b/internal/v1_20_R2/src/main/java/com/lishid/openinv/internal/v1_20_R2/OpenPlayer.java
@@ -0,0 +1,74 @@
+/*
+ * 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_R2;
+
+import com.mojang.logging.LogUtils;
+import java.io.File;
+import net.minecraft.Util;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.nbt.NbtIo;
+import net.minecraft.server.level.ServerPlayer;
+import net.minecraft.world.level.storage.PlayerDataStorage;
+import org.bukkit.craftbukkit.v1_20_R2.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R2.entity.CraftPlayer;
+
+public class OpenPlayer extends CraftPlayer {
+
+ public OpenPlayer(CraftServer server, ServerPlayer entity) {
+ super(server, entity);
+ }
+
+ @Override
+ public void loadData() {
+ // See CraftPlayer#loadData
+ CompoundTag loaded = this.server.getHandle().playerIo.load(this.getHandle());
+ if (loaded != null) {
+ getHandle().readAdditionalSaveData(loaded);
+ }
+ }
+
+ @Override
+ public void saveData() {
+ ServerPlayer player = this.getHandle();
+ // See net.minecraft.world.level.storage.PlayerDataStorage#save(EntityHuman)
+ try {
+ PlayerDataStorage worldNBTStorage = player.server.getPlayerList().playerIo;
+
+ CompoundTag playerData = player.saveWithoutId(new CompoundTag());
+ setExtraData(playerData);
+
+ if (!isOnline()) {
+ // Special case: save old vehicle data
+ CompoundTag oldData = worldNBTStorage.load(player);
+
+ if (oldData != null && oldData.contains("RootVehicle", 10)) {
+ // See net.minecraft.server.PlayerList#a(NetworkManager, EntityPlayer) and net.minecraft.server.EntityPlayer#b(NBTTagCompound)
+ playerData.put("RootVehicle", oldData.getCompound("RootVehicle"));
+ }
+ }
+
+ File file = File.createTempFile(player.getStringUUID() + "-", ".dat", worldNBTStorage.getPlayerDir());
+ NbtIo.writeCompressed(playerData, file);
+ File file1 = new File(worldNBTStorage.getPlayerDir(), player.getStringUUID() + ".dat");
+ File file2 = new File(worldNBTStorage.getPlayerDir(), player.getStringUUID() + ".dat_old");
+ Util.safeReplaceFile(file1, file, file2);
+ } catch (Exception e) {
+ LogUtils.getLogger().warn("Failed to save player data for {}: {}", player.getScoreboardName(), e);
+ }
+ }
+
+}
diff --git a/internal/v1_20_R2/src/main/java/com/lishid/openinv/internal/v1_20_R2/PlayerDataManager.java b/internal/v1_20_R2/src/main/java/com/lishid/openinv/internal/v1_20_R2/PlayerDataManager.java
new file mode 100644
index 00000000..0f3d5ce4
--- /dev/null
+++ b/internal/v1_20_R2/src/main/java/com/lishid/openinv/internal/v1_20_R2/PlayerDataManager.java
@@ -0,0 +1,263 @@
+/*
+ * 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_R2;
+
+import com.lishid.openinv.OpenInv;
+import com.lishid.openinv.internal.IPlayerDataManager;
+import com.lishid.openinv.internal.ISpecialInventory;
+import com.lishid.openinv.internal.OpenInventoryView;
+import com.mojang.authlib.GameProfile;
+import net.minecraft.nbt.CompoundTag;
+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 org.bukkit.Bukkit;
+import org.bukkit.OfflinePlayer;
+import org.bukkit.Server;
+import org.bukkit.craftbukkit.v1_20_R2.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R2.entity.CraftPlayer;
+import org.bukkit.craftbukkit.v1_20_R2.event.CraftEventFactory;
+import org.bukkit.craftbukkit.v1_20_R2.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.logging.Logger;
+
+public class PlayerDataManager implements IPlayerDataManager {
+
+ private @Nullable Field bukkitEntity;
+
+ public PlayerDataManager() {
+ try {
+ bukkitEntity = Entity.class.getDeclaredField("bukkitEntity");
+ } catch (NoSuchFieldException e) {
+ Logger logger = OpenInv.getPlugin(OpenInv.class).getLogger();
+ logger.warning("Unable to obtain field to inject custom save process - players' mounts may be deleted when loaded.");
+ 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 provided Player implementation");
+ }
+
+ return nmsPlayer;
+ }
+
+ @Override
+ public @Nullable Player loadPlayer(@NotNull final OfflinePlayer offline) {
+ // Ensure player has data
+ if (!offline.hasPlayedBefore()) {
+ return null;
+ }
+
+ // Create a profile and entity to load the player data
+ // See net.minecraft.server.players.PlayerList#canPlayerLogin
+ // and net.minecraft.server.network.ServerLoginPacketListenerImpl#handleHello
+ GameProfile profile = new GameProfile(offline.getUniqueId(),
+ offline.getName() != null ? offline.getName() : offline.getUniqueId().toString());
+ MinecraftServer server = ((CraftServer) Bukkit.getServer()).getServer();
+ ServerLevel worldServer = server.getLevel(Level.OVERWORLD);
+
+ if (worldServer == null) {
+ return null;
+ }
+
+ 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);
+
+ // Stop listening for advancement progression - if this is not cleaned up, loading causes a memory leak.
+ entity.getAdvancements().stopListening();
+
+ try {
+ injectPlayer(entity);
+ } catch (IllegalAccessException e) {
+ e.printStackTrace();
+ }
+
+ // Load data. This also reads basic data into the player.
+ // See CraftPlayer#loadData
+ CompoundTag loadedData = server.getPlayerList().playerIo.load(entity);
+
+ if (loadedData == null) {
+ // Exceptions with loading are logged by Mojang.
+ return null;
+ }
+
+ // Also read "extra" data.
+ entity.readAdditionalSaveData(loadedData);
+
+ if (entity.level() == null) {
+ // Paper: Move player to spawn
+ entity.spawnIn(null);
+ }
+
+ // Return the Bukkit entity.
+ return entity.getBukkitEntity();
+ }
+
+ void injectPlayer(ServerPlayer player) throws IllegalAccessException {
+ if (bukkitEntity == null) {
+ return;
+ }
+
+ bukkitEntity.setAccessible(true);
+
+ bukkitEntity.set(player, new OpenPlayer(player.server.server, player));
+ }
+
+ @NotNull
+ @Override
+ public 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) {
+ e.printStackTrace();
+ return player;
+ }
+ }
+
+ @Nullable
+ @Override
+ public InventoryView openInventory(@NotNull Player player, @NotNull ISpecialInventory inventory) {
+
+ ServerPlayer nmsPlayer = getHandle(player);
+
+ if (nmsPlayer.connection == null) {
+ return null;
+ }
+
+ InventoryView view = getView(player, inventory);
+
+ if (view == null) {
+ return player.openInventory(inventory.getBukkitInventory());
+ }
+
+ AbstractContainerMenu container = new CraftContainer(view, nmsPlayer, nmsPlayer.nextContainerCounter()) {
+ @Override
+ public MenuType> getType() {
+ return getContainers(inventory.getBukkitInventory().getSize());
+ }
+ };
+
+ container.setTitle(Component.literal(view.getTitle()));
+ 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();
+
+ }
+
+ private @Nullable InventoryView getView(Player player, ISpecialInventory inventory) {
+ if (inventory instanceof SpecialEnderChest) {
+ return new OpenInventoryView(player, inventory, "container.enderchest", "'s Ender Chest");
+ } else if (inventory instanceof SpecialPlayerInventory) {
+ return new OpenInventoryView(player, inventory, "container.player", "'s Inventory");
+ } else {
+ return null;
+ }
+ }
+
+ 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
+ };
+ }
+
+ @Override
+ public int convertToPlayerSlot(InventoryView view, int rawSlot) {
+ int topSize = view.getTopInventory().getSize();
+ if (topSize <= rawSlot) {
+ // Slot is not inside special inventory, use Bukkit logic.
+ return view.convertSlot(rawSlot);
+ }
+
+ // Main inventory, slots 0-26 -> 9-35
+ if (rawSlot < 27) {
+ return rawSlot + 9;
+ }
+ // Hotbar, slots 27-35 -> 0-8
+ if (rawSlot < 36) {
+ return rawSlot - 27;
+ }
+ // Armor, slots 36-39 -> 39-36
+ if (rawSlot < 40) {
+ return 36 + (39 - rawSlot);
+ }
+ // Off hand
+ if (rawSlot == 40) {
+ return 40;
+ }
+ // Drop slots, "out of inventory"
+ return -1;
+ }
+
+}
diff --git a/internal/v1_20_R2/src/main/java/com/lishid/openinv/internal/v1_20_R2/SpecialEnderChest.java b/internal/v1_20_R2/src/main/java/com/lishid/openinv/internal/v1_20_R2/SpecialEnderChest.java
new file mode 100644
index 00000000..5987e9bf
--- /dev/null
+++ b/internal/v1_20_R2/src/main/java/com/lishid/openinv/internal/v1_20_R2/SpecialEnderChest.java
@@ -0,0 +1,350 @@
+/*
+ * 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_R2;
+
+import com.lishid.openinv.internal.ISpecialEnderChest;
+import java.util.List;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+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_R2.entity.CraftHumanEntity;
+import org.bukkit.craftbukkit.v1_20_R2.inventory.CraftInventory;
+import org.bukkit.entity.HumanEntity;
+import org.bukkit.inventory.InventoryHolder;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+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(PlayerDataManager.getHandle(player));
+ this.inventory = new CraftInventory(this);
+ this.owner = PlayerDataManager.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 = PlayerDataManager.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_R2/src/main/java/com/lishid/openinv/internal/v1_20_R2/SpecialPlayerInventory.java b/internal/v1_20_R2/src/main/java/com/lishid/openinv/internal/v1_20_R2/SpecialPlayerInventory.java
new file mode 100644
index 00000000..3e527ea2
--- /dev/null
+++ b/internal/v1_20_R2/src/main/java/com/lishid/openinv/internal/v1_20_R2/SpecialPlayerInventory.java
@@ -0,0 +1,806 @@
+/*
+ * 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_R2;
+
+import com.google.common.collect.ImmutableList;
+import com.lishid.openinv.internal.ISpecialPlayerInventory;
+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;
+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_R2.entity.CraftHumanEntity;
+import org.bukkit.craftbukkit.v1_20_R2.inventory.CraftInventory;
+import org.bukkit.entity.HumanEntity;
+import org.bukkit.inventory.InventoryHolder;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+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(PlayerDataManager.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 = PlayerDataManager.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/plugin/pom.xml b/plugin/pom.xml
index 358b3962..f5d9eb22 100644
--- a/plugin/pom.xml
+++ b/plugin/pom.xml
@@ -1,5 +1,5 @@
+ unknown
-
- api
- plugin
- internal
- assembly
-
-
-
-
-
all
+
+ api
+ plugin
+ internal/v1_19_R3
+ internal/v1_20_R1
+ internal/v1_20_R2
+ assembly
+
+
+
+
+ default
-
- all
- true
-
+ true
+
+ api
+ plugin
+ assembly
+
-
@@ -63,42 +67,134 @@
+
+
+
+ annotations
+ org.jetbrains
+ provided
+ 24.0.1
+
+
+ spigot-api
+ org.spigotmc
+ provided
+ 1.18.2-R0.1-SNAPSHOT
+
+
+ openinvapi
+ com.lishid
+ compile
+ 4.4.1-SNAPSHOT
+
+
+ openinvplugincore
+ com.lishid
+ compile
+ 4.4.1-SNAPSHOT
+
+
+ com.lishid
+ openinvapi
+
+
+
+
+
+
-
-
- org.apache.maven.plugins
- maven-shade-plugin
- 3.2.2
-
-
-
- *:*
-
-
- META-INF/maven/**
-
-
-
-
-
-
- package
-
- shade
-
-
-
-
+
+
+
+
+ maven-dependency-plugin
+ org.apache.maven.plugins
+ 3.6.0
+
+
+
+ maven-shade-plugin
+
+
+
+
+ com.lishid:openinv*
+
+ **
+
+
+
+ *:*
+
+
+ META-INF/MANIFEST.MF
+
+
+
+ true
+
+
+
+ package
+
+ shade
+
+
+
+ org.apache.maven.plugins
+ 3.5.1
+
+
+
+ maven-compiler-plugin
+ org.apache.maven.plugins
+ 3.11.0
+
+
+
+ maven-assembly-plugin
+ org.apache.maven.plugins
+ 3.6.0
+
-
- maven-compiler-plugin
- 3.8.1
-
-
- 1.8
-
-
-
+
+ net.md-5
+ specialsource-maven-plugin
+ 1.2.4
+
+
+ package
+
+ remap
+
+ remap-obf
+
+ org.spigotmc:minecraft-server:${spigot.version}:txt:maps-mojang
+ true
+ org.spigotmc:spigot:${spigot.version}:jar:remapped-mojang
+ true
+ remapped-obf
+
+
+
+ package
+
+ remap
+
+ remap-spigot
+
+ ${project.build.directory}/${project.artifactId}-${project.version}-remapped-obf.jar
+ org.spigotmc:minecraft-server:${spigot.version}:csrg:maps-spigot
+ org.spigotmc:spigot:${spigot.version}:jar:remapped-obf
+
+
+
+
+
+
diff --git a/scripts/generate_changelog.sh b/scripts/generate_changelog.sh
index 72c823d9..bc234b9d 100644
--- a/scripts/generate_changelog.sh
+++ b/scripts/generate_changelog.sh
@@ -1,6 +1,6 @@
#!/bin/bash
#
-# Copyright (C) 2011-2021 lishid. All rights reserved.
+# 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
@@ -15,14 +15,12 @@
# along with this program. If not, see .
#
-# A script for generating a changelog from Git.
-#
# Note that this script is designed for use in GitHub Actions, and is not
# particularly robust nor configurable. Run from project parent directory.
# Query GitHub for the username of the given email address.
# Falls through to the given author name.
-lookup_email_username() {
+function lookup_email_username() {
lookup=$(curl -G --data-urlencode "q=$1 in:email" https://api.github.com/search/users -H 'Accept: application/vnd.github.v3+json' | grep '"login":' | sed -e 's/^.*": "//g' -e 's/",.*$//g')
if [[ $lookup ]]; then
@@ -32,10 +30,25 @@ lookup_email_username() {
fi
}
+# Get a pretty list of supported Minecraft versions
+function get_minecraft_versions() {
+ readarray -t versions <<< "$(. ./scripts/get_spigot_versions.sh)"
+
+ for version in "${versions[@]}"; do
+ # Append comma if variable is set, then append version
+ minecraft_versions="${minecraft_versions:+${minecraft_versions}, }${version%%-R*}"
+ done
+
+ echo "${minecraft_versions}"
+}
+
+previous_tag=$(git describe --tags --abbrev=0 @^)
+
# Use formatted log to pull authors list
-authors_raw=$(git log --pretty=format:"%ae|%an" "$(git describe --tags --abbrev=0 @^)"..@)
+authors_raw=$(git log --pretty=format:"%ae|%an" "$previous_tag"..@)
readarray -t authors <<<"$authors_raw"
+# Use associative array to map email to author name
declare -A author_data
for author in "${authors[@]}"; do
@@ -55,7 +68,7 @@ for author in "${authors[@]}"; do
done
# Fetch actual formatted changelog
-changelog=$(git log --pretty=format:"%s (%h) - %ae" "$(git describe --tags --abbrev=0 @^)"..@)
+changelog=$(git log --pretty=format:"* %s (%h) - %ae" "$previous_tag"..@)
for author_email in "${!author_data[@]}"; do
# Ignore case when matching
@@ -64,4 +77,6 @@ for author_email in "${!author_data[@]}"; do
changelog=${changelog//$author_email/${author_data[$author_email]}}
done
-echo "GENERATED_CHANGELOG<> "$GITHUB_ENV"
+minecraft_versions=$(get_minecraft_versions)
+
+printf "## Supported Minecraft versions\n%s\n\n## Changelog\n%s" "${minecraft_versions}" "${changelog}"
diff --git a/scripts/get_spigot_versions.sh b/scripts/get_spigot_versions.sh
new file mode 100644
index 00000000..c4e7b264
--- /dev/null
+++ b/scripts/get_spigot_versions.sh
@@ -0,0 +1,52 @@
+#!/bin/bash
+#
+# 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 .
+#
+
+# Note that this script is designed for use in GitHub Actions, and is not
+# particularly robust nor configurable. Run from project parent directory.
+
+# Use a nameref as a cache - maven evaluation is pretty slow.
+# Re-calling the script and relying on it to handle caching is way easier than passing around info.
+declare -a spigot_versions
+
+# We don't care about concatenation - either it's not null and we handle entries or it's null and we instantiate.
+# shellcheck disable=SC2199
+if [[ ${spigot_versions[@]} ]]; then
+ for spigot_version in "${spigot_versions[@]}"; do
+ echo "$spigot_version"
+ done
+ return
+fi
+
+old_maven_opts=$MAVEN_OPTS
+# Add JVM parameters to allow help plugin access to packages it needs.
+export MAVEN_OPTS="$old_maven_opts --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED --add-opens java.base/java.text=ALL-UNNAMED --add-opens java.desktop/java.awt.font=ALL-UNNAMED"
+
+# Pull Spigot dependency information from Maven.
+# Since we only care about Spigot versions, only check modules in the folder internal.
+readarray -t modules <<< "$(mvn help:evaluate -Dexpression=project.modules -q -DforceStdout -P all | grep -oP '(?<=)(internal/.*)(?=)')"
+
+declare -n versions="spigot_versions"
+
+for module in "${modules[@]}"; do
+ # Get Spigot version.
+ spigot_version=$(mvn help:evaluate -Dexpression=spigot.version -q -DforceStdout -P all -pl "$module")
+ versions+=("$spigot_version")
+ echo "$spigot_version"
+done
+
+# Reset JVM parameters
+export MAVEN_OPTS=$old_maven_opts
\ No newline at end of file
diff --git a/scripts/install_spigot_dependencies.sh b/scripts/install_spigot_dependencies.sh
index 09e93e16..61668d42 100644
--- a/scripts/install_spigot_dependencies.sh
+++ b/scripts/install_spigot_dependencies.sh
@@ -1,6 +1,6 @@
#!/bin/bash
#
-# Copyright (C) 2011-2021 lishid. All rights reserved.
+# 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
@@ -15,39 +15,12 @@
# along with this program. If not, see .
#
-# A script for installing required Spigot versions.
-#
-# Note that this script is designed for use in GitHub Actions, and is
-# not particularly robust nor configurable.
-# In its current state, the script must be run from OpenInv's parent
-# project directory and will always install BuildTools to ~/buildtools.
+# Note that this script is designed for use in GitHub Actions, and is not
+# particularly robust nor configurable. Run from project parent directory.
buildtools_dir=~/buildtools
buildtools=$buildtools_dir/BuildTools.jar
-get_spigot_versions () {
- # Get all submodules of internal module
- modules=$(mvn help:evaluate -Dexpression=project.modules -q -DforceStdout -P all -pl internal | grep -oP '(?<=)(.*)(?=<\/string>)')
- for module in "${modules[@]}"; do
-
- # Get number of dependencies declared in pom of specified internal module
- max_index=$(mvn help:evaluate -Dexpression=project.dependencies -q -DforceStdout -P all -pl internal/"$module" | grep -c "")
-
- for ((i=0; i < max_index; i++)); do
- # Get artifactId of dependency
- artifact_id=$(mvn help:evaluate -Dexpression=project.dependencies["$i"].artifactId -q -DforceStdout -P all -pl internal/"$module")
-
- # Ensure dependency is spigot
- if [[ "$artifact_id" == spigot ]]; then
- # Get spigot version
- spigot_version=$(mvn help:evaluate -Dexpression=project.dependencies["$i"].version -q -DforceStdout -P all -pl internal/"$module")
- echo "$spigot_version"
- break
- fi
- done
- done
-}
-
get_buildtools () {
if [[ -d $buildtools_dir && -f $buildtools ]]; then
return
@@ -57,18 +30,21 @@ get_buildtools () {
wget https://hub.spigotmc.org/jenkins/job/BuildTools/lastSuccessfulBuild/artifact/target/BuildTools.jar -O $buildtools
}
-versions=$(get_spigot_versions)
-echo Found Spigot dependencies: "$versions"
+readarray -t versions <<< "$(. ./scripts/get_spigot_versions.sh)"
+echo Found Spigot dependencies: "${versions[@]}"
+
+# Install dependencies aside from Spigot prior to running in offline mode.
+mvn dependency:go-offline -DexcludeArtifactIds=spigot
for version in "${versions[@]}"; do
set -e
exit_code=0
- mvn dependency:get -Dartifact=org.spigotmc:spigot:"$version" -q -o || exit_code=$?
+ mvn dependency:get -Dartifact=org.spigotmc:spigot:"$version":remapped-mojang -q -o || exit_code=$?
if [ $exit_code -ne 0 ]; then
echo Installing missing Spigot version "$version"
- revision=$(echo "$version" | grep -oP '(\d+\.\d+(\.\d+)?)(?=-R[0-9\.]+-SNAPSHOT)')
+ revision=${version%%-R*}
get_buildtools
- java -jar $buildtools -rev "$revision"
+ java -jar $buildtools -rev "$revision" --remapped
else
echo Spigot "$version" is already installed
fi
diff --git a/scripts/set_curseforge_env.sh b/scripts/set_curseforge_env.sh
new file mode 100644
index 00000000..09f08b45
--- /dev/null
+++ b/scripts/set_curseforge_env.sh
@@ -0,0 +1,55 @@
+#!/bin/bash
+#
+# Copyright (C) 2011-2021 lishid. All rights reserved.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, version 3.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+#
+
+# Note that this script is designed for use in GitHub Actions, and is not
+# particularly robust nor configurable. Run from project parent directory.
+
+# Parse Spigot dependency information into major Minecraft versions
+function get_curseforge_minecraft_versions() {
+ readarray -t versions <<< "$(. ./scripts/get_spigot_versions.sh)"
+
+ for version in "${versions[@]}"; do
+ # Parse Minecraft major version
+ version="${version%[.-]"${version#*.*[.-]}"}"
+
+ # Skip already listed versions
+ if [[ "$minecraft_versions" =~ "$version"($|,) ]]; then
+ continue
+ fi
+
+ # Append comma if variable is set, then append version
+ minecraft_versions="${minecraft_versions:+${minecraft_versions},}Minecraft ${version}"
+ done
+
+ echo "${minecraft_versions}"
+}
+
+# Modify provided changelog to not break when inserted into yaml file.
+function get_yaml_safe_changelog() {
+ changelog=$1
+ # Since we're using a flow scalar, newlines need to be doubled.
+ echo "${changelog//
+/
+
+}"
+}
+
+minecraft_versions=$(get_curseforge_minecraft_versions)
+echo "CURSEFORGE_MINECRAFT_VERSIONS=$minecraft_versions" >> "$GITHUB_ENV"
+
+changelog=$(get_yaml_safe_changelog "$1")
+printf "CURSEFORGE_CHANGELOG<> "$GITHUB_ENV"
\ No newline at end of file
diff --git a/scripts/set_release_env.sh b/scripts/set_release_env.sh
new file mode 100644
index 00000000..3d850dcc
--- /dev/null
+++ b/scripts/set_release_env.sh
@@ -0,0 +1,32 @@
+#!/bin/bash
+#
+# Copyright (C) 2011-2021 lishid. All rights reserved.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, version 3.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+#
+
+# Note that this script is designed for use in GitHub Actions, and is not
+# particularly robust nor configurable. Run from project parent directory.
+
+# Get a pretty string of the project's name and version
+# Disable SC warning about variable expansion for this function - those are Maven variables.
+# shellcheck disable=SC2016
+function get_versioned_name() {
+ mvn -q -Dexec.executable=echo -Dexec.args='${project.name} ${project.version}' --non-recursive exec:exec
+}
+
+# Set GitHub environmental variables
+echo "VERSIONED_NAME=$(get_versioned_name)" >> "$GITHUB_ENV"
+
+changelog="$(. ./scripts/generate_changelog.sh)"
+printf "GENERATED_CHANGELOG<> "$GITHUB_ENV"
diff --git a/scripts/tag_release.sh b/scripts/tag_release.sh
new file mode 100644
index 00000000..2e4d022a
--- /dev/null
+++ b/scripts/tag_release.sh
@@ -0,0 +1,37 @@
+#!/bin/bash
+#
+# Copyright (C) 2011-2021 lishid. All rights reserved.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, version 3.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+#
+
+if [[ ! $1 ]]; then
+ echo "Please provide a version string."
+ return
+fi
+
+version="$1"
+snapshot="${version%.*}.$((${version##*.} + 1))-SNAPSHOT"
+
+mvn versions:set -DnewVersion="$version"
+
+git add .
+git commit -S -m "Bump version to $version for release"
+git tag -s "$version" -m "Release $version"
+
+mvn clean package -am -P all
+
+mvn versions:set -DnewVersion="$snapshot"
+
+git add .
+git commit -S -m "Bump version to $snapshot for development"