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 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
CommandAliasesDescription
/openinv [player]oi, inv, openOpen a player's inventory. If unspecified, will select last player opened or own if none opened previously.
/openender [player]oeOpen a player's ender chest. If unspecified, will select last player opened or own if none opened previously.
/searchinv <item> [minAmount]siLists all online players that have a certain item in their inventory.
/searchender <item> [minAmount]seLists all online players that have a certain item in their ender chest.
/searchenchant <[enchantment] [MinLevel]>searchenchantsLists all online players with a specific enchantment.
/anycontainer [check]ac, anychestCheck or toggle the AnyContainer function, allowing opening blocked containers.
/silentcontainer [check]sc, silentchestCheck 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 - 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"