Compare commits

...
37 Commits
Author SHA1 Message Date
Jikoo 3820d2e974 Bump version to 5.3.4 for release 2026-09-23 08:00:57 -04:00
Adam 337ba2c35a Update to 26.3 (#430)
* Some overall code health improvement
* Spigot BlockState handling optimization
2026-09-23 07:58:00 -04:00
Adam fdf7f68c51 Improve player lookups with inconsistent casing (#428) 2026-09-13 17:21:02 +00:00
dependabot[bot] f1253fea6f Bump softprops/action-gh-release from 3.0.2 to 3.0.3 (#423)
Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 3.0.2 to 3.0.3.
- [Release notes](https://github.com/softprops/action-gh-release/releases)
- [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md)
- [Commits](https://github.com/softprops/action-gh-release/compare/v3.0.2...v3.0.3)

---
updated-dependencies:
- dependency-name: softprops/action-gh-release
  dependency-version: 3.0.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-01 17:18:25 -04:00
dependabot[bot] 5af49a6bbd Bump actions/setup-java from 5 to 6 (#422)
Bumps [actions/setup-java](https://github.com/actions/setup-java) from 5 to 6.
- [Release notes](https://github.com/actions/setup-java/releases)
- [Commits](https://github.com/actions/setup-java/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/setup-java
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-01 17:17:55 -04:00
dependabot[bot] e8c01c7893 Bump net.ltgt.errorprone from 5.1.0 to 5.1.1 (#424) 2026-09-01 17:26:24 +00:00
dependabot[bot] 1675952e94 Bump gradle-wrapper from 9.6.1 to 9.7.1 (#425) 2026-09-01 17:26:08 +00:00
dependabot[bot] 35e39ffd3e Bump org.jspecify:jspecify from 1.0.0 to 1.0.1 (#426) 2026-09-01 17:26:01 +00:00
Adam 7da27a0da0 Clean up new errorprone warnings (#419)
MenuType is no longer an enum
Offhand is fine with equality check
InventoryManager really does intend to check if the references match
2026-08-11 16:41:41 +00:00
dependabot[bot]andJikoo 180ceaf7e1 Bump com.gradleup.shadow from 9.4.3 to 9.6.1 (#418)
* Bump com.gradleup.shadow from 9.4.3 to 9.6.1

Bumps [com.gradleup.shadow](https://github.com/GradleUp/shadow) from 9.4.3 to 9.6.1.
- [Release notes](https://github.com/GradleUp/shadow/releases)
- [Commits](https://github.com/GradleUp/shadow/compare/9.4.3...9.6.1)

---
updated-dependencies:
- dependency-name: com.gradleup.shadow
  dependency-version: 9.6.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* Don't bother excluding projects

This is a piece of legacy configuration for the old project structure where modules were reflectively loaded.

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jikoo <jikoo.games@gmail.com>
2026-08-11 16:32:13 +00:00
dependabot[bot] e608233ad9 Bump softprops/action-gh-release from 3.0.1 to 3.0.2 (#416)
Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 3.0.1 to 3.0.2.
- [Release notes](https://github.com/softprops/action-gh-release/releases)
- [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md)
- [Commits](https://github.com/softprops/action-gh-release/compare/v3.0.1...v3.0.2)

---
updated-dependencies:
- dependency-name: softprops/action-gh-release
  dependency-version: 3.0.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-11 08:10:00 -04:00
R00tB33rMan d787516fce Fix search commands reporting only the first matching player (#414)
- Resolves https://github.com/Jikoo/OpenInv/issues/413
- Regression introduced by: https://github.com/Jikoo/OpenInv/pull/311

Before the regression, `SearchInvCommand` used two nested loops: an outer loop
over online players and an inner loop over the inventory contents. The
`break` lived in the **inner** loop, ending the per-item count-up once the
requested threshold was reached.

That commit extracted the inner loop into `SearchHelper.findMatch(...)` but
left the `break` behind. It now sits directly inside the **outer**
`for (Player player : ...)` loop, so the entire player scan terminates at
the first match.

I could be wrong about the intentionality of this `break` statement; however, in my eyes, this looks correct!
2026-07-19 17:40:48 -04:00
dependabot[bot] 50c522b8f6 Bump actions/checkout from 6 to 7 (#409)
Bumps [actions/checkout](https://github.com/actions/checkout) from 6 to 7.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-01 13:21:34 -04:00
dependabot[bot] 0192f5a6a9 Bump gradle-wrapper from 9.5.1 to 9.6.1 (#410) 2026-07-01 17:18:05 +00:00
dependabot[bot] 8123679c46 Bump com.gradleup.shadow from 9.4.2 to 9.4.3 (#411) 2026-07-01 17:17:41 +00:00
dependabot[bot] 1d37aad00a Bump com.google.errorprone:error_prone_core from 2.49.0 to 2.50.0 (#412) 2026-07-01 17:17:25 +00:00
dependabot[bot] 27a6e59682 Bump softprops/action-gh-release from 3.0.0 to 3.0.1 (#408)
Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 3.0.0 to 3.0.1.
- [Release notes](https://github.com/softprops/action-gh-release/releases)
- [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md)
- [Commits](https://github.com/softprops/action-gh-release/compare/v3.0.0...v3.0.1)

---
updated-dependencies:
- dependency-name: softprops/action-gh-release
  dependency-version: 3.0.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-01 13:14:09 -04:00
Jikoo f72e22c2a1 Bump version to 5.3.4-SNAPSHOT for development 2026-06-28 10:24:36 -04:00
Jikoo 55053dacbf Bump version to 5.3.3 for release 2026-06-28 10:24:30 -04:00
KalkaandJikoo dacb07bd41 Update to Minecraft 26.2 (#406)
* Add support for Minecraft 26.2
* Update resource pack version
* Drop support for Minecraft 1.21.6-1.21.8
* Drop internal version listing

---------

Co-authored-by: Jikoo <jikoo.games@gmail.com>
2026-06-28 10:17:18 -04:00
dependabot[bot] 06d3fd7df9 Bump org.slf4j:slf4j-api from 2.0.17 to 2.0.18 (#404) 2026-06-03 01:11:53 +00:00
dependabot[bot] 4acf43b0fe Bump gradle-wrapper from 9.5.0 to 9.5.1 (#402) 2026-06-03 01:11:49 +00:00
dependabot[bot] a570001eaf Bump com.gradleup.shadow from 9.4.1 to 9.4.2 (#403) 2026-06-03 01:10:57 +00:00
Jikoo 70c53521c1 Update release template 2026-05-22 11:01:44 -04:00
Jikoo 9021ef9a22 Explicitly disable Modrinth unfeaturing
Docs suggest that this should not be necessary if featured management is not enabled, but here we are.
2026-05-22 10:55:36 -04:00
Jikoo 722ab4d2a3 Bump version to 5.3.3-SNAPSHOT for development 2026-05-22 10:05:45 -04:00
Jikoo 5f04575780 Bump version to 5.3.2 for release 2026-05-22 10:05:43 -04:00
Jikoo 0f5368b134 Include release notes on Hangar 2026-05-22 10:05:09 -04:00
Camden Cove b43a32139d Fix missed copy sending contents to viewers (#401) 2026-05-18 16:23:34 -04:00
R00tB33rMan 98d819c49a Fix inventory access using incorrect schedulers (#400) 2026-05-17 13:09:59 +00:00
tonyjamesstark 8f058fb206 Change format of searchchest (#393)
Displays name if available, shows one entry per line.
2026-05-04 16:16:59 -04:00
dependabot[bot] 60a32b7af5 Bump softprops/action-gh-release from 2.6.1 to 3.0.0 (#395)
Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2.6.1 to 3.0.0.
- [Release notes](https://github.com/softprops/action-gh-release/releases)
- [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md)
- [Commits](https://github.com/softprops/action-gh-release/compare/v2.6.1...v3.0.0)

---
updated-dependencies:
- dependency-name: softprops/action-gh-release
  dependency-version: 3.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-01 18:52:41 -04:00
dependabot[bot] 61f7d5cee5 Bump actions/github-script from 8 to 9 (#394)
Bumps [actions/github-script](https://github.com/actions/github-script) from 8 to 9.
- [Release notes](https://github.com/actions/github-script/releases)
- [Commits](https://github.com/actions/github-script/compare/v8...v9)

---
updated-dependencies:
- dependency-name: actions/github-script
  dependency-version: '9'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-01 18:52:06 -04:00
dependabot[bot] 3d65681c6f Bump com.google.errorprone:error_prone_core from 2.48.0 to 2.49.0 (#398) 2026-05-01 20:43:44 +00:00
dependabot[bot] cf2f53f9e9 Bump gradle-wrapper from 9.4.1 to 9.5.0 (#397) 2026-05-01 20:43:22 +00:00
dependabot[bot] 95ae1cfa0a Bump net.ltgt.errorprone from 4.4.0 to 5.1.0 (#396) 2026-05-01 20:41:17 +00:00
Jikoo 07451de7a3 Bump version to 5.3.2-SNAPSHOT for development 2026-04-21 21:34:03 -04:00
174 changed files with 4374 additions and 3848 deletions

No files matched your search

+1 -1
View File
@@ -15,7 +15,7 @@ jobs:
# 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@v8
uses: actions/github-script@v9
with:
script: |
let allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({
+2 -2
View File
@@ -15,9 +15,9 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- uses: actions/setup-java@v5
- uses: actions/setup-java@v6
with:
distribution: 'temurin'
java-version: '25'
+5 -3
View File
@@ -20,17 +20,19 @@ jobs:
- name: Create Release
id: create-release
uses: softprops/action-gh-release@v2.6.1
uses: softprops/action-gh-release@v3.0.3
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
name: OpenInv ${{ env.VERSIONED_NAME }}
name: "OpenInv ${{ github.ref_name }}"
body: |-
## Supported server versions
**Paper:** TODO VERSION, 1.21.8, 1.21.7, 1.21.6, 1.21.5, 1.21.4, 1.21.3, 1.21.1
**Paper:** TODO VERSION, 26.1.2, 1.21.11, 1.21.10, 1.21.9, 1.21.8, 1.21.7, 1.21.6
**Spigot:** TODO VERSION
TODO HELLO HUMAN, PRESS THE GENERATE CHANGELOG BUTTON PLEASE.
Don't forget to add a newline after the resulting header and prettify the tag compare link!
draft: true
prerelease: false
files: ./dist/**
+2
View File
@@ -59,6 +59,7 @@ jobs:
with:
modrinth-id: 1UlvXbzL
modrinth-featured: false
modrinth-unfeature-mode: none
modrinth-token: ${{ secrets.MODRINTH_TOKEN }}
files: ./OpenInv.jar
game-versions: "${{ steps.parse_versions.outputs.PAPER_VERSIONS }}"
@@ -98,6 +99,7 @@ jobs:
slug: 'OpenInv'
version: ${{ github.event.release.tag_name }}
channel: release
description: "${{ github.event.release.body }}"
files: |-
[
{
+1 -1
View File
@@ -13,7 +13,7 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- name: Build resource pack
id: upload-resource-pack
@@ -24,8 +24,8 @@ import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
import org.bukkit.inventory.InventoryView;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
import java.util.UUID;
import java.util.logging.Logger;
@@ -33,6 +33,7 @@ import java.util.logging.Logger;
/**
* Interface defining behavior for the OpenInv plugin.
*/
@NullMarked
public interface IOpenInv {
/**
@@ -76,7 +77,7 @@ public interface IOpenInv {
* @return the active implementation for the server version
* @throws IllegalStateException if the server version is unsupported
*/
@NotNull IAnySilentContainer getAnySilentContainer();
IAnySilentContainer getAnySilentContainer();
/**
* Get whether a user has AnyContainer mode enabled.
@@ -84,7 +85,7 @@ public interface IOpenInv {
* @param offline the user to obtain the state of
* @return true if AnyContainer mode is enabled
*/
boolean getAnyContainerStatus(@NotNull OfflinePlayer offline);
boolean getAnyContainerStatus(OfflinePlayer offline);
/**
* Set whether a user has AnyContainer mode enabled.
@@ -92,7 +93,7 @@ public interface IOpenInv {
* @param offline the user to set the state of
* @param status the state of the mode
*/
void setAnyContainerStatus(@NotNull OfflinePlayer offline, boolean status);
void setAnyContainerStatus(OfflinePlayer offline, boolean status);
/**
* Get whether a user has SilentContainer mode enabled.
@@ -100,7 +101,7 @@ public interface IOpenInv {
* @param offline the user to obtain the state of
* @return true if SilentContainer mode is enabled
*/
boolean getSilentContainerStatus(@NotNull OfflinePlayer offline);
boolean getSilentContainerStatus(OfflinePlayer offline);
/**
* Set whether a user has SilentContainer mode enabled.
@@ -108,7 +109,7 @@ public interface IOpenInv {
* @param offline the user to set the state of
* @param status the state of the mode
*/
void setSilentContainerStatus(@NotNull OfflinePlayer offline, boolean status);
void setSilentContainerStatus(OfflinePlayer offline, boolean status);
/**
* Get an {@link ISpecialEnderChest} for a user.
@@ -119,8 +120,8 @@ public interface IOpenInv {
* @throws IllegalStateException if the server version is unsupported
* @throws InstantiationException if there was an issue creating the inventory
*/
@NotNull ISpecialEnderChest getSpecialEnderChest(
@NotNull Player player,
ISpecialEnderChest getSpecialEnderChest(
Player player,
boolean online
) throws InstantiationException;
@@ -133,8 +134,8 @@ public interface IOpenInv {
* @throws IllegalStateException if the server version is unsupported
* @throws InstantiationException if there was an issue creating the inventory
*/
@NotNull ISpecialPlayerInventory getSpecialInventory(
@NotNull Player player,
ISpecialPlayerInventory getSpecialInventory(
Player player,
boolean online
) throws InstantiationException;
@@ -142,7 +143,7 @@ public interface IOpenInv {
* @deprecated Use {@link #openInventory(Player, ISpecialInventory, boolean)}
*/
@Deprecated(forRemoval = true, since = "5.2.0")
@Nullable InventoryView openInventory(@NotNull Player player, @NotNull ISpecialInventory inventory);
@Nullable InventoryView openInventory(Player player, ISpecialInventory inventory);
/**
* Open an {@link ISpecialInventory} for a {@link Player}.
@@ -152,7 +153,7 @@ public interface IOpenInv {
* @param viewOnly whether the inventory should be view-only
* @return the resulting {@link InventoryView}
*/
@Nullable InventoryView openInventory(@NotNull Player player, @NotNull ISpecialInventory inventory, boolean viewOnly);
@Nullable InventoryView openInventory(Player player, ISpecialInventory inventory, boolean viewOnly);
/**
* Check if a {@link Player} is currently loaded by OpenInv.
@@ -161,7 +162,7 @@ public interface IOpenInv {
* @return whether the {@code Player} is loaded
* @since 4.2.0
*/
boolean isPlayerLoaded(@NotNull UUID playerUuid);
boolean isPlayerLoaded(UUID playerUuid);
/**
* Load a {@link Player} from an {@link OfflinePlayer}. If the user has not played before or the default world for
@@ -171,7 +172,7 @@ public interface IOpenInv {
* @return the loaded {@code Player}
* @throws IllegalStateException if the server version is unsupported
*/
@Nullable Player loadPlayer(@NotNull final OfflinePlayer offline);
@Nullable Player loadPlayer(final OfflinePlayer offline);
/**
* Match an existing {@link OfflinePlayer}. If the name is a {@link UUID#toString() UUID string}, this will only
@@ -184,14 +185,14 @@ public interface IOpenInv {
* @param name the string to match
* @return the user with the closest matching name
*/
@Nullable OfflinePlayer matchPlayer(@NotNull String name);
@Nullable OfflinePlayer matchPlayer(String name);
/**
* Forcibly close inventories of and unload any cached data for a user.
*
* @param offline the {@link OfflinePlayer} to unload
*/
void unload(@NotNull OfflinePlayer offline);
void unload(OfflinePlayer offline);
Logger getLogger();
@@ -5,11 +5,12 @@ import com.lishid.openinv.internal.ISpecialInventory;
import org.bukkit.entity.Player;
import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jspecify.annotations.NullMarked;
/**
* Event fired before OpenInv saves a player's data when closing an {@link ISpecialInventory}.
*/
@NullMarked
public class OpenPlayerSaveEvent extends PlayerSaveEvent {
private static final HandlerList HANDLERS = new HandlerList();
@@ -26,10 +27,11 @@ public class OpenPlayerSaveEvent extends PlayerSaveEvent {
*/
@RestrictedApi(
explanation = "Constructor is not considered part of the API and may be subject to change.",
link = "",
allowedOnPath = ".*/com/lishid/openinv/event/OpenEvents.java"
)
@ApiStatus.Internal
OpenPlayerSaveEvent(@NotNull Player player, @NotNull ISpecialInventory inventory) {
OpenPlayerSaveEvent(Player player, ISpecialInventory inventory) {
super(player);
this.inventory = inventory;
}
@@ -39,11 +41,10 @@ public class OpenPlayerSaveEvent extends PlayerSaveEvent {
*
* @return the special inventory
*/
public @NotNull ISpecialInventory getInventory() {
public ISpecialInventory getInventory() {
return inventory;
}
@NotNull
@Override
public HandlerList getHandlers() {
return HANDLERS;
@@ -7,11 +7,12 @@ import org.bukkit.event.Cancellable;
import org.bukkit.event.HandlerList;
import org.bukkit.event.player.PlayerEvent;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jspecify.annotations.NullMarked;
/**
* Event fired before a {@link Player} loaded via OpenInv is saved.
*/
@NullMarked
public class PlayerSaveEvent extends PlayerEvent implements Cancellable {
private static final HandlerList HANDLERS = new HandlerList();
@@ -27,10 +28,11 @@ public class PlayerSaveEvent extends PlayerEvent implements Cancellable {
*/
@RestrictedApi(
explanation = "Constructor is not considered part of the API and may be subject to change.",
link = "",
allowedOnPath = ".*/com/lishid/openinv/event/(OpenPlayerSaveEvent|OpenEvents).java"
)
@ApiStatus.Internal
PlayerSaveEvent(@NotNull Player player) {
PlayerSaveEvent(Player player) {
super(player);
}
@@ -54,7 +56,6 @@ public class PlayerSaveEvent extends PlayerEvent implements Cancellable {
this.cancelled = cancel;
}
@NotNull
@Override
public HandlerList getHandlers() {
return HANDLERS;
@@ -5,27 +5,29 @@ import com.lishid.openinv.util.setting.PlayerToggle;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jspecify.annotations.NullMarked;
import java.util.UUID;
/**
* Event fired after OpenInv modifies a toggleable setting for a player.
*/
@NullMarked
public class PlayerToggledEvent extends Event {
private static final HandlerList HANDLERS = new HandlerList();
private final @NotNull PlayerToggle toggle;
private final @NotNull UUID uuid;
private final PlayerToggle toggle;
private final UUID uuid;
private final boolean enabled;
@RestrictedApi(
explanation = "Constructor is not considered part of the API and may be subject to change.",
link = "",
allowedOnPath = ".*/com/lishid/openinv/event/OpenEvents.java"
)
@ApiStatus.Internal
PlayerToggledEvent(@NotNull PlayerToggle toggle, @NotNull UUID uuid, boolean enabled) {
PlayerToggledEvent(PlayerToggle toggle, UUID uuid, boolean enabled) {
this.toggle = toggle;
this.uuid = uuid;
this.enabled = enabled;
@@ -36,7 +38,7 @@ public class PlayerToggledEvent extends Event {
*
* @return the toggle
*/
public @NotNull PlayerToggle getToggle() {
public PlayerToggle getToggle() {
return toggle;
}
@@ -45,7 +47,7 @@ public class PlayerToggledEvent extends Event {
*
* @return the player ID
*/
public @NotNull UUID getPlayerId() {
public UUID getPlayerId() {
return uuid;
}
@@ -58,7 +60,6 @@ public class PlayerToggledEvent extends Event {
return enabled;
}
@NotNull
@Override
public HandlerList getHandlers() {
return HANDLERS;
@@ -26,8 +26,10 @@ import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryHolder;
import org.bukkit.util.BoundingBox;
import org.jetbrains.annotations.NotNull;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
@NullMarked
public interface IAnySilentContainer {
/**
@@ -39,14 +41,14 @@ public interface IAnySilentContainer {
* @param block the {@link Block} of the container
* @return true if the container can be opened
*/
boolean activateContainer(@NotNull Player player, boolean silent, @NotNull Block block);
boolean activateContainer(Player player, boolean silent, Block block);
/**
* Perform operations required to close the current container silently.
*
* @param player the {@link Player} closing a container
*/
void deactivateContainer(@NotNull Player player);
void deactivateContainer(Player player);
/**
* Check if the container at the given coordinates is blocked.
@@ -54,7 +56,7 @@ public interface IAnySilentContainer {
* @param block the {@link Block} of the container
* @return true if the container is blocked
*/
boolean isAnyContainerNeeded(@NotNull Block block);
boolean isAnyContainerNeeded(Block block);
/**
* Check if a shulker box block cannot be opened under ordinary circumstances.
@@ -62,7 +64,7 @@ public interface IAnySilentContainer {
* @param shulkerBox the shulker box block
* @return whether the container is blocked
*/
default boolean isShulkerBlocked(@NotNull Block shulkerBox) {
default boolean isShulkerBlocked(Block shulkerBox) {
Directional directional = (Directional) shulkerBox.getBlockData();
BlockFace facing = directional.getFacing();
// Construct a new 1-block bounding box at the origin.
@@ -81,7 +83,7 @@ public interface IAnySilentContainer {
* @param chest the chest block
* @return whether the container is blocked
*/
default boolean isChestBlocked(@NotNull Block chest) {
default boolean isChestBlocked(Block chest) {
org.bukkit.block.Block relative = chest.getRelative(0, 1, 0);
return relative.getType().isOccluding()
|| !chest.getWorld().getNearbyEntities(BoundingBox.of(relative), Cat.class::isInstance).isEmpty();
@@ -93,7 +95,7 @@ public interface IAnySilentContainer {
* @param block the potential container
* @return true if the type is a supported container
*/
boolean isAnySilentContainer(@NotNull Block block);
boolean isAnySilentContainer(Block block);
/**
* Check if the given {@link BlockState} is a container which can be unblocked or silenced.
@@ -101,7 +103,7 @@ public interface IAnySilentContainer {
* @param blockState the potential container
* @return true if the type is a supported container
*/
default boolean isAnySilentContainer(@NotNull BlockState blockState) {
default boolean isAnySilentContainer(BlockState blockState) {
return (blockState instanceof InventoryHolder holder && isAnySilentContainer(holder))
|| blockState instanceof EnderChest;
}
@@ -112,7 +114,7 @@ public interface IAnySilentContainer {
* @param holder the potential container
* @return true if the type is a supported container
*/
default boolean isAnySilentContainer(@NotNull InventoryHolder holder) {
default boolean isAnySilentContainer(@Nullable InventoryHolder holder) {
return holder instanceof org.bukkit.block.EnderChest
|| holder instanceof org.bukkit.block.Chest
|| holder instanceof org.bukkit.block.DoubleChest
@@ -126,6 +128,6 @@ public interface IAnySilentContainer {
* @param inventory the potential container inventory
* @return true if the type is a supported container
*/
boolean isAnySilentContainer(@NotNull Inventory inventory);
boolean isAnySilentContainer(Inventory inventory);
}
@@ -17,15 +17,16 @@
package com.lishid.openinv.internal;
import org.bukkit.event.inventory.InventoryType;
import org.jetbrains.annotations.NotNull;
import org.jspecify.annotations.NullMarked;
/**
* An {@link ISpecialInventory} representing an ender chest.
*/
@NullMarked
public interface ISpecialEnderChest extends ISpecialInventory {
@Override
default @NotNull InventoryType getBukkitType() {
default InventoryType getBukkitType() {
return InventoryType.ENDER_CHEST;
}
@@ -20,11 +20,12 @@ import org.bukkit.entity.HumanEntity;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.InventoryType;
import org.bukkit.inventory.Inventory;
import org.jetbrains.annotations.NotNull;
import org.jspecify.annotations.NullMarked;
/**
* Interface defining behavior for special inventories backed by other inventories' content listings.
*/
@NullMarked
public interface ISpecialInventory {
/**
@@ -32,21 +33,21 @@ public interface ISpecialInventory {
*
* @return the Bukkit inventory
*/
@NotNull Inventory getBukkitInventory();
Inventory getBukkitInventory();
/**
* Get the {@link InventoryType} corresponding to this {@code ISpecialInventory}.
*
* @return the type of Bukkit inventory
*/
@NotNull InventoryType getBukkitType();
InventoryType getBukkitType();
/**
* Set the owning {@link Player} instance to a newly-joined user.
*
* @param player the user coming online
*/
void setPlayerOnline(@NotNull Player player);
void setPlayerOnline(Player player);
/**
* Mark the owner of the inventory offline.
@@ -70,6 +71,6 @@ public interface ISpecialInventory {
*
* @return the {@link HumanEntity} who owns the inventory
*/
@NotNull HumanEntity getPlayer();
HumanEntity getPlayer();
}
@@ -17,15 +17,16 @@
package com.lishid.openinv.internal;
import org.bukkit.event.inventory.InventoryType;
import org.jetbrains.annotations.NotNull;
import org.jspecify.annotations.NullMarked;
/**
* An {@link ISpecialInventory} representing a player inventory.
*/
@NullMarked
public interface ISpecialPlayerInventory extends ISpecialInventory {
@Override
default @NotNull InventoryType getBukkitType() {
default InventoryType getBukkitType() {
return InventoryType.PLAYER;
}
@@ -22,14 +22,15 @@ import com.lishid.openinv.internal.ISpecialInventory;
import com.lishid.openinv.internal.ISpecialPlayerInventory;
import org.bukkit.inventory.Inventory;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
import java.util.function.BiFunction;
@NullMarked
public final class InventoryAccess {
private static @Nullable BiFunction<Inventory, Class<? extends ISpecialInventory>, ISpecialInventory> provider;
private static @Nullable BiFunction<Inventory, Class<? extends ISpecialInventory>, @Nullable ISpecialInventory> provider;
public static boolean isUsable() {
return provider != null;
@@ -41,7 +42,7 @@ public final class InventoryAccess {
* @param inventory the Bukkit inventory
* @return true if backed by the correct implementation
*/
public static boolean isPlayerInventory(@NotNull Inventory inventory) {
public static boolean isPlayerInventory(Inventory inventory) {
return getPlayerInventory(inventory) != null;
}
@@ -52,7 +53,7 @@ public final class InventoryAccess {
* @param inventory the Bukkit inventory
* @return the backing implementation if available
*/
public static @Nullable ISpecialPlayerInventory getPlayerInventory(@NotNull Inventory inventory) {
public static @Nullable ISpecialPlayerInventory getPlayerInventory(Inventory inventory) {
return provider == null ? null : (ISpecialPlayerInventory) provider.apply(inventory, ISpecialPlayerInventory.class);
}
@@ -62,7 +63,7 @@ public final class InventoryAccess {
* @param inventory the Bukkit inventory
* @return true if backed by the correct implementation
*/
public static boolean isEnderChest(@NotNull Inventory inventory) {
public static boolean isEnderChest(Inventory inventory) {
return getEnderChest(inventory) != null;
}
@@ -73,7 +74,7 @@ public final class InventoryAccess {
* @param inventory the Bukkit inventory
* @return the backing implementation if available
*/
public static @Nullable ISpecialEnderChest getEnderChest(@NotNull Inventory inventory) {
public static @Nullable ISpecialEnderChest getEnderChest(Inventory inventory) {
return provider == null ? null : (ISpecialEnderChest) provider.apply(inventory, ISpecialEnderChest.class);
}
@@ -84,17 +85,18 @@ public final class InventoryAccess {
* @param inventory the Bukkit inventory
* @return the backing implementation if available
*/
public static @Nullable ISpecialInventory getInventory(@NotNull Inventory inventory) {
public static @Nullable ISpecialInventory getInventory(Inventory inventory) {
return provider == null ? null : provider.apply(inventory, ISpecialInventory.class);
}
@RestrictedApi(
explanation = "Not part of the API.",
link = "",
allowedOnPath = ".*/com/lishid/openinv/util/InternalAccessor.java"
)
@ApiStatus.Internal
static void setProvider(
@Nullable BiFunction<Inventory, Class<? extends ISpecialInventory>, ISpecialInventory> provider
@Nullable BiFunction<Inventory, Class<? extends ISpecialInventory>, @Nullable ISpecialInventory> provider
) {
InventoryAccess.provider = provider;
}
@@ -17,7 +17,7 @@
package com.lishid.openinv.util;
import org.bukkit.permissions.Permissible;
import org.jetbrains.annotations.NotNull;
import org.jspecify.annotations.NullMarked;
/**
* An enum containing all permissions directly checked by OpenInv.
@@ -25,6 +25,7 @@ import org.jetbrains.annotations.NotNull;
* <p>Note that this is not an exhaustive list! This does not contain
* all permissions managed by Bukkit, largely parent nodes.</p>
*/
@NullMarked
public enum Permissions {
/// Permission to open one's own inventory.
@@ -121,7 +122,7 @@ public enum Permissions {
* @param permissible the Permissible
* @return true if the permission is granted
*/
public boolean hasPermission(@NotNull Permissible permissible) {
public boolean hasPermission(Permissible permissible) {
return permissible.hasPermission(permission);
}
@@ -1,13 +1,14 @@
package com.lishid.openinv.util.setting;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jspecify.annotations.NullMarked;
import java.util.UUID;
/**
* A per-player setting that may be enabled or disabled.
*/
@NullMarked
public interface PlayerToggle {
/**
@@ -15,7 +16,7 @@ public interface PlayerToggle {
*
* @return the setting name
*/
@NotNull String getName();
String getName();
/**
* Get the state of the toggle for a particular player ID.
@@ -23,7 +24,7 @@ public interface PlayerToggle {
* @param uuid the player ID
* @return true if the setting is enabled
*/
boolean is(@NotNull UUID uuid);
boolean is(UUID uuid);
/**
* Get the state of the toggle for a particular {@link Player},
@@ -32,7 +33,7 @@ public interface PlayerToggle {
* @param player the player
* @return true if the setting is enabled and the player has the required permissions
*/
boolean is(@NotNull Player player);
boolean is(Player player);
/**
* Set the state of the toggle for a particular player ID.
@@ -41,6 +42,6 @@ public interface PlayerToggle {
* @param enabled whether the setting is enabled
* @return true if the setting changed as a result of being set
*/
boolean set(@NotNull UUID uuid, boolean enabled);
boolean set(UUID uuid, boolean enabled);
}
@@ -2,9 +2,9 @@ package com.lishid.openinv.util.setting;
import com.lishid.openinv.util.Permissions;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.UnmodifiableView;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
import java.util.Collection;
import java.util.Collections;
@@ -18,19 +18,20 @@ import java.util.UUID;
/**
* Utility class containing all of OpenInv's {@link PlayerToggle PlayerToggles}.
*/
@NullMarked
public final class PlayerToggles {
private static final Map<String, PlayerToggle> TOGGLES = new HashMap<>();
private static final PlayerToggle ANY = add(new MemoryToggle("AnyContainer") {
@Override
public boolean is(@NotNull Player player) {
public boolean is(Player player) {
return is(player.getUniqueId())
&& (Permissions.CONTAINER_ANY.hasPermission(player) || Permissions.CONTAINER_ANY_USE.hasPermission(player));
}
});
private static final PlayerToggle SILENT = add(new MemoryToggle("SilentContainer") {
@Override
public boolean is(@NotNull Player player) {
public boolean is(Player player) {
return is(player.getUniqueId())
&& (Permissions.CONTAINER_SILENT.hasPermission(player) || Permissions.CONTAINER_SILENT_USE.hasPermission(player));
}
@@ -41,7 +42,7 @@ public final class PlayerToggles {
*
* @return the AnyContainer toggle
*/
public static @NotNull PlayerToggle any() {
public static PlayerToggle any() {
return ANY;
}
@@ -50,7 +51,7 @@ public final class PlayerToggles {
*
* @return the SilentContainer toggle
*/
public static @NotNull PlayerToggle silent() {
public static PlayerToggle silent() {
return SILENT;
}
@@ -60,7 +61,7 @@ public final class PlayerToggles {
* @param toggleName the name of the toggle
* @return the toggle, or null if no such toggle exists.
*/
public static @Nullable PlayerToggle get(@NotNull String toggleName) {
public static @Nullable PlayerToggle get(String toggleName) {
PlayerToggle toggle = TOGGLES.get(toggleName);
if (toggle == null) {
toggle = TOGGLES.get(toggleName.toLowerCase(Locale.ENGLISH));
@@ -73,11 +74,11 @@ public final class PlayerToggles {
*
* @return a view of all toggles available
*/
public static @UnmodifiableView @NotNull Collection<PlayerToggle> get() {
public static @UnmodifiableView Collection<PlayerToggle> get() {
return Collections.unmodifiableCollection(TOGGLES.values());
}
private static @NotNull PlayerToggle add(@NotNull PlayerToggle toggle) {
private static PlayerToggle add(PlayerToggle toggle) {
TOGGLES.put(toggle.getName().toLowerCase(Locale.ENGLISH), toggle);
return toggle;
}
@@ -88,26 +89,26 @@ public final class PlayerToggles {
private static abstract class MemoryToggle implements PlayerToggle {
private final @NotNull Set<UUID> enabled;
private final @NotNull String name;
private final Set<UUID> enabled;
private final String name;
private MemoryToggle(@NotNull String name) {
private MemoryToggle(String name) {
enabled = new HashSet<>();
this.name = name;
}
@Override
public @NotNull String getName() {
public String getName() {
return this.name;
}
@Override
public boolean is(@NotNull UUID uuid) {
public boolean is(UUID uuid) {
return enabled.contains(uuid);
}
@Override
public boolean set(@NotNull UUID uuid, boolean enabled) {
public boolean set(UUID uuid, boolean enabled) {
if (enabled) {
return this.enabled.add(uuid);
} else {
@@ -0,0 +1,28 @@
package com.github.jikoo.openinv.internal;
import com.lishid.openinv.internal.IAnySilentContainer;
import com.lishid.openinv.internal.ISpecialEnderChest;
import com.lishid.openinv.internal.ISpecialInventory;
import com.lishid.openinv.internal.ISpecialPlayerInventory;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
@NullMarked
public interface Accessor {
PlayerManager getPlayerManager();
IAnySilentContainer getAnySilentContainer();
ISpecialPlayerInventory createPlayerInventory(Player player);
ISpecialEnderChest createEnderChest(Player player);
<T extends ISpecialInventory> @Nullable T get(Inventory bukkitInventory, Class<T> clazz);
void reload(ConfigurationSection config);
}
@@ -14,14 +14,16 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.lishid.openinv.internal;
package com.github.jikoo.openinv.internal;
import com.lishid.openinv.internal.ISpecialInventory;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
import org.bukkit.inventory.InventoryView;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
@NullMarked
public interface PlayerManager {
/**
@@ -32,14 +34,14 @@ public interface PlayerManager {
* @param offline the OfflinePlayer
* @return the Player loaded
*/
@Nullable Player loadPlayer(@NotNull OfflinePlayer offline);
@Nullable Player loadPlayer(OfflinePlayer offline);
/**
* Creates a new Player from an existing one that will function slightly better offline.
*
* @return the Player
*/
@NotNull Player inject(@NotNull Player player);
Player inject(Player player);
/**
* Opens an ISpecialInventory for a Player.
@@ -49,6 +51,6 @@ public interface PlayerManager {
*
* @return the InventoryView opened
*/
@Nullable InventoryView openInventory(@NotNull Player player, @NotNull ISpecialInventory inventory, boolean viewOnly);
@Nullable InventoryView openInventory(Player player, ISpecialInventory inventory, boolean viewOnly);
}
@@ -0,0 +1,107 @@
package com.github.jikoo.openinv.internal.container;
import com.lishid.openinv.internal.IAnySilentContainer;
import org.bukkit.block.Barrel;
import org.bukkit.block.Block;
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.type.Chest;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryHolder;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
@NullMarked
public abstract class AnySilentContainerBase implements IAnySilentContainer {
public enum StateType {
ENDER_CHEST,
SHULKER_BOX,
CHEST,
BARREL,
OTHER
}
@Override
public boolean isAnyContainerNeeded(Block block) {
return isAnyContainerNeeded(getStateType(block), block);
}
protected boolean isAnyContainerNeeded(StateType material, Block block) {
return switch (material) {
// Enderchests require a non-occluding block on top to open.
case ENDER_CHEST -> block.getRelative(0, 1, 0).getType().isOccluding();
// Shulker boxes require half a block clear in the direction they open.
case SHULKER_BOX -> isShulkerBlocked(block);
// Chests require that the block above not be occluding or occupied by a cat.
case CHEST -> isChestBlocked(block);
// Barrels need silent but not any. All others unsupported.
default -> false;
};
}
@Override
public boolean isChestBlocked(Block block) {
if (IAnySilentContainer.super.isChestBlocked(block)) {
return true;
}
BlockData blockData = block.getBlockData();
if (!(blockData instanceof Chest chest) || chest.getType() == Chest.Type.SINGLE) {
return false;
}
BlockFace relativeFace = switch (chest.getFacing()) {
case NORTH -> chest.getType() == Chest.Type.RIGHT ? BlockFace.WEST : BlockFace.EAST;
case EAST -> chest.getType() == Chest.Type.RIGHT ? BlockFace.NORTH : BlockFace.SOUTH;
case SOUTH -> chest.getType() == Chest.Type.RIGHT ? BlockFace.EAST : BlockFace.WEST;
case WEST -> chest.getType() == Chest.Type.RIGHT ? BlockFace.SOUTH : BlockFace.NORTH;
default -> BlockFace.SELF;
};
Block relative = block.getRelative(relativeFace);
if (relative.getType() != block.getType()) {
return false;
}
BlockData relativeData = relative.getBlockData();
if (!(relativeData instanceof Chest relativeChest)) {
return false;
}
if (relativeChest.getFacing() != chest.getFacing()
|| relativeChest.getType() != (chest.getType() == Chest.Type.RIGHT ? Chest.Type.LEFT : Chest.Type.RIGHT)) {
return false;
}
return IAnySilentContainer.super.isChestBlocked(relative);
}
@Override
public boolean isAnySilentContainer(Block block) {
return isAnySilentContainer(getState(block));
}
@Override
public boolean isAnySilentContainer(Inventory inventory) {
return isAnySilentContainer(getHolder(inventory));
}
protected StateType getStateType(Block block) {
return switch (getState(block)) {
case Barrel ignored -> StateType.BARREL;
case EnderChest ignored -> StateType.ENDER_CHEST;
case ShulkerBox ignored -> StateType.SHULKER_BOX;
case org.bukkit.block.Chest ignored -> StateType.CHEST;
default -> StateType.OTHER;
};
}
protected abstract BlockState getState(Block block);
protected abstract @Nullable InventoryHolder getHolder(Inventory inventory);
}
@@ -0,0 +1,10 @@
package com.github.jikoo.openinv.internal.container;
import org.jspecify.annotations.NullMarked;
@NullMarked
public interface InternalOwned<T> {
T getOwnerHandle();
}
@@ -0,0 +1,4 @@
package com.github.jikoo.openinv.internal.container;
public interface ViewOnly {
}
@@ -1,53 +1,50 @@
package com.lishid.openinv.internal.paper26_1.container.slot;
package com.github.jikoo.openinv.internal.container.slot;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import org.jetbrains.annotations.NotNull;
import org.jspecify.annotations.NullMarked;
/**
* An interface defining behaviors for entries in a {@link Container}. Used to reduce duplicate content reordering.
* An interface defining behaviors for entries in a {@link net.minecraft.world.Container Container}. Used to reduce duplicate content reordering.
*/
public interface Content {
@NullMarked
public interface Content<P, I, C, S> {
/**
* Update internal holder.
*
* @param holder the new holder
*/
void setHolder(@NotNull ServerPlayer holder);
void setHolder(P holder);
/**
* Get the current item.
*
* @return the current item
*/
ItemStack get();
I get();
/**
* Remove the current item.
*
* @return the current item
*/
ItemStack remove();
I remove();
/**
* Remove some of the current item.
*
* @return the current item
*/
ItemStack removePartial(int amount);
I removePartial(int amount);
/**
* Set the current item. If slot is currently not usable, will drop item instead.
*
* @param itemStack the item to set
*/
void set(ItemStack itemStack);
void set(I itemStack);
/**
* Get a {@link Slot} for use in a {@link net.minecraft.world.inventory.AbstractContainerMenu ContainerMenu}. Will
* Get a {@link net.minecraft.world.inventory.Slot Slot} for use in a {@link net.minecraft.world.inventory.AbstractContainerMenu ContainerMenu}. Will
* impose any specific restrictions to insertion or removal.
*
* @param container the backing container
@@ -56,7 +53,7 @@ public interface Content {
* @param y clientside y dimension from top left of inventory, not used
* @return a menu slot
*/
Slot asSlot(Container container, int slot, int x, int y);
S asSlot(C container, int slot, int x, int y);
/**
* Get a loose Bukkit translation of what this slot stores. For example, any slot that drops items at the owner rather
@@ -0,0 +1,35 @@
package com.github.jikoo.openinv.internal.container.slot;
import org.bukkit.event.inventory.InventoryType;
import org.bukkit.inventory.ItemStack;
import org.jspecify.annotations.NullMarked;
import java.util.List;
import java.util.function.Function;
@NullMarked
public interface InventoryFactory<P, I, C, S, E> {
Content<P, I, C, S> newCrafting(P holder, int index);
Content<P, I, C, S> newCraftingResult(P holder);
Content<P, I, C, S> newCursor(P holder);
Content<P, I, C, S> newDrop(P holder);
Content<P, I, C, S> newEquipment(P holder, E equipmentSlot);
Content<P, I, C, S> newList(P holder, int index, InventoryType.SlotType slotType, Function<P, List<I>> listSupplier);
Content<P, I, C, S> newOffHand(P holder);
Content<P, I, C, S> newViewOnly(P holder);
Content<P, I, C, S> newViewOnly(P holder, I itemStack);
S wrapViewOnly(S wrapped);
ItemStack asBukkitMirror(I itemStack);
}
@@ -1,20 +1,4 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.lishid.openinv.util.lang;
package com.github.jikoo.openinv.lang;
import net.md_5.bungee.api.ChatMessageType;
import net.md_5.bungee.api.chat.TextComponent;
@@ -25,8 +9,8 @@ import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
import java.io.BufferedReader;
import java.io.File;
@@ -44,9 +28,8 @@ import java.util.logging.Level;
/**
* A simple language manager supporting both custom and bundled languages.
*
* @author Jikoo
*/
@NullMarked
public class LanguageManager {
private final Plugin plugin;
@@ -54,7 +37,7 @@ public class LanguageManager {
private final String defaultLocale;
private final Map<String, YamlConfiguration> locales;
public LanguageManager(@NotNull Plugin plugin, @NotNull String defaultLocale) {
public LanguageManager(Plugin plugin, String defaultLocale) {
this.plugin = plugin;
this.defaultLocale = defaultLocale;
this.locales = new HashMap<>();
@@ -72,7 +55,7 @@ public class LanguageManager {
getOrLoadLocale(defaultLocale);
}
private @NotNull YamlConfiguration getOrLoadLocale(@NotNull String locale) {
private YamlConfiguration getOrLoadLocale(String locale) {
YamlConfiguration loaded = locales.get(locale);
if (loaded != null) {
return loaded;
@@ -110,7 +93,7 @@ public class LanguageManager {
return localeConfig;
}
private @NotNull LangLocation bestMatch(@NotNull String locale, @Nullable LangLocation initial) {
private LangLocation bestMatch(String locale, @Nullable LangLocation initial) {
File file = new File(folder, locale + ".yml");
InputStream bundled = plugin.getResource("locale/" + locale + ".yml");
@@ -132,7 +115,7 @@ public class LanguageManager {
return bestMatch(locale.substring(0, lastSeparator), initial);
}
private @NotNull YamlConfiguration loadLocale(@NotNull LangLocation lang) {
private YamlConfiguration loadLocale(LangLocation lang) {
YamlConfiguration localeConfigDefaults;
if (lang.bundled == null) {
localeConfigDefaults = new YamlConfiguration();
@@ -195,7 +178,7 @@ public class LanguageManager {
return localeConfig;
}
private void addTranslationFallthrough(@NotNull LangLocation location, @NotNull YamlConfiguration localeConfig) {
private void addTranslationFallthrough(LangLocation location, YamlConfiguration localeConfig) {
YamlConfiguration defaultLocaleConfig = locales.get(defaultLocale);
// Get missing keys. Keys that already have a guess value are not new and don't need to trigger another write.
@@ -222,9 +205,9 @@ public class LanguageManager {
localeConfig.setDefaults(defaultLocaleConfig);
}
private @NotNull List<String> getMissingKeys(
@NotNull Configuration configurationDefault,
@NotNull Predicate<String> nodeSetPredicate
private List<String> getMissingKeys(
Configuration configurationDefault,
Predicate<String> nodeSetPredicate
) {
List<String> missingKeys = new ArrayList<>();
for (String key : configurationDefault.getKeys(true)) {
@@ -236,7 +219,7 @@ public class LanguageManager {
return missingKeys;
}
public @Nullable String getValue(@NotNull String key, @Nullable String locale) {
public @Nullable String getValue(String key, @Nullable String locale) {
String value = getOrLoadLocale(locale == null ? defaultLocale : locale.toLowerCase(Locale.ENGLISH)).getString(key);
if (value == null || value.isBlank()) {
return null;
@@ -248,9 +231,9 @@ public class LanguageManager {
}
public @Nullable String getValue(
@NotNull String key,
String key,
@Nullable String locale,
Replacement @NotNull ... replacements
Replacement... replacements
) {
String value = getValue(key, locale);
@@ -265,19 +248,19 @@ public class LanguageManager {
return value;
}
public @Nullable String getLocalizedMessage(@NotNull CommandSender sender, @NotNull String key) {
public @Nullable String getLocalizedMessage(CommandSender sender, String key) {
return getValue(key, getLocale(sender));
}
public @Nullable String getLocalizedMessage(
@NotNull CommandSender sender,
@NotNull String key,
Replacement @NotNull ... replacements
CommandSender sender,
String key,
Replacement... replacements
) {
return getValue(key, getLocale(sender), replacements);
}
private @NotNull String getLocale(@NotNull CommandSender sender) {
private String getLocale(CommandSender sender) {
if (sender instanceof Player player) {
return player.getLocale();
} else {
@@ -285,7 +268,7 @@ public class LanguageManager {
}
}
public void sendMessage(@NotNull CommandSender sender, @NotNull String key) {
public void sendMessage(CommandSender sender, String key) {
String message = getLocalizedMessage(sender, key);
if (message != null && !message.isEmpty()) {
@@ -293,7 +276,7 @@ public class LanguageManager {
}
}
public void sendMessage(@NotNull CommandSender sender, @NotNull String key, Replacement @NotNull ... replacements) {
public void sendMessage(CommandSender sender, String key, Replacement... replacements) {
String message = getLocalizedMessage(sender, key, replacements);
if (message != null && !message.isEmpty()) {
@@ -301,7 +284,7 @@ public class LanguageManager {
}
}
public void sendSystemMessage(@NotNull Player player, @NotNull String key) {
public void sendSystemMessage(Player player, String key) {
String message = getLocalizedMessage(player, key);
if (message == null) {
@@ -321,6 +304,6 @@ public class LanguageManager {
player.spigot().sendMessage(ChatMessageType.ACTION_BAR, TextComponent.fromLegacy(message));
}
private record LangLocation(@NotNull String locale, @NotNull File file, @Nullable InputStream bundled) {}
private record LangLocation(String locale, File file, @Nullable InputStream bundled) {}
}
@@ -0,0 +1,14 @@
package com.github.jikoo.openinv.lang;
import org.jspecify.annotations.NullMarked;
/**
* A data holder for string replacement in translations.
*
* @param placeholder the placeholder to be replaced
* @param value the value to insert
*/
@NullMarked
public record Replacement(String placeholder, String value) {
}
@@ -1,5 +1,6 @@
package com.lishid.openinv.util;
package com.github.jikoo.openinv.util;
import org.jspecify.annotations.NullMarked;
import org.slf4j.Marker;
import org.slf4j.helpers.LegacyAbstractLogger;
import org.slf4j.helpers.MessageFormatter;
@@ -14,6 +15,7 @@ import java.util.logging.Logger;
* An adapter for wrapping a {@link java.util.logging.Logger} as a {@link org.slf4j.Logger}.
* <br>Largely based on {@code JDK14LoggerAdapter}, which is not present at runtime.
*/
@NullMarked
public class JulLoggerAdapter extends LegacyAbstractLogger implements LocationAwareLogger {
private final Logger wrapped;
@@ -55,22 +57,17 @@ public class JulLoggerAdapter extends LegacyAbstractLogger implements LocationAw
}
private void addSource(String fqcn, LogRecord logRecord) {
// TODO stackwalker?
StackTraceElement[] trace = new Throwable().getStackTrace();
int maxElements = 12;
int lastIgnored = maxElements;
// Start from 2; 0 is above and 1 is caller of internal method.
for (int i = 2; i < maxElements; ++i) {
if (isIgnored(trace[i].getClassName(), fqcn)) {
lastIgnored = i;
}
}
if (lastIgnored < maxElements - 1) {
StackTraceElement caller = trace[lastIgnored + 1];
logRecord.setSourceClassName(caller.getClassName());
logRecord.setSourceMethodName(caller.getMethodName());
}
StackWalker.getInstance().walk(stream -> stream
// Start at index 2 - this is an internal method, so we want at least the caller's caller.
.skip(2)
// Walk at maximum 10 frames.
.limit(10)
.filter(frame -> isIgnored(frame.getClassName(), fqcn))
.findFirst()
).ifPresent(frame -> {
logRecord.setSourceClassName(frame.getClassName());
logRecord.setSourceMethodName(frame.getMethodName());
});
}
private boolean isIgnored(String className, String fqcn) {
@@ -1,28 +1,14 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.github.jikoo.openinv.util;
package com.lishid.openinv.util;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
import java.lang.reflect.Field;
/**
* A utility for making reflection easier.
*/
@NullMarked
public final class ReflectionHelper {
/**
@@ -6,24 +6,25 @@ import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.Cancellable;
import org.bukkit.event.Event;
import org.jetbrains.annotations.NotNull;
import org.jspecify.annotations.NullMarked;
import java.util.UUID;
/**
* Construct and call events.
*/
@NullMarked
public final class OpenEvents {
public static boolean saveCancelled(@NotNull Player player) {
public static boolean saveCancelled(Player player) {
return call(new PlayerSaveEvent(player));
}
public static boolean saveCancelled(@NotNull ISpecialInventory inventory) {
public static boolean saveCancelled(ISpecialInventory inventory) {
return call(new OpenPlayerSaveEvent((Player) inventory.getPlayer(), inventory));
}
public static void notifyPlayerToggle(@NotNull PlayerToggle toggle, @NotNull UUID uuid, boolean state) {
public static void notifyPlayerToggle(PlayerToggle toggle, UUID uuid, boolean state) {
Bukkit.getPluginManager().callEvent(new PlayerToggledEvent(toggle, uuid, state));
}
@@ -1,24 +0,0 @@
package com.lishid.openinv.internal;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public interface Accessor {
@NotNull
PlayerManager getPlayerManager();
@NotNull IAnySilentContainer getAnySilentContainer();
@NotNull ISpecialPlayerInventory createPlayerInventory(@NotNull Player player);
@NotNull ISpecialEnderChest createEnderChest(@NotNull Player player);
<T extends ISpecialInventory> @Nullable T get(@NotNull Inventory bukkitInventory, @NotNull Class<T> clazz);
void reload(@NotNull ConfigurationSection config);
}
@@ -1,89 +0,0 @@
package com.lishid.openinv.internal;
import org.bukkit.block.Barrel;
import org.bukkit.block.Block;
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.type.Chest;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryHolder;
import org.jetbrains.annotations.NotNull;
public abstract class AnySilentContainerBase implements IAnySilentContainer {
@Override
public boolean isAnyContainerNeeded(@NotNull Block block) {
BlockState blockState = getState(block);
// 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 half a block clear in the direction they open.
if (blockState instanceof ShulkerBox) {
return isShulkerBlocked(block);
}
if (!(blockState instanceof org.bukkit.block.Chest)) {
return false;
}
if (isChestBlocked(block)) {
return true;
}
BlockData blockData = block.getBlockData();
if (!(blockData instanceof Chest chest) || chest.getType() == Chest.Type.SINGLE) {
return false;
}
BlockFace relativeFace = switch (chest.getFacing()) {
case NORTH -> chest.getType() == Chest.Type.RIGHT ? BlockFace.WEST : BlockFace.EAST;
case EAST -> chest.getType() == Chest.Type.RIGHT ? BlockFace.NORTH : BlockFace.SOUTH;
case SOUTH -> chest.getType() == Chest.Type.RIGHT ? BlockFace.EAST : BlockFace.WEST;
case WEST -> chest.getType() == Chest.Type.RIGHT ? BlockFace.SOUTH : BlockFace.NORTH;
default -> BlockFace.SELF;
};
Block relative = block.getRelative(relativeFace);
if (relative.getType() != block.getType()) {
return false;
}
BlockData relativeData = relative.getBlockData();
if (!(relativeData instanceof Chest relativeChest)) {
return false;
}
if (relativeChest.getFacing() != chest.getFacing()
|| relativeChest.getType() != (chest.getType() == Chest.Type.RIGHT ? Chest.Type.LEFT : Chest.Type.RIGHT)) {
return false;
}
return isChestBlocked(relative);
}
@Override
public boolean isAnySilentContainer(@NotNull Block block) {
return isAnySilentContainer(getState(block));
}
@Override
public boolean isAnySilentContainer(@NotNull Inventory inventory) {
return isAnySilentContainer(getHolder(inventory));
}
protected abstract BlockState getState(@NotNull Block block);
protected abstract InventoryHolder getHolder(@NotNull Inventory inventory);
}
@@ -1,7 +0,0 @@
package com.lishid.openinv.internal;
public interface InternalOwned<T> {
T getOwnerHandle();
}
@@ -1,4 +0,0 @@
package com.lishid.openinv.internal;
public interface ViewOnly {
}
@@ -1,29 +0,0 @@
/*
* Copyright (C) 2011-2022 lishid. All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.lishid.openinv.util.lang;
import org.jetbrains.annotations.NotNull;
/**
* A data holder for string replacement in translations.
*
* @param placeholder the placeholder to be replaced
* @param value the value to insert
*/
public record Replacement(@NotNull String placeholder, @NotNull String value) {
}
+1 -1
View File
@@ -1,6 +1,6 @@
# Project meta
group=com.lishid.openinv
version=5.3.1
version=5.3.4
description=A Bukkit plugin for opening normally-inaccessible inventories.
# Gradle configuration
+12 -6
View File
@@ -1,15 +1,18 @@
[versions]
spigotapi = "1.21.5-R0.1-SNAPSHOT"
spigotapi = "1.21.3-R0.1-SNAPSHOT"
planarwrappers = "3.3.0"
jspecify = "1.0.0"
jspecify = "1.0.1"
annotations = "26.1.0"
paperweight = "2.0.0-beta.21"
shadow = "9.4.1"
shadow = "9.6.1"
folia-scheduler-wrapper = "v0.0.3"
errorprone-core = "2.48.0"
errorprone-gradle = "4.4.0"
slf4j = "2.0.17"
errorprone-core = "2.50.0"
errorprone-gradle = "5.1.1"
slf4j = "2.0.18"
sqlite-jdbc = "3.49.1.0"
mockito-core = "5.23.0"
junit-jupiter = "6.1.3"
hamcrest = "3.0"
[libraries]
spigotapi = { module = "org.spigotmc:spigot-api", version.ref = "spigotapi" }
@@ -20,6 +23,9 @@ folia-scheduler-wrapper = { module = "com.github.NahuLD.folia-scheduler-wrapper:
errorprone-core = { module = "com.google.errorprone:error_prone_core", version.ref = "errorprone-core" }
slf4j-api = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" }
sqlite-jdbc = { module = "org.xerial:sqlite-jdbc", version.ref = "sqlite-jdbc" }
mockito-core = { module = "org.mockito:mockito-core", version.ref = "mockito-core" }
junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit-jupiter" }
hamcrest = { module = "org.hamcrest:hamcrest", version.ref = "hamcrest" }
[plugins]
paperweight = { id = "io.papermc.paperweight.userdev", version.ref = "paperweight" }
Binary file not shown.
+3 -1
View File
@@ -1,7 +1,9 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.1-bin.zip
networkTimeout=10000
retries=0
retryBackOffMs=500
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Vendored
+4 -7
View File
@@ -1,7 +1,7 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
# Copyright © 2015 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -20,7 +20,7 @@
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
# gradlew start up script for POSIX generated by Gradle.
#
# Important for running:
#
@@ -29,7 +29,7 @@
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
# ksh gradlew
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
@@ -57,7 +57,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
@@ -114,7 +114,6 @@ case "$( uname )" in #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH="\\\"\\\""
# Determine the Java command to use to start the JVM.
@@ -172,7 +171,6 @@ fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
@@ -212,7 +210,6 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
Vendored
+82 -94
View File
@@ -1,94 +1,82 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:execute
@rem Setup the command line
set CLASSPATH=
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem gradlew startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables, and ensure extensions are enabled
setlocal EnableExtensions
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
"%COMSPEC%" /c exit 1
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
"%COMSPEC%" /c exit 1
:execute
@rem Setup the command line
@rem Execute gradlew
@rem endlocal doesn't take effect until after the line is parsed and variables are expanded
@rem which allows us to clear the local environment before executing the java command
endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel
:exitWithErrorLevel
@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts
"%COMSPEC%" /c exit %ERRORLEVEL%
-28
View File
@@ -1,28 +0,0 @@
plugins {
alias(libs.plugins.paperweight)
}
java {
toolchain.languageVersion = JavaLanguageVersion.of(21)
}
tasks.withType<JavaCompile>().configureEach {
options.release = 21
}
dependencies {
implementation(project(":openinvapi")) {
exclude(group = "org.spigotmc", module = "spigot-api")
}
implementation(project(":openinvcommon")) {
exclude(group = "org.spigotmc", module = "spigot-api")
}
implementation(project(":openinvadapterpaper26_1")) {
exclude(group = "io.papermc.paper", module = "dev-bundle")
}
implementation(project(":openinvadapterpaper1_21_11")) {
exclude(group = "io.papermc.paper", module = "dev-bundle")
}
paperweight.paperDevBundle("1.21.10-R0.1-SNAPSHOT")
}
@@ -1,31 +0,0 @@
package com.lishid.openinv.internal.paper1_21_10;
import com.lishid.openinv.internal.ISpecialPlayerInventory;
import com.lishid.openinv.internal.paper1_21_10.container.OpenInventory;
import com.lishid.openinv.internal.paper1_21_10.player.PlayerManager;
import com.lishid.openinv.util.lang.LanguageManager;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.util.logging.Logger;
public class InternalAccessor extends com.lishid.openinv.internal.paper1_21_11.InternalAccessor {
private final @NotNull PlayerManager manager;
public InternalAccessor(@NotNull Logger logger, @NotNull LanguageManager lang) {
super(logger, lang);
manager = new PlayerManager(logger);
}
@Override
public @NotNull PlayerManager getPlayerManager() {
return manager;
}
@Override
public @NotNull ISpecialPlayerInventory createPlayerInventory(@NotNull Player player) {
return new OpenInventory(player);
}
}
@@ -1,59 +0,0 @@
package com.lishid.openinv.internal.paper1_21_10.container;
import com.lishid.openinv.internal.paper1_21_11.container.menu.OpenInventoryMenu;
import com.lishid.openinv.internal.paper26_1.container.BaseOpenInventory;
import com.lishid.openinv.internal.paper26_1.container.menu.OpenChestMenu;
import net.minecraft.ChatFormatting;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.FontDescription;
import net.minecraft.network.chat.MutableComponent;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class OpenInventory extends BaseOpenInventory {
public OpenInventory(@NotNull Player bukkitPlayer) {
super(bukkitPlayer);
}
@Override
public @NotNull Component getTitle(@Nullable ServerPlayer viewer, @Nullable OpenChestMenu<?> menu) {
MutableComponent component = Component.empty();
// Prefix for use with custom bitmap image fonts.
if (owner.equals(viewer)) {
component.append(
Component.translatableWithFallback("openinv.container.inventory.self", "")
.withStyle(style -> style
.withFont(new FontDescription.Resource(ResourceLocation.parse("openinv:font/inventory")))
.withColor(ChatFormatting.WHITE)));
} else {
component.append(
Component.translatableWithFallback("openinv.container.inventory.other", "")
.withStyle(style -> style
.withFont(new FontDescription.Resource(ResourceLocation.parse("openinv:font/inventory")))
.withColor(ChatFormatting.WHITE)));
}
if (menu != null && menu.isViewOnly()) {
component.append(Component.translatableWithFallback("openinv.container.inventory.viewonly", "[RO] "));
} else {
component.append(Component.translatableWithFallback("openinv.container.inventory.editable", ""));
}
// Normal title: "Inventory - OwnerName"
component.append(Component.translatableWithFallback("openinv.container.inventory.prefix", "", owner.getName()))
.append(Component.translatable("container.inventory"))
.append(Component.translatableWithFallback("openinv.container.inventory.suffix", " - %s", owner.getName()));
return component;
}
@Override
public @Nullable OpenChestMenu<?> createMenu(net.minecraft.world.entity.player.Player player, int i, boolean viewOnly) {
if (player instanceof ServerPlayer serverPlayer) {
return new OpenInventoryMenu(this, serverPlayer, i, viewOnly);
}
return null;
}
}
@@ -1,35 +0,0 @@
package com.lishid.openinv.internal.paper1_21_10.player;
import com.lishid.openinv.internal.paper26_1.player.PlayerManager;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.server.level.ServerPlayer;
import org.bukkit.craftbukkit.CraftServer;
import org.jetbrains.annotations.NotNull;
import java.nio.file.Path;
public class OpenPlayer extends com.lishid.openinv.internal.paper26_1.player.OpenPlayer {
protected OpenPlayer(
CraftServer server,
ServerPlayer entity,
PlayerManager manager
) {
super(server, entity, manager);
}
@Override
protected void safeReplaceFile(
@NotNull Path dataFile,
@NotNull Path tempFile,
@NotNull Path backupFile
) {
net.minecraft.Util.safeReplaceFile(dataFile, tempFile, backupFile);
}
@Override
protected void remove(@NotNull CompoundTag tag, @NotNull String key) {
tag.remove(key);
}
}
@@ -1,26 +0,0 @@
package com.lishid.openinv.internal.paper1_21_10.player;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerPlayer;
import org.jetbrains.annotations.NotNull;
import java.util.logging.Logger;
public class PlayerManager extends com.lishid.openinv.internal.paper26_1.player.PlayerManager {
public PlayerManager(@NotNull Logger logger) {
super(logger);
}
@Override
protected void injectPlayer(@NotNull MinecraftServer server, @NotNull ServerPlayer player) throws IllegalAccessException {
if (bukkitEntity == null) {
return;
}
bukkitEntity.setAccessible(true);
bukkitEntity.set(player, new OpenPlayer(server.server, player, this));
}
}
@@ -1,29 +1,30 @@
package com.lishid.openinv.internal.paper1_21_11;
import com.github.jikoo.openinv.lang.LanguageManager;
import com.lishid.openinv.internal.ISpecialEnderChest;
import com.lishid.openinv.internal.ISpecialPlayerInventory;
import com.lishid.openinv.internal.paper1_21_11.container.OpenEnderChest;
import com.lishid.openinv.internal.paper1_21_11.container.OpenInventory;
import com.lishid.openinv.util.lang.LanguageManager;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jspecify.annotations.NullMarked;
import java.util.logging.Logger;
@NullMarked
public class InternalAccessor extends com.lishid.openinv.internal.paper26_1.InternalAccessor {
public InternalAccessor(@NotNull Logger logger, @NotNull LanguageManager lang) {
public InternalAccessor(Logger logger, LanguageManager lang) {
super(logger, lang);
}
@Override
public @NotNull ISpecialEnderChest createEnderChest(@NotNull Player player) {
public ISpecialEnderChest createEnderChest(Player player) {
return new OpenEnderChest(player);
}
@Override
public @NotNull ISpecialPlayerInventory createPlayerInventory(@NotNull Player player) {
return new OpenInventory(player);
public ISpecialPlayerInventory createPlayerInventory(Player player) {
return new OpenInventory(factory, player);
}
}
@@ -1,23 +1,33 @@
package com.lishid.openinv.internal.paper1_21_11.container;
import com.github.jikoo.openinv.internal.container.slot.InventoryFactory;
import com.lishid.openinv.internal.paper1_21_11.container.menu.OpenEnderChestMenu;
import com.lishid.openinv.internal.paper26_1.container.menu.OpenChestMenu;
import com.lishid.openinv.internal.paper26_3.container.menu.OpenChestMenu;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.NullMarked;
@NullMarked
public class OpenEnderChest extends com.lishid.openinv.internal.paper26_1.container.OpenEnderChest {
public class OpenEnderChest extends com.lishid.openinv.internal.paper26_3.container.OpenEnderChest {
public OpenEnderChest(org.bukkit.entity.Player player) {
super(player);
}
@Override
public @Nullable OpenChestMenu<?> createMenu(Player player, int i, boolean viewOnly) {
public @Nullable OpenChestMenu<?> createMenu(
InventoryFactory<ServerPlayer, ItemStack, Container, Slot, EquipmentSlot> factory,
Player player,
int i,
boolean viewOnly
) {
if (player instanceof ServerPlayer serverPlayer) {
return new OpenEnderChestMenu(this, serverPlayer, i, viewOnly);
return new OpenEnderChestMenu(factory, this, serverPlayer, i, viewOnly);
}
return null;
}
@@ -1,23 +1,31 @@
package com.lishid.openinv.internal.paper1_21_11.container;
import com.github.jikoo.openinv.internal.container.slot.InventoryFactory;
import com.lishid.openinv.internal.paper1_21_11.container.menu.OpenInventoryMenu;
import com.lishid.openinv.internal.paper26_1.container.menu.OpenChestMenu;
import com.lishid.openinv.internal.paper26_3.container.menu.OpenChestMenu;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.NullMarked;
@NullMarked
public class OpenInventory extends com.lishid.openinv.internal.paper26_1.container.OpenInventory {
public class OpenInventory extends com.lishid.openinv.internal.paper26_3.container.OpenInventory {
public OpenInventory(org.bukkit.entity.Player bukkitPlayer) {
super(bukkitPlayer);
public OpenInventory(
InventoryFactory<ServerPlayer, ItemStack, Container, Slot, EquipmentSlot> factory,
org.bukkit.entity.Player bukkitPlayer
) {
super(factory, bukkitPlayer);
}
@Override
public @Nullable OpenChestMenu<?> createMenu(Player player, int i, boolean viewOnly) {
if (player instanceof ServerPlayer serverPlayer) {
return new OpenInventoryMenu(this, serverPlayer, i, viewOnly);
return new OpenInventoryMenu(factory, this, serverPlayer, i, viewOnly);
}
return null;
}
@@ -1,8 +1,11 @@
package com.lishid.openinv.internal.paper1_21_11.container.menu;
import com.lishid.openinv.internal.paper26_1.container.OpenEnderChest;
import com.lishid.openinv.internal.paper26_1.container.menu.OpenSyncMenu;
import com.github.jikoo.openinv.internal.container.slot.InventoryFactory;
import com.lishid.openinv.internal.paper26_3.container.OpenEnderChest;
import com.lishid.openinv.internal.paper26_3.container.menu.OpenSyncMenu;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.ClickType;
import net.minecraft.world.inventory.Slot;
@@ -13,12 +16,13 @@ import org.jspecify.annotations.NullMarked;
public class OpenEnderChestMenu extends OpenSyncMenu<OpenEnderChest> {
public OpenEnderChestMenu(
InventoryFactory<ServerPlayer, ItemStack, Container, Slot, EquipmentSlot> factory,
OpenEnderChest enderChest,
ServerPlayer viewer,
int containerId,
boolean viewOnly
) {
super(getChestMenuType(enderChest.getContainerSize()), containerId, enderChest, viewer, viewOnly);
super(getChestMenuType(enderChest.getContainerSize()), containerId, factory, enderChest, viewer, viewOnly);
}
@Override
@@ -1,17 +1,28 @@
package com.lishid.openinv.internal.paper1_21_11.container.menu;
import com.lishid.openinv.internal.paper26_1.container.BaseOpenInventory;
import com.lishid.openinv.internal.paper26_1.container.menu.BaseOpenInventoryMenu;
import com.github.jikoo.openinv.internal.container.slot.InventoryFactory;
import com.lishid.openinv.internal.paper26_3.container.BaseOpenInventory;
import com.lishid.openinv.internal.paper26_3.container.menu.BaseOpenInventoryMenu;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.ClickType;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import org.jspecify.annotations.NullMarked;
@NullMarked
public class OpenInventoryMenu extends BaseOpenInventoryMenu {
public OpenInventoryMenu(BaseOpenInventory inventory, ServerPlayer viewer, int i, boolean viewOnly) {
super(inventory, viewer, i, viewOnly);
public OpenInventoryMenu(
InventoryFactory<ServerPlayer, ItemStack, Container, Slot, EquipmentSlot> factory,
BaseOpenInventory inventory,
ServerPlayer viewer,
int i,
boolean viewOnly
) {
super(factory, inventory, viewer, i, viewOnly);
}
@Override
-28
View File
@@ -1,28 +0,0 @@
plugins {
alias(libs.plugins.paperweight)
}
java {
toolchain.languageVersion = JavaLanguageVersion.of(21)
}
tasks.withType<JavaCompile>().configureEach {
options.release = 21
}
dependencies {
implementation(project(":openinvapi")) {
exclude(group = "org.spigotmc", module = "spigot-api")
}
implementation(project(":openinvcommon")) {
exclude(group = "org.spigotmc", module = "spigot-api")
}
implementation(project(":openinvadapterpaper26_1")) {
exclude(group = "io.papermc.paper", module = "dev-bundle")
}
implementation(project(":openinvadapterpaper1_21_11")) {
exclude(group = "io.papermc.paper", module = "dev-bundle")
}
paperweight.paperDevBundle("1.21.8-R0.1-SNAPSHOT")
}
@@ -1,31 +0,0 @@
package com.lishid.openinv.internal.paper1_21_8;
import com.lishid.openinv.internal.ISpecialPlayerInventory;
import com.lishid.openinv.internal.paper1_21_8.container.OpenInventory;
import com.lishid.openinv.internal.paper1_21_8.player.PlayerManager;
import com.lishid.openinv.util.lang.LanguageManager;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.util.logging.Logger;
public class InternalAccessor extends com.lishid.openinv.internal.paper1_21_11.InternalAccessor {
private final @NotNull PlayerManager manager;
public InternalAccessor(@NotNull Logger logger, @NotNull LanguageManager lang) {
super(logger, lang);
manager = new PlayerManager(logger);
}
@Override
public @NotNull PlayerManager getPlayerManager() {
return manager;
}
@Override
public @NotNull ISpecialPlayerInventory createPlayerInventory(@NotNull Player player) {
return new OpenInventory(player);
}
}
@@ -1,58 +0,0 @@
package com.lishid.openinv.internal.paper1_21_8.container;
import com.lishid.openinv.internal.paper1_21_11.container.menu.OpenInventoryMenu;
import com.lishid.openinv.internal.paper26_1.container.BaseOpenInventory;
import com.lishid.openinv.internal.paper26_1.container.menu.OpenChestMenu;
import net.minecraft.ChatFormatting;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.MutableComponent;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class OpenInventory extends BaseOpenInventory {
public OpenInventory(@NotNull Player bukkitPlayer) {
super(bukkitPlayer);
}
@Override
public @NotNull Component getTitle(@Nullable ServerPlayer viewer, @Nullable OpenChestMenu<?> menu) {
MutableComponent component = Component.empty();
// Prefix for use with custom bitmap image fonts.
if (owner.equals(viewer)) {
component.append(
Component.translatableWithFallback("openinv.container.inventory.self", "")
.withStyle(style -> style
.withFont(ResourceLocation.parse("openinv:font/inventory"))
.withColor(ChatFormatting.WHITE)));
} else {
component.append(
Component.translatableWithFallback("openinv.container.inventory.other", "")
.withStyle(style -> style
.withFont(ResourceLocation.parse("openinv:font/inventory"))
.withColor(ChatFormatting.WHITE)));
}
if (menu != null && menu.isViewOnly()) {
component.append(Component.translatableWithFallback("openinv.container.inventory.viewonly", "[RO] "));
} else {
component.append(Component.translatableWithFallback("openinv.container.inventory.editable", ""));
}
// Normal title: "Inventory - OwnerName"
component.append(Component.translatableWithFallback("openinv.container.inventory.prefix", "", owner.getName()))
.append(Component.translatable("container.inventory"))
.append(Component.translatableWithFallback("openinv.container.inventory.suffix", " - %s", owner.getName()));
return component;
}
@Override
public @Nullable OpenChestMenu<?> createMenu(net.minecraft.world.entity.player.Player player, int i, boolean viewOnly) {
if (player instanceof ServerPlayer serverPlayer) {
return new OpenInventoryMenu(this, serverPlayer, i, viewOnly);
}
return null;
}
}
@@ -1,44 +0,0 @@
package com.lishid.openinv.internal.paper1_21_8.player;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.util.ProblemReporter;
import net.minecraft.world.level.storage.PlayerDataStorage;
import org.bukkit.craftbukkit.CraftServer;
import org.jetbrains.annotations.NotNull;
import java.nio.file.Path;
import java.util.Optional;
public class OpenPlayer extends com.lishid.openinv.internal.paper26_1.player.OpenPlayer {
protected OpenPlayer(
CraftServer server,
ServerPlayer entity,
PlayerManager manager
) {
super(server, entity, manager);
}
@Override
protected Optional<CompoundTag> load(
PlayerDataStorage storage,
ServerPlayer player,
ProblemReporter.ScopedCollector collector
) {
return storage.load(player.getName().getString(), player.getStringUUID(), collector);
}
@Override
protected void safeReplaceFile(@NotNull Path dataFile, @NotNull Path tempFile, @NotNull Path backupFile) {
net.minecraft.Util.safeReplaceFile(dataFile, tempFile, backupFile);
}
@Override
protected void remove(@NotNull CompoundTag tag, @NotNull String key) {
// Note that the method signature here is different, though the usage
// appears identical. This version does not return the removed tag.
tag.remove(key);
}
}
@@ -1,100 +0,0 @@
package com.lishid.openinv.internal.paper1_21_8.player;
import com.lishid.openinv.internal.paper26_1.player.OpenPlayer;
import com.lishid.openinv.util.JulLoggerAdapter;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.util.ProblemReporter;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.storage.ValueInput;
import org.bukkit.Bukkit;
import org.bukkit.craftbukkit.CraftServer;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.util.logging.Logger;
public class PlayerManager extends com.lishid.openinv.internal.paper26_1.player.PlayerManager {
public PlayerManager(@NotNull Logger logger) {
super(logger);
}
@Override
protected boolean loadData(@NotNull MinecraftServer server, @NotNull ServerPlayer player) {
// See CraftPlayer#loadData
try (ProblemReporter.ScopedCollector scopedCollector = new ProblemReporter.ScopedCollector(player.problemPath(), new JulLoggerAdapter(logger))) {
ValueInput loadedData = server.getPlayerList().playerIo.load(player, scopedCollector).orElse(null);
if (loadedData == null) {
// Exceptions with loading are logged.
return false;
}
// Read basic data into the player.
player.load(loadedData);
// Game type settings are loaded separately.
player.loadGameTypes(loadedData);
// World is not loaded by ServerPlayer#load(CompoundTag) on Paper.
parseWorld(server, player, loadedData);
}
return true;
}
@Override
protected void spawnInDefaultWorld(@NotNull MinecraftServer server, @NotNull ServerPlayer player) {
ServerLevel level = server.getLevel(Level.OVERWORLD);
if (level != null) {
// Adjust player to default spawn (in keeping with Paper handling) when world not found.
player.snapTo(player.adjustSpawnLocation(level, level.getSharedSpawnPos()).getBottomCenter(), level.getSharedSpawnAngle(), 0.0F);
player.spawnIn(level);
} else {
logger.warning("Tried to load player with invalid world when no fallback was available!");
}
}
@Override
protected void injectPlayer(@NotNull MinecraftServer server, @NotNull ServerPlayer player) throws IllegalAccessException {
if (bukkitEntity == null) {
return;
}
bukkitEntity.setAccessible(true);
bukkitEntity.set(player, new com.lishid.openinv.internal.paper1_21_8.player.OpenPlayer(server.server, player, this));
}
@Override
public @NotNull Player inject(@NotNull Player player) {
try {
ServerPlayer nmsPlayer = getHandle(player);
if (nmsPlayer.getBukkitEntity() instanceof OpenPlayer openPlayer) {
return openPlayer;
}
MinecraftServer server = nmsPlayer.getServer();
if (server == null) {
if (!(Bukkit.getServer() instanceof CraftServer craftServer)) {
logger.warning(() ->
"Unable to inject ServerPlayer, certain player data may be lost when saving! Server is not a CraftServer: "
+ Bukkit.getServer().getClass().getName());
return player;
}
server = craftServer.getServer();
}
injectPlayer(server, nmsPlayer);
return nmsPlayer.getBukkitEntity();
} catch (IllegalAccessException e) {
logger.log(
java.util.logging.Level.WARNING,
e,
() -> "Unable to inject ServerPlayer, certain player data may be lost when saving!"
);
return player;
}
}
}
+11 -34
View File
@@ -1,5 +1,3 @@
import org.gradle.api.internal.attributes.DefaultCompatibilityRuleChain
plugins {
alias(libs.plugins.paperweight)
}
@@ -8,57 +6,36 @@ repositories {
maven("https://repo.papermc.io/repository/maven-public/")
}
// Paper is compiled with Java 25, so we have to use a Java 25 toolchain.
java {
toolchain.languageVersion.set(JavaLanguageVersion.of(25))
}
// To minimize duplicate code, the adapter for 1.21.11 is built off of this module, so we need to target Java 21.
tasks.withType<JavaCompile>().configureEach {
options.release = 21
}
class ToolchainJvmVersion: AttributeCompatibilityRule<Int> {
override fun execute(details: CompatibilityCheckDetails<Int>) {
if (details.consumerValue == details.producerValue) {
details.compatible()
return
}
if (details.producerValue == null) {
details.incompatible()
return
}
if (25 >= details.producerValue!!) {
details.compatible()
} else {
details.incompatible()
}
// As a result, we have to specifically tell Gradle it's okay if our dependencies require Java 25,
// because secretly sometimes they don't!
configurations.matching { it.isCanBeResolved && it.name.endsWith("Classpath") }.all {
attributes {
attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 25)
}
}
dependencies {
attributesSchema {
attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE) {
// Gradle thinks that depending on a library with a certain language level requires
// any project that depends on it to also require that language level no matter what.
// This is a bit obnoxious when we're specifically adding compatiblity patches to shared common code.
// Realistically Gradle is correct and the chain should be reversed - the oldest, least common
// denominator should be targeted and patches should be slapped on top as necessary.
// However, I don't want to spend a week restructuring the project to cut a release.
// Hack out Gradle's rules and replace them with our own less-restrictive version.
val rules = compatibilityRules as DefaultCompatibilityRuleChain
rules.rules.clear()
compatibilityRules.add(ToolchainJvmVersion::class.java)
}
}
implementation(project(":openinvapi")) {
exclude(group = "org.spigotmc", module = "spigot-api")
}
implementation(project(":openinvcommon")) {
exclude(group = "org.spigotmc", module = "spigot-api")
}
api(project(":openinvadapterpaper26_2")) {
exclude(group = "io.papermc.paper", module = "dev-bundle")
}
paperweight.paperDevBundle("26.1.2.build.5-alpha")
paperweight.paperDevBundle("26.1.2.build.+")
}
tasks.reobfJar {
@@ -1,80 +1,53 @@
package com.lishid.openinv.internal.paper26_1;
import com.lishid.openinv.internal.Accessor;
import com.lishid.openinv.internal.IAnySilentContainer;
import com.lishid.openinv.internal.ISpecialEnderChest;
import com.lishid.openinv.internal.ISpecialInventory;
import com.lishid.openinv.internal.ISpecialPlayerInventory;
import com.github.jikoo.openinv.internal.container.slot.InventoryFactory;
import com.lishid.openinv.internal.paper26_1.container.AnySilentContainer;
import com.lishid.openinv.internal.paper26_1.container.OpenEnderChest;
import com.lishid.openinv.internal.paper26_1.container.OpenInventory;
import com.lishid.openinv.internal.paper26_1.container.slot.placeholder.PlaceholderLoader;
import com.lishid.openinv.internal.paper26_1.player.PlayerManager;
import com.lishid.openinv.util.lang.LanguageManager;
import com.lishid.openinv.internal.paper26_3.container.slot.placeholder.PlaceholderLoader;
import com.github.jikoo.openinv.lang.LanguageManager;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.craftbukkit.inventory.CraftInventory;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.NullMarked;
import java.util.logging.Level;
import java.util.logging.Logger;
public class InternalAccessor implements Accessor {
@NullMarked
public class InternalAccessor extends com.lishid.openinv.internal.paper26_2.InternalAccessor {
protected final @NotNull Logger logger;
private final @NotNull PlayerManager manager;
private final @NotNull AnySilentContainer anySilentContainer;
public InternalAccessor(@NotNull Logger logger, @NotNull LanguageManager lang) {
this.logger = logger;
manager = new PlayerManager(logger);
anySilentContainer = new AnySilentContainer(logger, lang);
public InternalAccessor(Logger logger, LanguageManager lang) {
super(logger, lang);
}
@Override
public @NotNull PlayerManager getPlayerManager() {
return manager;
protected PlayerManager createPlayerManager(
Logger logger,
InventoryFactory<ServerPlayer, ItemStack, Container, Slot, EquipmentSlot> factory
) {
return new PlayerManager(logger, factory);
}
@Override
public @NotNull IAnySilentContainer getAnySilentContainer() {
return anySilentContainer;
protected AnySilentContainer createAnySilentContainer(
Logger logger,
LanguageManager lang
) {
return new AnySilentContainer(logger, lang);
}
@Override
public @NotNull ISpecialPlayerInventory createPlayerInventory(@NotNull Player player) {
return new OpenInventory(player);
protected PlaceholderLoader createPlaceholderLoader() {
return new com.lishid.openinv.internal.paper26_1.container.slot.placeholder.PlaceholderLoader();
}
@Override
public @NotNull ISpecialEnderChest createEnderChest(@NotNull Player player) {
public ISpecialEnderChest createEnderChest(Player player) {
return new OpenEnderChest(player);
}
@Override
public <T extends ISpecialInventory> @Nullable T get(@NotNull Inventory bukkitInventory, @NotNull Class<T> clazz) {
if (!(bukkitInventory instanceof CraftInventory craftInventory)) {
return null;
}
Container container = craftInventory.getInventory();
if (clazz.isInstance(container)) {
return clazz.cast(container);
}
return null;
}
@Override
public void reload(@NotNull ConfigurationSection config) {
ConfigurationSection placeholders = config.getConfigurationSection("placeholders");
try {
// Reset placeholders to defaults and try to load configuration.
new PlaceholderLoader().load(placeholders);
} catch (Exception e) {
logger.log(Level.WARNING, "Caught exception loading placeholder overrides!", e);
}
}
}
@@ -1,228 +1,21 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.lishid.openinv.internal.paper26_1.container;
import com.lishid.openinv.internal.AnySilentContainerBase;
import com.lishid.openinv.internal.paper26_1.container.menu.OpenChestMenu;
import com.lishid.openinv.internal.paper26_1.player.PlayerManager;
import com.lishid.openinv.util.ReflectionHelper;
import com.lishid.openinv.util.lang.LanguageManager;
import net.minecraft.core.BlockPos;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.level.ServerPlayerGameMode;
import net.minecraft.world.MenuProvider;
import net.minecraft.world.SimpleMenuProvider;
import net.minecraft.world.entity.Entity;
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 com.github.jikoo.openinv.lang.LanguageManager;
import net.minecraft.world.level.block.entity.RandomizableContainerBlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import org.bukkit.GameMode;
import org.bukkit.Material;
import org.bukkit.Statistic;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryHolder;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.NullMarked;
import java.lang.reflect.Field;
import java.util.logging.Logger;
public class AnySilentContainer extends AnySilentContainerBase {
@NullMarked
public class AnySilentContainer extends com.lishid.openinv.internal.paper26_3.container.AnySilentContainer {
private final @NotNull Logger logger;
private final @NotNull LanguageManager lang;
private @Nullable Field serverPlayerGameModeGameType;
public AnySilentContainer(@NotNull Logger logger, @NotNull LanguageManager lang) {
this.logger = logger;
this.lang = lang;
try {
try {
this.serverPlayerGameModeGameType = ServerPlayerGameMode.class.getDeclaredField("gameModeForPlayer");
this.serverPlayerGameModeGameType.setAccessible(true);
} catch (NoSuchFieldException e) {
logger.warning("The field ServerPlayerGameMode#gameModeForPlayer is no longer present!");
logger.warning("Please report this at https://github.com/Jikoo/OpenInv/issues");
logger.warning("Attempting to fall through using reflection. Please verify that SilentContainer does not fail.");
// N.B. gameModeForPlayer is (for now) declared before previousGameModeForPlayer so silent shouldn't break.
this.serverPlayerGameModeGameType = ReflectionHelper.grabFieldByType(ServerPlayerGameMode.class, GameType.class);
}
} catch (SecurityException e) {
logger.warning("Unable to directly write player game mode! SilentContainer will fail.");
logger.log(java.util.logging.Level.WARNING, "Error obtaining GameType field", e);
}
public AnySilentContainer(Logger logger, LanguageManager lang) {
super(logger, lang);
}
@Override
public boolean activateContainer(
@NotNull final Player bukkitPlayer,
final boolean silentchest,
@NotNull final org.bukkit.block.Block bukkitBlock
) {
// Silent ender chest is API-only
if (silentchest && bukkitBlock.getType() == Material.ENDER_CHEST) {
bukkitPlayer.openInventory(bukkitPlayer.getEnderChest());
bukkitPlayer.incrementStatistic(Statistic.ENDERCHEST_OPENED);
return true;
}
ServerPlayer player = PlayerManager.getHandle(bukkitPlayer);
final net.minecraft.world.level.Level level = ((Entity) 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 = OpenChestMenu.getChestMenuType(enderChest.getContainerSize());
int rows = enderChest.getContainerSize() / 9;
return new ChestMenu(containers, containerCounter, playerInventory, enderChest, rows);
}, Component.translatable("container.enderchest")
)
);
bukkitPlayer.incrementStatistic(Statistic.ENDERCHEST_OPENED);
return true;
}
if (!(blockEntity instanceof MenuProvider menuProvider)) {
return false;
}
BlockState blockState = level.getBlockState(blockPos);
Block block = blockState.getBlock();
if (block instanceof ChestBlock chestBlock) {
// boolean flag: do not check if chest is blocked
menuProvider = chestBlock.getMenuProvider(blockState, level, blockPos, true);
if (menuProvider == null) {
lang.sendSystemMessage(bukkitPlayer, "messages.error.lootNotGenerated");
return false;
}
if (block instanceof TrappedChestBlock) {
bukkitPlayer.incrementStatistic(Statistic.TRAPPED_CHEST_TRIGGERED);
} else {
bukkitPlayer.incrementStatistic(Statistic.CHEST_OPENED);
}
}
if (block instanceof ShulkerBoxBlock) {
bukkitPlayer.incrementStatistic(Statistic.SHULKER_BOX_OPENED);
}
if (block instanceof BarrelBlock) {
bukkitPlayer.incrementStatistic(Statistic.OPEN_BARREL);
}
// AnyChest only - SilentChest not active, container unsupported, or unnecessary.
if (!silentchest || player.gameMode.getGameModeForPlayer() == GameType.SPECTATOR) {
player.openMenu(menuProvider);
return true;
}
// SilentChest requires access to setting players' game mode directly.
if (this.serverPlayerGameModeGameType == null) {
return false;
}
if (blockEntity instanceof RandomizableContainerBlockEntity lootable) {
if (lootable.lootTable != null) {
lang.sendSystemMessage(bukkitPlayer, "messages.error.lootNotGenerated");
return false;
}
}
GameType gameType = player.gameMode.getGameModeForPlayer();
this.forceGameType(player, GameType.SPECTATOR);
player.openMenu(menuProvider);
this.forceGameType(player, gameType);
return true;
}
@Override
public void deactivateContainer(@NotNull final Player bukkitPlayer) {
if (this.serverPlayerGameModeGameType == null || bukkitPlayer.getGameMode() == GameMode.SPECTATOR) {
return;
}
ServerPlayer player = PlayerManager.getHandle(bukkitPlayer);
// Force game mode change without informing plugins or players.
// Regular game mode set calls GameModeChangeEvent and is cancellable.
GameType gameType = player.gameMode.getGameModeForPlayer();
this.forceGameType(player, GameType.SPECTATOR);
// ServerPlayer#closeContainer cannot be called without entering an
// infinite loop because this method is called during inventory close.
// From ServerPlayer#closeContainer -> CraftEventFactory#handleInventoryCloseEvent
player.containerMenu.transferTo(player.inventoryMenu, player.getBukkitEntity());
// From ServerPlayer#closeContainer
player.doCloseContainer();
// Regular inventory close will handle the rest - packet sending, etc.
// Revert forced game mode.
this.forceGameType(player, gameType);
}
private void forceGameType(final ServerPlayer player, final GameType gameMode) {
if (this.serverPlayerGameModeGameType == null) {
// No need to warn repeatedly, error on startup and lack of function should be enough.
return;
}
try {
this.serverPlayerGameModeGameType.setAccessible(true);
this.serverPlayerGameModeGameType.set(player.gameMode, gameMode);
} catch (IllegalArgumentException | IllegalAccessException e) {
logger.log(java.util.logging.Level.WARNING, "Error bypassing GameModeChangeEvent", e);
}
}
@Override
protected org.bukkit.block.BlockState getState(@NotNull org.bukkit.block.Block block) {
return block.getState(false);
}
@Override
protected InventoryHolder getHolder(@NotNull Inventory inventory) {
return inventory.getHolder(false);
protected boolean hasLootTable(RandomizableContainerBlockEntity lootable) {
return lootable.lootTable != null;
}
}
@@ -1,201 +1,20 @@
package com.lishid.openinv.internal.paper26_1.container;
import com.lishid.openinv.internal.ISpecialEnderChest;
import com.lishid.openinv.internal.InternalOwned;
import com.lishid.openinv.internal.paper26_1.container.menu.OpenChestMenu;
import com.lishid.openinv.internal.paper26_1.container.menu.OpenEnderChestMenu;
import com.lishid.openinv.internal.paper26_1.player.PlayerManager;
import net.minecraft.core.NonNullList;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.MutableComponent;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
import net.minecraft.world.ContainerHelper;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.entity.player.StackedItemContents;
import net.minecraft.world.inventory.StackedContentsCompatible;
import net.minecraft.world.item.ItemStack;
import org.bukkit.Location;
import org.bukkit.craftbukkit.entity.CraftHumanEntity;
import org.bukkit.craftbukkit.inventory.CraftInventory;
import org.bukkit.entity.HumanEntity;
import org.bukkit.event.inventory.InventoryType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.NullMarked;
import java.util.ArrayList;
import java.util.List;
@NullMarked
public class OpenEnderChest extends com.lishid.openinv.internal.paper26_3.container.OpenEnderChest {
public class OpenEnderChest implements Container, StackedContentsCompatible, InternalOwned<ServerPlayer>,
ISpecialEnderChest {
private CraftInventory inventory;
private @NotNull ServerPlayer owner;
private NonNullList<ItemStack> items;
private int maxStack = 64;
private final List<HumanEntity> transaction = new ArrayList<>();
public OpenEnderChest(@NotNull org.bukkit.entity.Player player) {
this.owner = PlayerManager.getHandle(player);
this.items = owner.getEnderChestInventory().items;
public OpenEnderChest(org.bukkit.entity.Player player) {
super(player);
}
@Override
public @NotNull ServerPlayer getOwnerHandle() {
return owner;
}
@Override
public @NotNull org.bukkit.inventory.Inventory getBukkitInventory() {
if (inventory == null) {
inventory = new CraftInventory(this) {
@Override
public @NotNull InventoryType getType() {
return InventoryType.ENDER_CHEST;
}
};
}
return inventory;
}
@Override
public void setPlayerOnline(@NotNull org.bukkit.entity.Player player) {
owner = PlayerManager.getHandle(player);
NonNullList<ItemStack> activeItems = owner.getEnderChestInventory().items;
// Guard against size changing. Theoretically on Purpur all row variations still have 6 rows internally.
int max = Math.min(items.size(), activeItems.size());
for (int index = 0; index < max; ++index) {
activeItems.set(index, items.get(index));
}
items = activeItems;
}
@Override
public @NotNull org.bukkit.entity.Player getPlayer() {
return owner.getBukkitEntity();
}
@Override
public int getContainerSize() {
return items.size();
}
@Override
public boolean isEmpty() {
return items.stream().allMatch(ItemStack::isEmpty);
}
@Override
public @NotNull ItemStack getItem(int index) {
return index >= 0 && index < items.size() ? items.get(index) : ItemStack.EMPTY;
}
@Override
public @NotNull ItemStack removeItem(int index, int amount) {
ItemStack itemstack = ContainerHelper.removeItem(items, index, amount);
if (!itemstack.isEmpty()) {
setChanged();
}
return itemstack;
}
@Override
public @NotNull ItemStack removeItemNoUpdate(int index) {
return index >= 0 && index < items.size() ? items.set(index, ItemStack.EMPTY) : ItemStack.EMPTY;
}
@Override
public void setItem(int index, @NotNull ItemStack itemStack) {
if (index >= 0 && index < items.size()) {
items.set(index, itemStack);
}
}
@Override
public int getMaxStackSize() {
return maxStack;
}
@Override
public void setChanged() {
this.owner.getEnderChestInventory().setChanged();
}
@Override
public boolean stillValid(@NotNull Player player) {
return true;
}
@Override
public @NotNull List<ItemStack> getContents() {
return items;
}
@Override
public void onOpen(@NotNull CraftHumanEntity craftHumanEntity) {
transaction.add(craftHumanEntity);
}
@Override
public void onClose(@NotNull CraftHumanEntity craftHumanEntity) {
transaction.remove(craftHumanEntity);
}
@Override
public @NotNull List<HumanEntity> getViewers() {
return transaction;
}
@Override
public org.bukkit.entity.Player getOwner() {
return getPlayer();
}
@Override
public void setMaxStackSize(int size) {
maxStack = size;
}
@Override
public @Nullable Location getLocation() {
return null;
}
@Override
public void clearContent() {
items.clear();
setChanged();
}
@Override
public void fillStackedContents(@NotNull StackedItemContents stackedContents) {
for (ItemStack itemstack : items) {
stackedContents.accountStack(itemstack);
}
}
public Component getTitle(@Nullable OpenChestMenu<?> menu) {
MutableComponent component;
if (menu != null && menu.isViewOnly()) {
component = Component.translatableWithFallback("openinv.container.enderchest.viewonly", "[RO] ");
} else {
component = Component.translatableWithFallback("openinv.container.enderchest.editable", "");
}
return component
.append(Component.translatableWithFallback("openinv.container.enderchest.prefix", "", owner.getName()))
.append(Component.translatable("container.enderchest"))
.append(Component.translatableWithFallback("openinv.container.enderchest.suffix", " - %s", owner.getName()));
}
public @Nullable OpenChestMenu<?> createMenu(Player player, int i, boolean viewOnly) {
if (player instanceof ServerPlayer serverPlayer) {
return new OpenEnderChestMenu(this, serverPlayer, i, viewOnly);
}
return null;
protected NonNullList<ItemStack> getEnderChestItems(ServerPlayer owner) {
return owner.getEnderChestInventory().items;
}
}
@@ -1,26 +0,0 @@
package com.lishid.openinv.internal.paper26_1.container.bukkit;
import com.lishid.openinv.internal.paper26_1.container.BaseOpenInventory;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
public class OpenPlayerInventorySelf extends OpenPlayerInventory {
private final int offset;
public OpenPlayerInventorySelf(@NotNull BaseOpenInventory inventory, int offset) {
super(inventory);
this.offset = offset;
}
@Override
public ItemStack getItem(int index) {
return super.getItem(offset + index);
}
@Override
public void setItem(int index, ItemStack item) {
super.setItem(offset + index, item);
}
}
@@ -1,26 +0,0 @@
package com.lishid.openinv.internal.paper26_1.container.menu;
import com.lishid.openinv.internal.paper26_1.container.BaseOpenInventory;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.ContainerInput;
import org.jspecify.annotations.NullMarked;
@NullMarked
public class OpenInventoryMenu extends BaseOpenInventoryMenu {
public OpenInventoryMenu(BaseOpenInventory inventory, ServerPlayer viewer, int i, boolean viewOnly) {
super(inventory, viewer, i, viewOnly);
}
@Override
public void clicked(int slotIndex, int buttonNum, ContainerInput containerInput, Player player) {
if (viewOnly) {
if (containerInput == ContainerInput.QUICK_CRAFT) {
sendAllDataToRemote();
}
}
super.clicked(slotIndex, buttonNum, containerInput, player);
}
}
@@ -1,151 +0,0 @@
package com.lishid.openinv.internal.paper26_1.container.slot;
import com.lishid.openinv.internal.paper26_1.container.slot.placeholder.Placeholders;
import net.minecraft.world.Container;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import org.jetbrains.annotations.NotNull;
import java.util.Optional;
/**
* A view-only {@link Slot}. "Blank" by default, but can wrap another slot to display its content.
*/
public class SlotViewOnly extends SlotPlaceholder {
public static @NotNull SlotViewOnly wrap(@NotNull Slot wrapped) {
SlotViewOnly wrapper;
if (wrapped instanceof SlotPlaceholder placeholder) {
wrapper = new SlotViewOnly(wrapped.container, wrapped.slot, wrapped.x, wrapped.y) {
@Override
public ItemStack getOrDefault() {
return placeholder.getOrDefault();
}
};
} else {
wrapper = new SlotViewOnly(wrapped.container, wrapped.slot, wrapped.x, wrapped.y) {
@Override
public ItemStack getOrDefault() {
return wrapped.getItem();
}
};
}
wrapper.index = wrapped.index;
return wrapper;
}
public SlotViewOnly(Container container, int index, int x, int y) {
super(container, index, x, y);
}
@Override
public ItemStack getOrDefault() {
return Placeholders.notSlot;
}
@Override
public void onQuickCraft(@NotNull ItemStack itemStack1, @NotNull ItemStack itemStack2) {
}
@Override
public void onTake(@NotNull Player player, @NotNull ItemStack itemStack) {
}
@Override
public boolean mayPlace(@NotNull ItemStack itemStack) {
return false;
}
@Override
public @NotNull ItemStack getItem() {
return ItemStack.EMPTY;
}
@Override
public boolean hasItem() {
return false;
}
@Override
public void setByPlayer(@NotNull ItemStack newStack) {
}
@Override
public void setByPlayer(@NotNull ItemStack newStack, @NotNull ItemStack oldStack) {
}
@Override
public void set(@NotNull ItemStack itemStack) {
}
@Override
public void setChanged() {
}
@Override
public int getMaxStackSize() {
return 0;
}
@Override
public int getMaxStackSize(@NotNull ItemStack itemStack) {
return 0;
}
@Override
public @NotNull ItemStack remove(int amount) {
return ItemStack.EMPTY;
}
@Override
public boolean mayPickup(@NotNull Player player) {
return false;
}
@Override
public boolean isActive() {
return false;
}
@Override
public @NotNull Optional<ItemStack> tryRemove(int var0, int var1, @NotNull Player player) {
return Optional.empty();
}
@Override
public @NotNull ItemStack safeTake(int var0, int var1, @NotNull Player player) {
return ItemStack.EMPTY;
}
@Override
public @NotNull ItemStack safeInsert(@NotNull ItemStack itemStack) {
return itemStack;
}
@Override
public @NotNull ItemStack safeInsert(@NotNull ItemStack itemStack, int amount) {
return itemStack;
}
@Override
public boolean allowModification(@NotNull Player player) {
return false;
}
@Override
public int getContainerSlot() {
return this.slot;
}
@Override
public boolean isHighlightable() {
return false;
}
@Override
public boolean isFake() {
return true;
}
}
@@ -1,40 +1,20 @@
package com.lishid.openinv.internal.paper26_1.container.slot.placeholder;
import net.minecraft.core.component.DataComponents;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.TagParser;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.component.CustomModelData;
import net.minecraft.world.item.component.DyedItemColor;
import net.minecraft.world.item.component.TooltipDisplay;
import org.jetbrains.annotations.NotNull;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.Items;
import org.jspecify.annotations.NullMarked;
import java.util.LinkedHashSet;
import java.util.List;
public class PlaceholderLoader extends PlaceholderLoaderBase {
private static final CustomModelData DEFAULT_CUSTOM_MODEL_DATA = new CustomModelData(List.of(), List.of(), List.of("openinv:custom"), List.of());
private static final TooltipDisplay HIDE_TOOLTIP = new TooltipDisplay(true, new LinkedHashSet<>());
@NullMarked
public class PlaceholderLoader extends com.lishid.openinv.internal.paper26_3.container.slot.placeholder.PlaceholderLoader {
@Override
protected @NotNull CompoundTag parseTag(@NotNull String itemText) throws Exception {
return TagParser.parseCompoundFully(itemText);
protected Item getDefaultCursorItem() {
return Items.WHITE_BANNER;
}
@Override
protected void addModelData(@NotNull ItemStack itemStack) {
itemStack.set(DataComponents.CUSTOM_MODEL_DATA, DEFAULT_CUSTOM_MODEL_DATA);
}
@Override
protected void hideTooltip(@NotNull ItemStack itemStack) {
itemStack.set(DataComponents.TOOLTIP_DISPLAY, HIDE_TOOLTIP);
}
@Override
protected DyedItemColor getDye(int rgb) {
return new DyedItemColor(rgb);
protected Item getDefaultNotSlotItem() {
return Items.WHITE_STAINED_GLASS_PANE;
}
}
@@ -1,37 +0,0 @@
package com.lishid.openinv.internal.paper26_1.container.slot.placeholder;
import com.lishid.openinv.internal.paper26_1.player.OpenPlayer;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.GameType;
import org.jetbrains.annotations.NotNull;
import java.util.EnumMap;
public final class Placeholders {
static final @NotNull EnumMap<GameType, ItemStack> BLOCKED_GAME_TYPE = new EnumMap<>(GameType.class);
public static @NotNull ItemStack craftingOutput = ItemStack.EMPTY;
public static @NotNull ItemStack cursor = ItemStack.EMPTY;
public static @NotNull ItemStack drop = ItemStack.EMPTY;
public static @NotNull ItemStack emptyHelmet = ItemStack.EMPTY;
public static @NotNull ItemStack emptyChestplate = ItemStack.EMPTY;
public static @NotNull ItemStack emptyLeggings = ItemStack.EMPTY;
public static @NotNull ItemStack emptyBoots = ItemStack.EMPTY;
public static @NotNull ItemStack emptyOffHand = ItemStack.EMPTY;
public static @NotNull ItemStack notSlot = ItemStack.EMPTY;
public static @NotNull ItemStack blockedOffline = ItemStack.EMPTY;
public static ItemStack survivalOnly(@NotNull ServerPlayer serverPlayer) {
if (!OpenPlayer.isConnected(serverPlayer.connection)) {
return blockedOffline;
}
return BLOCKED_GAME_TYPE.getOrDefault(serverPlayer.gameMode.getGameModeForPlayer(), ItemStack.EMPTY);
}
private Placeholders() {
throw new IllegalStateException("Cannot create instance of utility class.");
}
}
@@ -1,309 +1,50 @@
package com.lishid.openinv.internal.paper26_1.player;
import com.lishid.openinv.internal.ISpecialInventory;
import com.lishid.openinv.internal.paper26_1.container.BaseOpenInventory;
import com.lishid.openinv.internal.paper26_1.container.OpenEnderChest;
import com.lishid.openinv.internal.paper26_1.container.menu.OpenChestMenu;
import com.lishid.openinv.util.JulLoggerAdapter;
import com.mojang.authlib.GameProfile;
import com.mojang.logging.LogUtils;
import io.papermc.paper.adventure.PaperAdventure;
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.ParticleStatus;
import com.github.jikoo.openinv.internal.container.slot.InventoryFactory;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.util.ProblemReporter;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.player.ChatVisiblity;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.level.Level;
import net.minecraft.world.Container;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.storage.LevelData;
import net.minecraft.world.level.storage.TagValueInput;
import net.minecraft.world.level.storage.TagValueOutput;
import net.minecraft.world.level.storage.ValueInput;
import net.minecraft.world.level.storage.ValueOutput;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.Server;
import org.bukkit.World;
import org.bukkit.craftbukkit.CraftServer;
import org.bukkit.craftbukkit.CraftWorld;
import org.bukkit.craftbukkit.entity.CraftPlayer;
import org.bukkit.craftbukkit.event.CraftEventFactory;
import org.bukkit.entity.Player;
import org.bukkit.inventory.InventoryView;
import net.minecraft.world.phys.Vec3;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
import java.lang.reflect.Field;
import java.util.Optional;
import java.util.UUID;
import java.util.logging.Logger;
@NullMarked
public class PlayerManager implements com.lishid.openinv.internal.PlayerManager {
public class PlayerManager extends com.lishid.openinv.internal.paper26_3.player.PlayerManager {
protected final Logger logger;
protected @Nullable Field bukkitEntity;
public PlayerManager(Logger logger) {
this.logger = logger;
try {
bukkitEntity = Entity.class.getDeclaredField("bukkitEntity");
} catch (NoSuchFieldException e) {
logger.warning("Unable to obtain field to inject custom save process - certain player data may be lost when saving!");
logger.log(java.util.logging.Level.WARNING, e.getMessage(), e);
bukkitEntity = null;
}
}
public static ServerPlayer getHandle(final Player player) {
if (player instanceof CraftPlayer craftPlayer) {
return craftPlayer.getHandle();
}
Server server = player.getServer();
ServerPlayer nmsPlayer = null;
if (server instanceof CraftServer craftServer) {
nmsPlayer = craftServer.getHandle().getPlayer(player.getUniqueId());
}
if (nmsPlayer == null) {
// Could use reflection to examine fields, but it's honestly not worth the bother.
throw new RuntimeException("Unable to fetch EntityPlayer from Player implementation " + player.getClass().getName());
}
return nmsPlayer;
public PlayerManager(
Logger logger,
InventoryFactory<ServerPlayer, ItemStack, Container, Slot, EquipmentSlot> factory
) {
super(logger, factory);
}
@Override
public @Nullable Player loadPlayer(final OfflinePlayer offline) {
if (!(Bukkit.getServer() instanceof CraftServer craftServer)) {
return null;
}
MinecraftServer server = craftServer.getServer();
ServerLevel worldServer = server.getLevel(Level.OVERWORLD);
if (worldServer == null) {
return null;
}
// Create a new ServerPlayer.
ServerPlayer entity = createNewPlayer(server, worldServer, offline);
// Stop listening for advancement progression - if this is not cleaned up, loading causes a memory leak.
protected void removeAdvancementListeners(ServerPlayer entity) {
entity.getAdvancements().stopListening();
// Try to load the player's data.
if (loadData(server, entity)) {
// If data is loaded successfully, return the Bukkit entity.
return entity.getBukkitEntity();
}
return null;
}
protected ServerPlayer createNewPlayer(
MinecraftServer server,
ServerLevel worldServer,
final OfflinePlayer offline
) {
// See net.minecraft.server.players.PlayerList#canPlayerLogin(ServerLoginPacketListenerImpl, GameProfile)
// See net.minecraft.server.network.ServerLoginPacketListenerImpl#handleHello(ServerboundHelloPacket)
GameProfile profile = new GameProfile(offline.getUniqueId(),
offline.getName() != null ? offline.getName() : offline.getUniqueId().toString()
);
ClientInformation dummyInfo = new ClientInformation(
"en_us",
1, // Reduce distance just in case.
ChatVisiblity.HIDDEN, // Don't accept chat.
false,
ServerPlayer.DEFAULT_MODEL_CUSTOMIZATION,
ServerPlayer.DEFAULT_MAIN_HAND,
true,
false, // Don't list in player list (not that this player is in the list anyway).
ParticleStatus.MINIMAL
);
ServerPlayer entity = new ServerPlayer(server, worldServer, profile, dummyInfo);
try {
injectPlayer(server, entity);
} catch (IllegalAccessException e) {
logger.log(
java.util.logging.Level.WARNING,
e,
() -> "Unable to inject ServerPlayer, certain player data may be lost when saving!"
);
}
return entity;
@Override
protected LevelData.RespawnData getRespawnData(ServerLevel level) {
return level.levelData.getRespawnData();
}
protected boolean loadData(MinecraftServer server, ServerPlayer player) {
// See CraftPlayer#loadData
try (ProblemReporter.ScopedCollector scopedCollector = new ProblemReporter.ScopedCollector(player.problemPath(), new JulLoggerAdapter(logger))) {
CompoundTag loadedData = server.getPlayerList().playerIo.load(player.nameAndId()).orElse(null);
if (loadedData == null) {
// Exceptions with loading are logged.
return false;
}
ValueInput valueInput = TagValueInput.create(scopedCollector, player.registryAccess(), loadedData);
// Read basic data into the player.
player.load(valueInput);
// World is not loaded by ServerPlayer#load(CompoundTag) on Paper.
parseWorld(server, player, valueInput);
}
return true;
}
protected void parseWorld(
MinecraftServer server,
@Override
protected Vec3 getAdjustedSpawnLocation(
ServerPlayer player,
ValueInput loadedData
ServerLevel level,
LevelData.RespawnData respawnData
) {
// See PlayerList#placeNewPlayer
World bukkitWorld;
Optional<Long> msbs = loadedData.getLong("WorldUUIDMost");
Optional<Long> lsbs = loadedData.getLong("WorldUUIDLeast");
if (msbs.isPresent() && lsbs.isPresent()) {
// Modern Bukkit world.
bukkitWorld = Bukkit.getServer().getWorld(new UUID(msbs.get(), lsbs.get()));
} else {
bukkitWorld = loadedData.getString("world").map(Bukkit::getWorld).orElse(null);
}
if (bukkitWorld == null) {
spawnInDefaultWorld(server, player);
return;
}
player.setServerLevel(((CraftWorld) bukkitWorld).getHandle());
}
protected void spawnInDefaultWorld(MinecraftServer server, ServerPlayer player) {
ServerLevel level = server.getLevel(Level.OVERWORLD);
if (level != null) {
// Adjust player to default spawn (in keeping with Paper handling) when world not found.
LevelData.RespawnData respawnData = level.levelData.getRespawnData();
player.snapTo(player.adjustSpawnLocation(level, respawnData.pos()).getBottomCenter(), respawnData.yaw(), 0.0F);
player.spawnIn(level);
} else {
logger.warning("Tried to load player with invalid world when no fallback was available!");
}
}
protected void injectPlayer(MinecraftServer server, ServerPlayer player) throws IllegalAccessException {
if (bukkitEntity == null) {
return;
}
bukkitEntity.setAccessible(true);
bukkitEntity.set(player, new OpenPlayer(server.server, player, this));
return player.adjustSpawnLocation(level, respawnData.pos()).getBottomCenter();
}
@Override
public Player inject(Player player) {
try {
ServerPlayer nmsPlayer = getHandle(player);
if (nmsPlayer.getBukkitEntity() instanceof OpenPlayer openPlayer) {
return openPlayer;
}
org.slf4j.Logger logger = LogUtils.getLogger();
try (ProblemReporter.ScopedCollector scopedCollector = new ProblemReporter.ScopedCollector(nmsPlayer.problemPath(), logger)) {
CompoundTag extraData = new CompoundTag();
// Copy extra data out of existing player.
ValueOutput output = TagValueOutput.createWrappingWithContext(scopedCollector, nmsPlayer.registryAccess(), extraData);
nmsPlayer.getBukkitEntity().setExtraData(output);
MinecraftServer server = nmsPlayer.level().getServer();
injectPlayer(server, nmsPlayer);
CraftPlayer newPlayer = nmsPlayer.getBukkitEntity();
// Set extra data in new player.
ValueInput input = TagValueInput.create(scopedCollector, nmsPlayer.registryAccess(), extraData);
newPlayer.readExtraData(input);
return newPlayer;
}
} catch (IllegalAccessException e) {
logger.log(
java.util.logging.Level.WARNING,
e,
() -> "Unable to inject ServerPlayer, certain player data may be lost when saving!"
);
return player;
}
}
@Override
public @Nullable InventoryView openInventory(
Player bukkitPlayer,
ISpecialInventory inventory,
boolean viewOnly
) {
ServerPlayer player = getHandle(bukkitPlayer);
if (!OpenPlayer.isConnected(player.connection)) {
return null;
}
// See net.minecraft.server.level.ServerPlayer#openMenu(MenuProvider)
OpenChestMenu<?> menu;
Component title;
if (inventory instanceof BaseOpenInventory playerInv) {
menu = playerInv.createMenu(player, player.nextContainerCounter(), viewOnly);
title = playerInv.getTitle(player, menu);
} else if (inventory instanceof OpenEnderChest enderChest) {
menu = enderChest.createMenu(player, player.nextContainerCounter(), viewOnly);
title = enderChest.getTitle(menu);
} else {
return null;
}
// Should never happen, player is a ServerPlayer with an active connection.
if (menu == null) {
return null;
}
// Set up title. Title can only be set once for a menu, and is set during the open process.
// Further title changes are a hack where the client is sent a "new" inventory with the same ID,
// resulting in a title change but no other state modifications (like cursor position).
menu.setTitle(title);
var pair = CraftEventFactory.callInventoryOpenEventWithTitle(player, menu);
AbstractContainerMenu opened = pair.getSecond();
// Menu is null if event is cancelled.
if (opened == null) {
return null;
}
var newTitle = pair.getFirst();
if (newTitle != null) {
title = PaperAdventure.asVanilla(newTitle);
}
player.containerMenu = opened;
player.connection.send(new ClientboundOpenScreenPacket(opened.containerId, opened.getType(), title));
player.initMenu(opened);
return opened.getBukkitView();
protected void setServerLevel(ServerPlayer player, ServerLevel level) {
player.spawnIn(level);
}
}
+45
View File
@@ -0,0 +1,45 @@
plugins {
alias(libs.plugins.paperweight)
}
repositories {
maven("https://repo.papermc.io/repository/maven-public/")
}
// Paper is compiled with Java 25, so we have to use a Java 25 toolchain.
java {
toolchain.languageVersion.set(JavaLanguageVersion.of(25))
}
// To minimize duplicate code, the adapter for 1.21.11 is built off of this module, so we need to target Java 21.
tasks.withType<JavaCompile>().configureEach {
options.release = 21
}
// As a result, we have to specifically tell Gradle it's okay if our dependencies require Java 25,
// because secretly sometimes they don't!
configurations.matching { it.isCanBeResolved && it.name.endsWith("Classpath") }.all {
attributes {
attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 25)
}
}
dependencies {
implementation(project(":openinvapi")) {
exclude(group = "org.spigotmc", module = "spigot-api")
}
implementation(project(":openinvcommon")) {
exclude(group = "org.spigotmc", module = "spigot-api")
}
api(project(":openinvadapterpaper26_3")) {
exclude(group = "io.papermc.paper", module = "dev-bundle")
}
paperweight.paperDevBundle("26.2.build.+")
}
tasks.reobfJar {
enabled = false
}
configurations.reobf.get().outgoing.artifacts.clear()
@@ -0,0 +1,27 @@
package com.lishid.openinv.internal.paper26_2;
import com.github.jikoo.openinv.internal.container.slot.InventoryFactory;
import com.lishid.openinv.internal.paper26_2.container.slot.OpenInventoryFactory;
import com.github.jikoo.openinv.lang.LanguageManager;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import org.jspecify.annotations.NullMarked;
import java.util.logging.Logger;
@NullMarked
public class InternalAccessor extends com.lishid.openinv.internal.paper26_3.InternalAccessor {
public InternalAccessor(Logger logger, LanguageManager lang) {
super(logger, lang);
}
@Override
protected InventoryFactory<ServerPlayer, ItemStack, Container, Slot, EquipmentSlot> createInventoryFactory() {
return new OpenInventoryFactory();
}
}
@@ -0,0 +1,95 @@
package com.lishid.openinv.internal.paper26_2.container.slot;
import com.github.jikoo.openinv.internal.container.slot.Content;
import com.lishid.openinv.internal.paper26_3.container.slot.ContentCrafting;
import com.lishid.openinv.internal.paper26_3.container.slot.ContentCursor;
import com.lishid.openinv.internal.paper26_3.container.slot.ContentDrop;
import com.lishid.openinv.internal.paper26_3.container.slot.ContentViewOnly;
import com.lishid.openinv.internal.paper26_3.container.slot.SlotViewOnly;
import com.lishid.openinv.internal.paper26_3.container.slot.placeholder.Placeholders;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import org.bukkit.craftbukkit.inventory.CraftItemStack;
import org.jspecify.annotations.NullMarked;
@NullMarked
public class OpenInventoryFactory extends com.lishid.openinv.internal.paper26_3.container.slot.OpenInventoryFactory {
@Override
public Content<ServerPlayer, ItemStack, Container, Slot> newCrafting(
ServerPlayer holder,
int index
) {
return new ContentCrafting(holder, index) {
@Override
public void set(ItemStack itemStack) {
if (isAvailable(holder)) {
items.set(index, itemStack);
holder.inventoryMenu.slotsChanged(holder.inventoryMenu.getCraftSlots());
} else {
this.holder.drop(itemStack, false);
}
}
};
}
@Override
public Content<ServerPlayer, ItemStack, Container, Slot> newCursor(ServerPlayer holder) {
return new ContentCursor(holder) {
@Override
public void set(ItemStack itemStack) {
if (isAvailable()) {
holder.containerMenu.setCarried(itemStack);
} else {
this.holder.drop(itemStack, false);
}
}
};
}
@Override
public Content<ServerPlayer, ItemStack, Container, Slot> newDrop(ServerPlayer holder) {
return new ContentDrop(holder) {
@Override
public void set(ItemStack itemStack) {
this.holder.drop(itemStack, false);
}
};
}
@Override
public Content<ServerPlayer, ItemStack, Container, Slot> newViewOnly(ServerPlayer holder) {
return newViewOnly(holder, Placeholders.notSlot);
}
@Override
public Content<ServerPlayer, ItemStack, Container, Slot> newViewOnly(
ServerPlayer holder,
ItemStack itemStack
) {
return new ContentViewOnly(holder) {
@Override
public void set(ItemStack itemStack) {
this.holder.drop(itemStack, false);
}
@Override
public Slot asSlot(Container container, int slot, int x, int y) {
return new SlotViewOnly(container, slot, x, y) {
@Override
public ItemStack getOrDefault() {
return itemStack;
}
};
}
};
}
@Override
public org.bukkit.inventory.ItemStack asBukkitMirror(ItemStack itemStack) {
return CraftItemStack.asCraftMirror(itemStack);
}
}
+42
View File
@@ -0,0 +1,42 @@
plugins {
alias(libs.plugins.paperweight)
}
repositories {
maven("https://repo.papermc.io/repository/maven-public/")
}
// Paper is compiled with Java 25, so we have to use a Java 25 toolchain.
java {
toolchain.languageVersion.set(JavaLanguageVersion.of(25))
}
// To minimize duplicate code, the adapter for 1.21.11 is built off of this module, so we need to target Java 21.
tasks.withType<JavaCompile>().configureEach {
options.release = 21
}
// As a result, we have to specifically tell Gradle it's okay if our dependencies require Java 25,
// because secretly sometimes they don't!
configurations.matching { it.isCanBeResolved && it.name.endsWith("Classpath") }.all {
attributes {
attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 25)
}
}
dependencies {
implementation(project(":openinvapi")) {
exclude(group = "org.spigotmc", module = "spigot-api")
}
implementation(project(":openinvcommon")) {
exclude(group = "org.spigotmc", module = "spigot-api")
}
paperweight.paperDevBundle("26.3.build.+")
}
tasks.reobfJar {
enabled = false
}
configurations.reobf.get().outgoing.artifacts.clear()
@@ -0,0 +1,111 @@
package com.lishid.openinv.internal.paper26_3;
import com.github.jikoo.openinv.internal.Accessor;
import com.lishid.openinv.internal.IAnySilentContainer;
import com.lishid.openinv.internal.ISpecialEnderChest;
import com.lishid.openinv.internal.ISpecialInventory;
import com.lishid.openinv.internal.ISpecialPlayerInventory;
import com.github.jikoo.openinv.internal.container.slot.InventoryFactory;
import com.lishid.openinv.internal.paper26_3.container.AnySilentContainer;
import com.lishid.openinv.internal.paper26_3.container.OpenEnderChest;
import com.lishid.openinv.internal.paper26_3.container.OpenInventory;
import com.lishid.openinv.internal.paper26_3.container.slot.OpenInventoryFactory;
import com.lishid.openinv.internal.paper26_3.container.slot.placeholder.PlaceholderLoader;
import com.lishid.openinv.internal.paper26_3.player.PlayerManager;
import com.github.jikoo.openinv.lang.LanguageManager;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.craftbukkit.inventory.CraftInventory;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
import java.util.logging.Level;
import java.util.logging.Logger;
@NullMarked
public class InternalAccessor implements Accessor {
protected final Logger logger;
private final PlayerManager manager;
protected final InventoryFactory<ServerPlayer, ItemStack, Container, Slot, EquipmentSlot> factory;
private final AnySilentContainer anySilentContainer;
public InternalAccessor(Logger logger, LanguageManager lang) {
this.logger = logger;
factory = createInventoryFactory();
manager = createPlayerManager(logger, factory);
anySilentContainer = createAnySilentContainer(logger, lang);
}
protected InventoryFactory<ServerPlayer, ItemStack, Container, Slot, EquipmentSlot> createInventoryFactory() {
return new OpenInventoryFactory();
}
protected PlayerManager createPlayerManager(
Logger logger,
InventoryFactory<ServerPlayer, ItemStack, Container, Slot, EquipmentSlot> factory
) {
return new PlayerManager(logger, factory);
}
protected AnySilentContainer createAnySilentContainer(
Logger logger,
LanguageManager lang
) {
return new AnySilentContainer(logger, lang);
}
protected PlaceholderLoader createPlaceholderLoader() {
return new PlaceholderLoader();
}
@Override
public PlayerManager getPlayerManager() {
return manager;
}
@Override
public IAnySilentContainer getAnySilentContainer() {
return anySilentContainer;
}
@Override
public ISpecialPlayerInventory createPlayerInventory(Player player) {
return new OpenInventory(factory, player);
}
@Override
public ISpecialEnderChest createEnderChest(Player player) {
return new OpenEnderChest(player);
}
@Override
public <T extends ISpecialInventory> @Nullable T get(Inventory bukkitInventory, Class<T> clazz) {
if (!(bukkitInventory instanceof CraftInventory craftInventory)) {
return null;
}
Container container = craftInventory.getInventory();
if (clazz.isInstance(container)) {
return clazz.cast(container);
}
return null;
}
@Override
public void reload(ConfigurationSection config) {
ConfigurationSection placeholders = config.getConfigurationSection("placeholders");
try {
// Reset placeholders to defaults and try to load configuration.
createPlaceholderLoader().load(placeholders);
} catch (Exception e) {
logger.log(Level.WARNING, "Caught exception loading placeholder overrides!", e);
}
}
}
@@ -0,0 +1,233 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.lishid.openinv.internal.paper26_3.container;
import com.github.jikoo.openinv.internal.container.AnySilentContainerBase;
import com.lishid.openinv.internal.paper26_3.container.menu.OpenChestMenu;
import com.lishid.openinv.internal.paper26_3.player.PlayerManager;
import com.github.jikoo.openinv.util.ReflectionHelper;
import com.github.jikoo.openinv.lang.LanguageManager;
import net.minecraft.core.BlockPos;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.level.ServerPlayerGameMode;
import net.minecraft.world.MenuProvider;
import net.minecraft.world.SimpleMenuProvider;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.inventory.ChestMenu;
import net.minecraft.world.inventory.MenuType;
import net.minecraft.world.inventory.PlayerEnderChestContainer;
import net.minecraft.world.level.GameType;
import net.minecraft.world.level.block.BarrelBlock;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.ChestBlock;
import net.minecraft.world.level.block.ShulkerBoxBlock;
import net.minecraft.world.level.block.TrappedChestBlock;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.EnderChestBlockEntity;
import net.minecraft.world.level.block.entity.RandomizableContainerBlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import org.bukkit.GameMode;
import org.bukkit.Material;
import org.bukkit.Statistic;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryHolder;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
import java.lang.reflect.Field;
import java.util.logging.Logger;
@NullMarked
public class AnySilentContainer extends AnySilentContainerBase {
private final Logger logger;
private final LanguageManager lang;
private @Nullable Field serverPlayerGameModeGameType;
public AnySilentContainer(Logger logger, LanguageManager lang) {
this.logger = logger;
this.lang = lang;
try {
try {
this.serverPlayerGameModeGameType = ServerPlayerGameMode.class.getDeclaredField("gameModeForPlayer");
this.serverPlayerGameModeGameType.setAccessible(true);
} catch (NoSuchFieldException e) {
logger.warning("The field ServerPlayerGameMode#gameModeForPlayer is no longer present!");
logger.warning("Please report this at https://github.com/Jikoo/OpenInv/issues");
logger.warning("Attempting to fall through using reflection. Please verify that SilentContainer does not fail.");
// N.B. gameModeForPlayer is (for now) declared before previousGameModeForPlayer so silent shouldn't break.
this.serverPlayerGameModeGameType = ReflectionHelper.grabFieldByType(ServerPlayerGameMode.class, GameType.class);
}
} catch (SecurityException e) {
logger.warning("Unable to directly write player game mode! SilentContainer will fail.");
logger.log(java.util.logging.Level.WARNING, "Error obtaining GameType field", e);
}
}
@Override
public boolean activateContainer(
final Player bukkitPlayer,
final boolean silentchest,
final org.bukkit.block.Block bukkitBlock
) {
// Silent ender chest is API-only
if (silentchest && bukkitBlock.getType() == Material.ENDER_CHEST) {
bukkitPlayer.openInventory(bukkitPlayer.getEnderChest());
bukkitPlayer.incrementStatistic(Statistic.ENDERCHEST_OPENED);
return true;
}
ServerPlayer player = PlayerManager.getHandle(bukkitPlayer);
final net.minecraft.world.level.Level level = ((Entity) 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 = OpenChestMenu.getChestMenuType(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 @Nullable MenuProvider menuProvider)) {
return false;
}
BlockState blockState = level.getBlockState(blockPos);
Block block = blockState.getBlock();
if (block instanceof ChestBlock chestBlock) {
// boolean flag: do not check if chest is blocked
menuProvider = chestBlock.getMenuProvider(blockState, level, blockPos, true);
if (menuProvider == null) {
lang.sendSystemMessage(bukkitPlayer, "messages.error.lootNotGenerated");
return false;
}
if (block instanceof TrappedChestBlock) {
bukkitPlayer.incrementStatistic(Statistic.TRAPPED_CHEST_TRIGGERED);
} else {
bukkitPlayer.incrementStatistic(Statistic.CHEST_OPENED);
}
}
if (block instanceof ShulkerBoxBlock) {
bukkitPlayer.incrementStatistic(Statistic.SHULKER_BOX_OPENED);
}
if (block instanceof BarrelBlock) {
bukkitPlayer.incrementStatistic(Statistic.OPEN_BARREL);
}
// AnyChest only - SilentChest not active, container unsupported, or unnecessary.
if (!silentchest || player.gameMode.getGameModeForPlayer() == GameType.SPECTATOR) {
player.openMenu(menuProvider);
return true;
}
// SilentChest requires access to setting players' game mode directly.
if (this.serverPlayerGameModeGameType == null) {
return false;
}
if (blockEntity instanceof RandomizableContainerBlockEntity lootable) {
if (hasLootTable(lootable)) {
lang.sendSystemMessage(bukkitPlayer, "messages.error.lootNotGenerated");
return false;
}
}
GameType gameType = player.gameMode.getGameModeForPlayer();
this.forceGameType(player, GameType.SPECTATOR);
player.openMenu(menuProvider);
this.forceGameType(player, gameType);
return true;
}
protected boolean hasLootTable(RandomizableContainerBlockEntity lootable) {
return lootable.getLootTable() != null;
}
@Override
public void deactivateContainer(final Player bukkitPlayer) {
if (this.serverPlayerGameModeGameType == null || bukkitPlayer.getGameMode() == GameMode.SPECTATOR) {
return;
}
ServerPlayer player = PlayerManager.getHandle(bukkitPlayer);
// Force game mode change without informing plugins or players.
// Regular game mode set calls GameModeChangeEvent and is cancellable.
GameType gameType = player.gameMode.getGameModeForPlayer();
this.forceGameType(player, GameType.SPECTATOR);
// ServerPlayer#closeContainer cannot be called without entering an
// infinite loop because this method is called during inventory close.
// From ServerPlayer#closeContainer -> CraftEventFactory#handleInventoryCloseEvent
player.containerMenu.transferTo(player.inventoryMenu, player.getBukkitEntity());
// From ServerPlayer#closeContainer
player.doCloseContainer();
// Regular inventory close will handle the rest - packet sending, etc.
// Revert forced game mode.
this.forceGameType(player, gameType);
}
private void forceGameType(final ServerPlayer player, final GameType gameMode) {
if (this.serverPlayerGameModeGameType == null) {
// No need to warn repeatedly, error on startup and lack of function should be enough.
return;
}
try {
this.serverPlayerGameModeGameType.setAccessible(true);
this.serverPlayerGameModeGameType.set(player.gameMode, gameMode);
} catch (IllegalArgumentException | IllegalAccessException e) {
logger.log(java.util.logging.Level.WARNING, "Error bypassing GameModeChangeEvent", e);
}
}
@Override
protected org.bukkit.block.BlockState getState(org.bukkit.block.Block block) {
return block.getState(false);
}
@Override
protected @Nullable InventoryHolder getHolder(Inventory inventory) {
return inventory.getHolder(false);
}
}
@@ -1,22 +1,14 @@
package com.lishid.openinv.internal.paper26_1.container;
package com.lishid.openinv.internal.paper26_3.container;
import com.lishid.openinv.internal.ISpecialPlayerInventory;
import com.lishid.openinv.internal.InternalOwned;
import com.lishid.openinv.internal.paper26_1.container.bukkit.OpenPlayerInventory;
import com.lishid.openinv.internal.paper26_1.container.menu.OpenChestMenu;
import com.lishid.openinv.internal.paper26_1.container.menu.OpenInventoryMenu;
import com.lishid.openinv.internal.paper26_1.container.slot.Content;
import com.lishid.openinv.internal.paper26_1.container.slot.ContentCrafting;
import com.lishid.openinv.internal.paper26_1.container.slot.ContentCraftingResult;
import com.lishid.openinv.internal.paper26_1.container.slot.ContentCursor;
import com.lishid.openinv.internal.paper26_1.container.slot.ContentDrop;
import com.lishid.openinv.internal.paper26_1.container.slot.ContentEquipment;
import com.lishid.openinv.internal.paper26_1.container.slot.ContentList;
import com.lishid.openinv.internal.paper26_1.container.slot.ContentOffHand;
import com.lishid.openinv.internal.paper26_1.container.slot.ContentViewOnly;
import com.lishid.openinv.internal.paper26_1.container.slot.SlotViewOnly;
import com.lishid.openinv.internal.paper26_1.container.slot.placeholder.Placeholders;
import com.lishid.openinv.internal.paper26_1.player.PlayerManager;
import com.github.jikoo.openinv.internal.container.InternalOwned;
import com.github.jikoo.openinv.internal.container.slot.Content;
import com.github.jikoo.openinv.internal.container.slot.InventoryFactory;
import com.lishid.openinv.internal.paper26_3.container.bukkit.OpenPlayerInventory;
import com.lishid.openinv.internal.paper26_3.container.menu.OpenChestMenu;
import com.lishid.openinv.internal.paper26_3.container.menu.OpenInventoryMenu;
import com.lishid.openinv.internal.paper26_3.container.slot.placeholder.Placeholders;
import com.lishid.openinv.internal.paper26_3.player.PlayerManager;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import net.minecraft.core.NonNullList;
import net.minecraft.network.chat.Component;
@@ -32,31 +24,37 @@ import org.bukkit.craftbukkit.entity.CraftHumanEntity;
import org.bukkit.craftbukkit.inventory.CraftInventory;
import org.bukkit.entity.HumanEntity;
import org.bukkit.event.inventory.InventoryType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
@NullMarked
public abstract class BaseOpenInventory implements Container, InternalOwned<ServerPlayer>, ISpecialPlayerInventory {
protected final List<Content> slots;
protected final InventoryFactory<ServerPlayer, ItemStack, Container, Slot, EquipmentSlot> factory;
protected final List<Content<ServerPlayer, ItemStack, Container, Slot>> slots;
private final int size;
protected ServerPlayer owner;
private int maxStackSize = 99;
protected CraftInventory bukkitEntity;
protected @Nullable CraftInventory bukkitEntity;
public List<HumanEntity> transaction = new ArrayList<>();
public BaseOpenInventory(@NotNull org.bukkit.entity.Player bukkitPlayer) {
public BaseOpenInventory(
InventoryFactory<ServerPlayer, ItemStack, Container, Slot, EquipmentSlot> factory,
org.bukkit.entity.Player bukkitPlayer
) {
this.factory = factory;
owner = PlayerManager.getHandle(bukkitPlayer);
// Get total size, rounding up to nearest 9 for client compatibility.
int rawSize = owner.getInventory().getContainerSize() + owner.inventoryMenu.getCraftSlots().getContainerSize() + 1;
size = ((int) Math.ceil(rawSize / 9.0)) * 9;
slots = NonNullList.withSize(size, new ContentViewOnly(owner));
slots = NonNullList.withSize(size, factory.newViewOnly(owner));
setupSlots();
}
@@ -74,9 +72,9 @@ public abstract class BaseOpenInventory implements Container, InternalOwned<Serv
// Off-hand: Below chestplate.
addOffHand(46);
// Drop slot: Bottom right.
slots.set(53, new ContentDrop(owner));
slots.set(53, factory.newDrop(owner));
// Cursor slot: Above drop.
slots.set(44, new ContentCursor(owner));
slots.set(44, factory.newCursor(owner));
// Crafting is displayed in the bottom right corner.
// As we're using the pretty view, this is a 3x2.
@@ -88,9 +86,9 @@ public abstract class BaseOpenInventory implements Container, InternalOwned<Serv
nextIndex = addArmor(nextIndex);
nextIndex = addOffHand(nextIndex);
nextIndex = addCrafting(nextIndex, false);
slots.set(nextIndex, new ContentCursor(owner));
slots.set(nextIndex, factory.newCursor(owner));
// Drop slot last.
slots.set(slots.size() - 1, new ContentDrop(owner));
slots.set(slots.size() - 1, factory.newDrop(owner));
}
private int addMainInventory() {
@@ -111,12 +109,7 @@ public abstract class BaseOpenInventory implements Container, InternalOwned<Serv
slots.set(
localIndex,
new ContentList(owner, invIndex, type) {
@Override
public void setHolder(@NotNull ServerPlayer holder) {
items = holder.getInventory().getNonEquipmentItems();
}
}
factory.newList(owner, invIndex, type, holder -> holder.getInventory().getNonEquipmentItems())
);
}
return listSize;
@@ -138,7 +131,7 @@ public abstract class BaseOpenInventory implements Container, InternalOwned<Serv
continue;
}
slots.set(startIndex + localIndex, new ContentEquipment(owner, sorted[i]));
slots.set(startIndex + localIndex, factory.newEquipment(owner, sorted[i]));
++localIndex;
}
@@ -151,7 +144,7 @@ public abstract class BaseOpenInventory implements Container, InternalOwned<Serv
return startIndex;
}
slots.set(startIndex, new ContentOffHand(owner));
slots.set(startIndex, factory.newOffHand(owner));
return startIndex + 1;
}
@@ -165,32 +158,17 @@ public abstract class BaseOpenInventory implements Container, InternalOwned<Serv
// Otherwise, subtract 2 and add 9 to start in the same position on the next row.
int modIndex = startIndex + (localIndex < 2 || !pretty ? localIndex : localIndex + 7);
slots.set(modIndex, new ContentCrafting(owner, localIndex));
slots.set(modIndex, factory.newCrafting(owner, localIndex));
}
if (pretty) {
slots.set(startIndex + 2, new ContentViewOnly(owner) {
@Override
public Slot asSlot(Container container, int slot, int x, int y) {
return new SlotViewOnly(container, slot, x, y) {
@Override
public ItemStack getOrDefault() {
return Placeholders.craftingOutput;
}
};
}
}
);
slots.set(startIndex + 11, getCraftingResult(owner));
slots.set(startIndex + 2, factory.newViewOnly(owner, Placeholders.craftingOutput));
slots.set(startIndex + 11, factory.newCraftingResult(owner));
}
return startIndex + listSize;
}
protected Content getCraftingResult(@NotNull ServerPlayer serverPlayer) {
return new ContentCraftingResult(serverPlayer);
}
public Slot getMenuSlot(int index, int x, int y) {
return slots.get(index).asSlot(this, index, x, y);
}
@@ -207,7 +185,7 @@ public abstract class BaseOpenInventory implements Container, InternalOwned<Serv
}
@Override
public @NotNull org.bukkit.inventory.Inventory getBukkitInventory() {
public org.bukkit.inventory.Inventory getBukkitInventory() {
if (bukkitEntity == null) {
bukkitEntity = new OpenPlayerInventory(this);
}
@@ -215,7 +193,7 @@ public abstract class BaseOpenInventory implements Container, InternalOwned<Serv
}
@Override
public void setPlayerOnline(@NotNull org.bukkit.entity.Player player) {
public void setPlayerOnline(org.bukkit.entity.Player player) {
ServerPlayer newOwner = PlayerManager.getHandle(player);
// Only transfer regular inventory - crafting and cursor slots are transient.
newOwner.getInventory().replaceWith(owner.getInventory());
@@ -230,7 +208,7 @@ public abstract class BaseOpenInventory implements Container, InternalOwned<Serv
}
@Override
public @NotNull org.bukkit.entity.Player getPlayer() {
public org.bukkit.entity.Player getPlayer() {
return getOwner();
}
@@ -245,22 +223,22 @@ public abstract class BaseOpenInventory implements Container, InternalOwned<Serv
}
@Override
public @NotNull ItemStack getItem(int index) {
public ItemStack getItem(int index) {
return slots.get(index).get();
}
@Override
public @NotNull ItemStack removeItem(int index, int amount) {
public ItemStack removeItem(int index, int amount) {
return slots.get(index).removePartial(amount);
}
@Override
public @NotNull ItemStack removeItemNoUpdate(int index) {
public ItemStack removeItemNoUpdate(int index) {
return slots.get(index).remove();
}
@Override
public void setItem(int index, @NotNull ItemStack itemStack) {
public void setItem(int index, ItemStack itemStack) {
slots.get(index).set(itemStack);
}
@@ -279,12 +257,12 @@ public abstract class BaseOpenInventory implements Container, InternalOwned<Serv
}
@Override
public boolean stillValid(@NotNull Player player) {
public boolean stillValid(Player player) {
return true;
}
@Override
public @NotNull List<ItemStack> getContents() {
public List<ItemStack> getContents() {
NonNullList<ItemStack> contents = NonNullList.withSize(getContainerSize(), ItemStack.EMPTY);
for (int i = 0; i < getContainerSize(); ++i) {
contents.set(i, getItem(i));
@@ -293,22 +271,22 @@ public abstract class BaseOpenInventory implements Container, InternalOwned<Serv
}
@Override
public void onOpen(@NotNull CraftHumanEntity viewer) {
public void onOpen(CraftHumanEntity viewer) {
transaction.add(viewer);
}
@Override
public void onClose(@NotNull CraftHumanEntity viewer) {
public void onClose(CraftHumanEntity viewer) {
transaction.remove(viewer);
}
@Override
public @NotNull List<HumanEntity> getViewers() {
public List<HumanEntity> getViewers() {
return transaction;
}
@Override
public @NotNull org.bukkit.entity.Player getOwner() {
public org.bukkit.entity.Player getOwner() {
return owner.getBukkitEntity();
}
@@ -327,7 +305,7 @@ public abstract class BaseOpenInventory implements Container, InternalOwned<Serv
public @Nullable OpenChestMenu<?> createMenu(Player player, int i, boolean viewOnly) {
if (player instanceof ServerPlayer serverPlayer) {
return new OpenInventoryMenu(this, serverPlayer, i, viewOnly);
return new OpenInventoryMenu(factory, this, serverPlayer, i, viewOnly);
}
return null;
}
@@ -0,0 +1,214 @@
package com.lishid.openinv.internal.paper26_3.container;
import com.lishid.openinv.internal.ISpecialEnderChest;
import com.github.jikoo.openinv.internal.container.InternalOwned;
import com.github.jikoo.openinv.internal.container.slot.InventoryFactory;
import com.lishid.openinv.internal.paper26_3.container.menu.OpenChestMenu;
import com.lishid.openinv.internal.paper26_3.container.menu.OpenEnderChestMenu;
import com.lishid.openinv.internal.paper26_3.player.PlayerManager;
import net.minecraft.core.NonNullList;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.MutableComponent;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
import net.minecraft.world.ContainerHelper;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.entity.player.StackedItemContents;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.inventory.StackedContentsCompatible;
import net.minecraft.world.item.ItemStack;
import org.bukkit.Location;
import org.bukkit.craftbukkit.entity.CraftHumanEntity;
import org.bukkit.craftbukkit.inventory.CraftInventory;
import org.bukkit.entity.HumanEntity;
import org.bukkit.event.inventory.InventoryType;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
@NullMarked
public class OpenEnderChest implements Container, StackedContentsCompatible, InternalOwned<ServerPlayer>,
ISpecialEnderChest {
private @Nullable CraftInventory inventory;
private ServerPlayer owner;
private NonNullList<ItemStack> items;
private int maxStack = 64;
private final List<HumanEntity> transaction = new ArrayList<>();
public OpenEnderChest(org.bukkit.entity.Player player) {
this.owner = PlayerManager.getHandle(player);
this.items = getEnderChestItems(owner);
}
@Override
public ServerPlayer getOwnerHandle() {
return owner;
}
@Override
public org.bukkit.inventory.Inventory getBukkitInventory() {
if (inventory == null) {
inventory = new CraftInventory(this) {
@Override
public InventoryType getType() {
return InventoryType.ENDER_CHEST;
}
};
}
return inventory;
}
@Override
public void setPlayerOnline(org.bukkit.entity.Player player) {
owner = PlayerManager.getHandle(player);
NonNullList<ItemStack> activeItems = getEnderChestItems(owner);
// Guard against size changing. Theoretically on Purpur all row variations still have 6 rows internally.
int max = Math.min(items.size(), activeItems.size());
for (int index = 0; index < max; ++index) {
activeItems.set(index, items.get(index));
}
items = activeItems;
}
protected NonNullList<ItemStack> getEnderChestItems(ServerPlayer owner) {
return owner.getEnderChestInventory().getItems();
}
@Override
public org.bukkit.entity.Player getPlayer() {
return owner.getBukkitEntity();
}
@Override
public int getContainerSize() {
return items.size();
}
@Override
public boolean isEmpty() {
return items.stream().allMatch(ItemStack::isEmpty);
}
@Override
public ItemStack getItem(int index) {
return index >= 0 && index < items.size() ? items.get(index) : ItemStack.EMPTY;
}
@Override
public ItemStack removeItem(int index, int amount) {
ItemStack itemstack = ContainerHelper.removeItem(items, index, amount);
if (!itemstack.isEmpty()) {
setChanged();
}
return itemstack;
}
@Override
public ItemStack removeItemNoUpdate(int index) {
return index >= 0 && index < items.size() ? items.set(index, ItemStack.EMPTY) : ItemStack.EMPTY;
}
@Override
public void setItem(int index, ItemStack itemStack) {
if (index >= 0 && index < items.size()) {
items.set(index, itemStack);
}
}
@Override
public int getMaxStackSize() {
return maxStack;
}
@Override
public void setChanged() {
this.owner.getEnderChestInventory().setChanged();
}
@Override
public boolean stillValid(Player player) {
return true;
}
@Override
public List<ItemStack> getContents() {
return items;
}
@Override
public void onOpen(CraftHumanEntity craftHumanEntity) {
transaction.add(craftHumanEntity);
}
@Override
public void onClose(CraftHumanEntity craftHumanEntity) {
transaction.remove(craftHumanEntity);
}
@Override
public List<HumanEntity> getViewers() {
return transaction;
}
@Override
public org.bukkit.entity.Player getOwner() {
return getPlayer();
}
@Override
public void setMaxStackSize(int size) {
maxStack = size;
}
@Override
public @Nullable Location getLocation() {
return null;
}
@Override
public void clearContent() {
items.clear();
setChanged();
}
@Override
public void fillStackedContents(StackedItemContents stackedContents) {
for (ItemStack itemstack : items) {
stackedContents.accountStack(itemstack);
}
}
public Component getTitle(@Nullable OpenChestMenu<?> menu) {
MutableComponent component;
if (menu != null && menu.isViewOnly()) {
component = Component.translatableWithFallback("openinv.container.enderchest.viewonly", "[RO] ");
} else {
component = Component.translatableWithFallback("openinv.container.enderchest.editable", "");
}
return component
.append(Component.translatableWithFallback("openinv.container.enderchest.prefix", "", owner.getName()))
.append(Component.translatable("container.enderchest"))
.append(Component.translatableWithFallback("openinv.container.enderchest.suffix", " - %s", owner.getName()));
}
public @Nullable OpenChestMenu<?> createMenu(
InventoryFactory<ServerPlayer, ItemStack, Container, Slot, EquipmentSlot> factory,
Player player,
int i,
boolean viewOnly
) {
if (player instanceof ServerPlayer serverPlayer) {
return new OpenEnderChestMenu(factory, this, serverPlayer, i, viewOnly);
}
return null;
}
}
@@ -1,24 +1,33 @@
package com.lishid.openinv.internal.paper26_1.container;
package com.lishid.openinv.internal.paper26_3.container;
import com.lishid.openinv.internal.paper26_1.container.menu.OpenChestMenu;
import com.github.jikoo.openinv.internal.container.slot.InventoryFactory;
import com.lishid.openinv.internal.paper26_3.container.menu.OpenChestMenu;
import net.minecraft.ChatFormatting;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.FontDescription;
import net.minecraft.network.chat.MutableComponent;
import net.minecraft.resources.Identifier;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
@NullMarked
public class OpenInventory extends BaseOpenInventory {
public OpenInventory(@NotNull Player bukkitPlayer) {
super(bukkitPlayer);
public OpenInventory(
InventoryFactory<ServerPlayer, ItemStack, Container, Slot, EquipmentSlot> factory,
Player bukkitPlayer
) {
super(factory, bukkitPlayer);
}
@Override
public @NotNull Component getTitle(@Nullable ServerPlayer viewer, @Nullable OpenChestMenu<?> menu) {
public Component getTitle(@Nullable ServerPlayer viewer, @Nullable OpenChestMenu<?> menu) {
MutableComponent component = Component.empty();
// Prefix for use with custom bitmap image fonts.
if (owner.equals(viewer)) {
@@ -1,13 +1,13 @@
package com.lishid.openinv.internal.paper26_1.container.bukkit;
package com.lishid.openinv.internal.paper26_3.container.bukkit;
import com.lishid.openinv.internal.ViewOnly;
import com.github.jikoo.openinv.internal.container.ViewOnly;
import net.minecraft.world.Container;
import org.bukkit.Material;
import org.bukkit.craftbukkit.inventory.CraftInventory;
import org.bukkit.event.inventory.InventoryType;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
import java.util.Collections;
import java.util.HashMap;
@@ -16,6 +16,7 @@ import java.util.ListIterator;
/**
* A locked down "empty" inventory that rejects plugin interaction.
*/
@NullMarked
public class OpenDummyInventory extends CraftInventory implements ViewOnly {
private final InventoryType type;
@@ -26,7 +27,7 @@ public class OpenDummyInventory extends CraftInventory implements ViewOnly {
}
@Override
public @NotNull InventoryType getType() {
public InventoryType getType() {
return type;
}
@@ -42,18 +43,18 @@ public class OpenDummyInventory extends CraftInventory implements ViewOnly {
@SuppressWarnings("NonApiType")
@Override
public @NotNull HashMap<Integer, ItemStack> addItem(@NotNull ItemStack... items) throws IllegalArgumentException {
public HashMap<Integer, ItemStack> addItem(ItemStack... items) throws IllegalArgumentException {
return arrayToHashMap(items);
}
@SuppressWarnings("NonApiType")
@Override
public @NotNull HashMap<Integer, ItemStack> removeItem(@NotNull ItemStack... items) throws IllegalArgumentException {
public HashMap<Integer, ItemStack> removeItem(ItemStack... items) throws IllegalArgumentException {
return arrayToHashMap(items);
}
@SuppressWarnings("NonApiType")
private static @NotNull HashMap<Integer, ItemStack> arrayToHashMap(@NotNull ItemStack[] items) {
private static HashMap<Integer, ItemStack> arrayToHashMap(ItemStack[] items) {
HashMap<Integer, ItemStack> ignored = new HashMap<>();
for (int index = 0; index < items.length; ++index) {
ignored.put(index, items[index]);
@@ -62,27 +63,27 @@ public class OpenDummyInventory extends CraftInventory implements ViewOnly {
}
@Override
public ItemStack @NotNull [] getContents() {
public ItemStack[] getContents() {
return new ItemStack[getSize()];
}
@Override
public void setContents(@NotNull ItemStack[] items) throws IllegalArgumentException {
public void setContents(ItemStack[] items) throws IllegalArgumentException {
}
@Override
public @NotNull ItemStack @NotNull [] getStorageContents() {
public ItemStack[] getStorageContents() {
return new ItemStack[getSize()];
}
@Override
public void setStorageContents(@NotNull ItemStack[] items) throws IllegalArgumentException {
public void setStorageContents(ItemStack[] items) throws IllegalArgumentException {
}
@Override
public boolean contains(@NotNull Material material) throws IllegalArgumentException {
public boolean contains(Material material) throws IllegalArgumentException {
return false;
}
@@ -92,7 +93,7 @@ public class OpenDummyInventory extends CraftInventory implements ViewOnly {
}
@Override
public boolean contains(@NotNull Material material, int amount) throws IllegalArgumentException {
public boolean contains(Material material, int amount) throws IllegalArgumentException {
return false;
}
@@ -108,25 +109,25 @@ public class OpenDummyInventory extends CraftInventory implements ViewOnly {
@SuppressWarnings("NonApiType")
@Override
public @NotNull HashMap<Integer, ItemStack> all(
@NotNull Material material
public HashMap<Integer, ItemStack> all(
Material material
) throws IllegalArgumentException {
return new HashMap<>();
}
@SuppressWarnings("NonApiType")
@Override
public @NotNull HashMap<Integer, ItemStack> all(@Nullable ItemStack item) {
public HashMap<Integer, ItemStack> all(@Nullable ItemStack item) {
return new HashMap<>();
}
@Override
public int first(@NotNull Material material) throws IllegalArgumentException {
public int first(Material material) throws IllegalArgumentException {
return -1;
}
@Override
public int first(@NotNull ItemStack item) {
public int first(ItemStack item) {
return -1;
}
@@ -141,12 +142,12 @@ public class OpenDummyInventory extends CraftInventory implements ViewOnly {
}
@Override
public void remove(@NotNull Material material) throws IllegalArgumentException {
public void remove(Material material) throws IllegalArgumentException {
}
@Override
public void remove(@NotNull ItemStack item) {
public void remove(ItemStack item) {
}
@@ -161,12 +162,12 @@ public class OpenDummyInventory extends CraftInventory implements ViewOnly {
}
@Override
public @NotNull ListIterator<ItemStack> iterator() {
public ListIterator<ItemStack> iterator() {
return Collections.emptyListIterator();
}
@Override
public @NotNull ListIterator<ItemStack> iterator(int index) {
public ListIterator<ItemStack> iterator(int index) {
return Collections.emptyListIterator();
}
@@ -1,15 +1,15 @@
package com.lishid.openinv.internal.paper26_1.container.bukkit;
package com.lishid.openinv.internal.paper26_3.container.bukkit;
import net.minecraft.world.Container;
import org.bukkit.Material;
import org.bukkit.entity.HumanEntity;
import org.bukkit.event.inventory.InventoryType;
import org.bukkit.inventory.EquipmentSlot;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.PlayerInventory;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
@NullMarked
public class OpenDummyPlayerInventory extends OpenDummyInventory implements PlayerInventory {
public OpenDummyPlayerInventory(Container inventory) {
@@ -22,52 +22,52 @@ public class OpenDummyPlayerInventory extends OpenDummyInventory implements Play
}
@Override
public @NotNull ItemStack @NotNull [] getArmorContents() {
public ItemStack[] getArmorContents() {
return new ItemStack[4];
}
@Override
public @NotNull ItemStack @NotNull [] getExtraContents() {
public ItemStack[] getExtraContents() {
return new ItemStack[4];
}
@Override
public @Nullable ItemStack getHelmet() {
return null;
public ItemStack getHelmet() {
return ItemStack.empty();
}
@Override
public @Nullable ItemStack getChestplate() {
return null;
public ItemStack getChestplate() {
return ItemStack.empty();
}
@Override
public @Nullable ItemStack getLeggings() {
return null;
public ItemStack getLeggings() {
return ItemStack.empty();
}
@Override
public @Nullable ItemStack getBoots() {
return null;
public ItemStack getBoots() {
return ItemStack.empty();
}
@Override
public void setItem(@NotNull EquipmentSlot slot, @Nullable ItemStack item) {
public void setItem(EquipmentSlot slot, @Nullable ItemStack item) {
}
@Override
public @NotNull ItemStack getItem(@NotNull EquipmentSlot slot) {
return new ItemStack(Material.AIR);
public ItemStack getItem(EquipmentSlot slot) {
return ItemStack.empty();
}
@Override
public void setArmorContents(ItemStack @NotNull [] items) {
public void setArmorContents(@Nullable ItemStack[] items) {
}
@Override
public void setExtraContents(ItemStack @NotNull [] items) {
public void setExtraContents(@Nullable ItemStack[] items) {
}
@@ -92,8 +92,8 @@ public class OpenDummyPlayerInventory extends OpenDummyInventory implements Play
}
@Override
public @NotNull ItemStack getItemInMainHand() {
return new ItemStack(Material.AIR);
public ItemStack getItemInMainHand() {
return ItemStack.empty();
}
@Override
@@ -102,8 +102,8 @@ public class OpenDummyPlayerInventory extends OpenDummyInventory implements Play
}
@Override
public @NotNull ItemStack getItemInOffHand() {
return new ItemStack(Material.AIR);
public ItemStack getItemInOffHand() {
return ItemStack.empty();
}
@Override
@@ -114,8 +114,8 @@ public class OpenDummyPlayerInventory extends OpenDummyInventory implements Play
@SuppressWarnings("InlineMeSuggester")
@Deprecated
@Override
public @NotNull ItemStack getItemInHand() {
return new ItemStack(Material.AIR);
public ItemStack getItemInHand() {
return ItemStack.empty();
}
@Deprecated
@@ -1,7 +1,7 @@
package com.lishid.openinv.internal.paper26_1.container.bukkit;
package com.lishid.openinv.internal.paper26_3.container.bukkit;
import com.google.common.base.Preconditions;
import com.lishid.openinv.internal.paper26_1.container.BaseOpenInventory;
import com.lishid.openinv.internal.paper26_3.container.BaseOpenInventory;
import net.minecraft.core.NonNullList;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.entity.player.Inventory;
@@ -11,29 +11,30 @@ import org.bukkit.entity.Player;
import org.bukkit.event.inventory.InventoryType;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.PlayerInventory;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
import java.util.List;
@NullMarked
public class OpenPlayerInventory extends CraftInventory implements PlayerInventory {
public OpenPlayerInventory(@NotNull BaseOpenInventory inventory) {
public OpenPlayerInventory(BaseOpenInventory inventory) {
super(inventory);
}
@Override
public @NotNull BaseOpenInventory getInventory() {
public BaseOpenInventory getInventory() {
return (BaseOpenInventory) super.getInventory();
}
@Override
public ItemStack @NotNull [] getContents() {
public ItemStack[] getContents() {
return asCraftMirror(getInventory().getOwnerHandle().getInventory().getContents());
}
@Override
public void setContents(ItemStack[] items) {
public void setContents(@Nullable ItemStack[] items) {
Inventory internal = getInventory().getOwnerHandle().getInventory();
int size = internal.getContainerSize();
Preconditions.checkArgument(items.length <= size, "items.length must be <= %s", size);
@@ -48,12 +49,12 @@ public class OpenPlayerInventory extends CraftInventory implements PlayerInvento
}
@Override
public ItemStack @NotNull [] getStorageContents() {
public ItemStack[] getStorageContents() {
return asCraftMirror(getInventory().getOwnerHandle().getInventory().getNonEquipmentItems());
}
@Override
public void setStorageContents(ItemStack[] items) throws IllegalArgumentException {
public void setStorageContents(@Nullable ItemStack[] items) throws IllegalArgumentException {
NonNullList<net.minecraft.world.item.ItemStack> list = getInventory().getOwnerHandle().getInventory().getNonEquipmentItems();
int size = list.size();
Preconditions.checkArgument(items.length <= size, "items.length must be <= %s", size);
@@ -63,22 +64,22 @@ public class OpenPlayerInventory extends CraftInventory implements PlayerInvento
}
@Override
public @NotNull InventoryType getType() {
public InventoryType getType() {
return InventoryType.PLAYER;
}
@Override
public @NotNull Player getHolder() {
public Player getHolder() {
return getInventory().getOwner();
}
@Override
public @NotNull ItemStack @NotNull [] getArmorContents() {
public ItemStack[] getArmorContents() {
return asCraftMirror(getInventory().getOwnerHandle().getInventory().getArmorContents());
}
@Override
public void setArmorContents(ItemStack @NotNull [] items) {
public void setArmorContents(@Nullable ItemStack[] items) {
int size = Inventory.EQUIPMENT_SLOTS_SORTED_BY_INDEX.length;
Preconditions.checkArgument(items.length <= size, "items.length must be <= %s", size);
for (int index = 0; index < items.length; ++index) {
@@ -90,12 +91,12 @@ public class OpenPlayerInventory extends CraftInventory implements PlayerInvento
}
@Override
public @NotNull ItemStack @NotNull [] getExtraContents() {
public ItemStack[] getExtraContents() {
return asCraftMirror(List.of(getInventory().getOwnerHandle().getInventory().equipment.get(EquipmentSlot.OFFHAND)));
}
@Override
public void setExtraContents(ItemStack @NotNull [] items) {
public void setExtraContents(@Nullable ItemStack[] items) {
Preconditions.checkArgument(items.length <= 1, "items.length must be <= 1");
for (ItemStack item : items) {
getInventory().getOwnerHandle().getInventory().equipment.set(EquipmentSlot.OFFHAND, CraftItemStack.asNMSCopy(item));
@@ -103,81 +104,69 @@ public class OpenPlayerInventory extends CraftInventory implements PlayerInvento
}
@Override
public @NotNull ItemStack getHelmet() {
return CraftItemStack.asCraftMirror(getInventory().getOwnerHandle().getInventory().equipment
.get(EquipmentSlot.HEAD));
public ItemStack getHelmet() {
return getInventory().getOwner().getInventory().getHelmet();
}
@Override
public void setHelmet(@Nullable ItemStack helmet) {
getInventory().getOwnerHandle().getInventory().equipment
.set(EquipmentSlot.HEAD, CraftItemStack.asNMSCopy(helmet));
getInventory().getOwner().getInventory().setHelmet(helmet);
}
@Override
public @NotNull ItemStack getChestplate() {
return CraftItemStack.asCraftMirror(getInventory().getOwnerHandle().getInventory().equipment
.get(EquipmentSlot.CHEST));
public ItemStack getChestplate() {
return getInventory().getOwner().getInventory().getChestplate();
}
@Override
public void setChestplate(@Nullable ItemStack chestplate) {
getInventory().getOwnerHandle().getInventory().equipment
.set(EquipmentSlot.CHEST, CraftItemStack.asNMSCopy(chestplate));
getInventory().getOwner().getInventory().setChestplate(chestplate);
}
@Override
public @NotNull ItemStack getLeggings() {
return CraftItemStack.asCraftMirror(getInventory().getOwnerHandle().getInventory().equipment
.get(EquipmentSlot.LEGS));
public ItemStack getLeggings() {
return getInventory().getOwner().getInventory().getLeggings();
}
@Override
public void setLeggings(@Nullable ItemStack leggings) {
getInventory().getOwnerHandle().getInventory().equipment
.set(EquipmentSlot.LEGS, CraftItemStack.asNMSCopy(leggings));
getInventory().getOwner().getInventory().setLeggings(leggings);
}
@Override
public @NotNull ItemStack getBoots() {
return CraftItemStack.asCraftMirror(getInventory().getOwnerHandle().getInventory().equipment
.get(EquipmentSlot.FEET));
public ItemStack getBoots() {
return getInventory().getOwner().getInventory().getBoots();
}
@Override
public void setBoots(@Nullable ItemStack boots) {
getInventory().getOwnerHandle().getInventory().equipment
.set(EquipmentSlot.FEET, CraftItemStack.asNMSCopy(boots));
getInventory().getOwner().getInventory().setBoots(boots);
}
@Override
public @NotNull ItemStack getItemInMainHand() {
Inventory internal = getInventory().getOwnerHandle().getInventory();
return CraftItemStack.asCraftMirror(internal.getSelectedItem());
public ItemStack getItemInMainHand() {
return getInventory().getOwner().getInventory().getItemInMainHand();
}
@Override
public void setItemInMainHand(@Nullable ItemStack item) {
Inventory internal = getInventory().getOwnerHandle().getInventory();
internal.setSelectedItem(CraftItemStack.asNMSCopy(item));
getInventory().getOwner().getInventory().setItemInMainHand(item);
}
@Override
public @NotNull ItemStack getItemInOffHand() {
return CraftItemStack.asCraftMirror(getInventory().getOwnerHandle().getInventory().equipment
.get(EquipmentSlot.OFFHAND));
public ItemStack getItemInOffHand() {
return getInventory().getOwner().getInventory().getItemInOffHand();
}
@Override
public void setItemInOffHand(@Nullable ItemStack item) {
getInventory().getOwnerHandle().getInventory().equipment
.set(EquipmentSlot.OFFHAND, CraftItemStack.asNMSCopy(item));
getInventory().getOwner().getInventory().setItemInOffHand(item);
}
@SuppressWarnings("InlineMeSuggester")
@Deprecated
@Override
public @NotNull ItemStack getItemInHand() {
public ItemStack getItemInHand() {
return getItemInMainHand();
}
@@ -201,7 +190,7 @@ public class OpenPlayerInventory extends CraftInventory implements PlayerInvento
}
@Override
public @NotNull ItemStack getItem(@NotNull org.bukkit.inventory.EquipmentSlot slot) {
public ItemStack getItem(org.bukkit.inventory.EquipmentSlot slot) {
return switch (slot) {
case HAND -> getItemInMainHand();
case OFF_HAND -> getItemInOffHand();
@@ -214,7 +203,7 @@ public class OpenPlayerInventory extends CraftInventory implements PlayerInvento
}
@Override
public void setItem(@NotNull org.bukkit.inventory.EquipmentSlot slot, @Nullable ItemStack item) {
public void setItem(org.bukkit.inventory.EquipmentSlot slot, @Nullable ItemStack item) {
switch (slot) {
case HAND -> setItemInMainHand(item);
case OFF_HAND -> setItemInOffHand(item);
@@ -0,0 +1,28 @@
package com.lishid.openinv.internal.paper26_3.container.bukkit;
import com.lishid.openinv.internal.paper26_3.container.BaseOpenInventory;
import org.bukkit.inventory.ItemStack;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
@NullMarked
public class OpenPlayerInventorySelf extends OpenPlayerInventory {
private final int offset;
public OpenPlayerInventorySelf(BaseOpenInventory inventory, int offset) {
super(inventory);
this.offset = offset;
}
@Override
public @Nullable ItemStack getItem(int index) {
return super.getItem(offset + index);
}
@Override
public void setItem(int index, @Nullable ItemStack item) {
super.setItem(offset + index, item);
}
}
@@ -1,14 +1,16 @@
package com.lishid.openinv.internal.paper26_1.container.menu;
package com.lishid.openinv.internal.paper26_3.container.menu;
import com.google.common.base.Preconditions;
import com.lishid.openinv.internal.paper26_1.container.BaseOpenInventory;
import com.lishid.openinv.internal.paper26_1.container.bukkit.OpenDummyPlayerInventory;
import com.lishid.openinv.internal.paper26_1.container.bukkit.OpenPlayerInventorySelf;
import com.lishid.openinv.internal.paper26_1.container.slot.ContentDrop;
import com.lishid.openinv.internal.paper26_1.container.slot.ContentEquipment;
import com.lishid.openinv.internal.paper26_1.container.slot.SlotViewOnly;
import com.github.jikoo.openinv.internal.container.slot.InventoryFactory;
import com.lishid.openinv.internal.paper26_3.container.BaseOpenInventory;
import com.lishid.openinv.internal.paper26_3.container.bukkit.OpenDummyPlayerInventory;
import com.lishid.openinv.internal.paper26_3.container.bukkit.OpenPlayerInventorySelf;
import com.lishid.openinv.internal.paper26_3.container.slot.ContentDrop;
import com.lishid.openinv.internal.paper26_3.container.slot.SlotEquipment;
import com.lishid.openinv.internal.paper26_3.container.slot.SlotViewOnly;
import com.lishid.openinv.util.Permissions;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.ChestMenu;
@@ -16,7 +18,6 @@ import net.minecraft.world.inventory.MenuType;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import org.bukkit.craftbukkit.inventory.CraftInventoryView;
import org.bukkit.craftbukkit.inventory.CraftItemStack;
import org.bukkit.event.inventory.InventoryType;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryView;
@@ -28,8 +29,14 @@ public class BaseOpenInventoryMenu extends OpenSyncMenu<BaseOpenInventory> {
private int offset;
public BaseOpenInventoryMenu(BaseOpenInventory inventory, ServerPlayer viewer, int i, boolean viewOnly) {
super(getMenuType(inventory, viewer), i, inventory, viewer, viewOnly);
public BaseOpenInventoryMenu(
InventoryFactory<ServerPlayer, ItemStack, Container, Slot, EquipmentSlot> factory,
BaseOpenInventory inventory,
ServerPlayer viewer,
int i,
boolean viewOnly
) {
super(getMenuType(inventory, viewer), i, factory, inventory, viewer, viewOnly);
}
private static MenuType<ChestMenu> getMenuType(BaseOpenInventory inventory, ServerPlayer viewer) {
@@ -64,9 +71,9 @@ public class BaseOpenInventoryMenu extends OpenSyncMenu<BaseOpenInventory> {
return new SlotViewOnly(container, index, x, y);
}
if (slot instanceof ContentEquipment.SlotEquipment equipment) {
if (slot instanceof SlotEquipment equipment) {
if (viewOnly) {
return SlotViewOnly.wrap(slot);
return factory.wrapViewOnly(slot);
}
Permissions perm = switch (equipment.getEquipmentSlot()) {
@@ -93,7 +100,7 @@ public class BaseOpenInventoryMenu extends OpenSyncMenu<BaseOpenInventory> {
}
if (viewOnly) {
return SlotViewOnly.wrap(slot);
return factory.wrapViewOnly(slot);
}
return slot;
@@ -118,7 +125,7 @@ public class BaseOpenInventoryMenu extends OpenSyncMenu<BaseOpenInventory> {
}
Slot slot = slots.get(index);
return CraftItemStack.asCraftMirror(slot.hasItem() ? slot.getItem() : ItemStack.EMPTY);
return factory.asBukkitMirror(slot.hasItem() ? slot.getItem() : ItemStack.EMPTY);
}
@Override
@@ -227,7 +234,7 @@ public class BaseOpenInventoryMenu extends OpenSyncMenu<BaseOpenInventory> {
// Locate the correct slot in the contents following the main inventory.
for (int extra = container.getOwnerHandle().getInventory().getNonEquipmentItems().size() - offset; extra < topSize; ++extra) {
Slot extraSlot = getSlot(extra);
if (extraSlot instanceof ContentEquipment.SlotEquipment equipSlot
if (extraSlot instanceof SlotEquipment equipSlot
&& equipSlot.getEquipmentSlot() == equipmentSlot) {
// If we've found a matching slot, try to move to it.
// If this succeeds, even partially, we will not attempt to move to other slots.
@@ -1,11 +1,13 @@
package com.lishid.openinv.internal.paper26_1.container.menu;
package com.lishid.openinv.internal.paper26_3.container.menu;
import com.lishid.openinv.internal.ISpecialInventory;
import com.lishid.openinv.internal.InternalOwned;
import com.lishid.openinv.internal.paper26_1.container.bukkit.OpenDummyInventory;
import com.lishid.openinv.internal.paper26_1.container.slot.SlotViewOnly;
import com.github.jikoo.openinv.internal.container.InternalOwned;
import com.github.jikoo.openinv.internal.container.slot.InventoryFactory;
import com.lishid.openinv.internal.paper26_3.container.bukkit.OpenDummyInventory;
import com.lishid.openinv.internal.paper26_3.container.slot.SlotViewOnly;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.inventory.ChestMenu;
@@ -28,6 +30,7 @@ public abstract class OpenChestMenu<T extends Container & ISpecialInventory & In
protected static final int BOTTOM_INVENTORY_SIZE = 36;
protected final InventoryFactory<ServerPlayer, ItemStack, Container, Slot, EquipmentSlot> factory;
protected final T container;
protected final ServerPlayer viewer;
protected final boolean viewOnly;
@@ -38,11 +41,13 @@ public abstract class OpenChestMenu<T extends Container & ISpecialInventory & In
protected OpenChestMenu(
MenuType<ChestMenu> type,
int containerCounter,
InventoryFactory<ServerPlayer, ItemStack, Container, Slot, EquipmentSlot> factory,
T container,
ServerPlayer viewer,
boolean viewOnly
) {
super(type, containerCounter);
this.factory = factory;
this.container = container;
this.viewer = viewer;
this.viewOnly = viewOnly;
@@ -109,7 +114,7 @@ public abstract class OpenChestMenu<T extends Container & ISpecialInventory & In
protected Slot getUpperSlot(int index, int x, int y) {
Slot slot = new Slot(container, index, x, y);
if (viewOnly) {
return SlotViewOnly.wrap(slot);
return factory.wrapViewOnly(slot);
}
return slot;
}
@@ -163,17 +168,17 @@ public abstract class OpenChestMenu<T extends Container & ISpecialInventory & In
private int getTopSize(ServerPlayer viewer) {
MenuType<?> menuType = getType();
if (menuType == MenuType.GENERIC_9x1) {
if (MenuType.GENERIC_9x1.equals(menuType)) {
return 9;
} else if (menuType == MenuType.GENERIC_9x2) {
} else if (MenuType.GENERIC_9x2.equals(menuType)) {
return 18;
} else if (menuType == MenuType.GENERIC_9x3) {
} else if (MenuType.GENERIC_9x3.equals(menuType)) {
return 27;
} else if (menuType == MenuType.GENERIC_9x4) {
} else if (MenuType.GENERIC_9x4.equals(menuType)) {
return 36;
} else if (menuType == MenuType.GENERIC_9x5) {
} else if (MenuType.GENERIC_9x5.equals(menuType)) {
return 45;
} else if (menuType == MenuType.GENERIC_9x6) {
} else if (MenuType.GENERIC_9x6.equals(menuType)) {
return 54;
}
// This is a bit gross, but allows us a safe fallthrough.
@@ -1,7 +1,10 @@
package com.lishid.openinv.internal.paper26_1.container.menu;
package com.lishid.openinv.internal.paper26_3.container.menu;
import com.lishid.openinv.internal.paper26_1.container.OpenEnderChest;
import com.github.jikoo.openinv.internal.container.slot.InventoryFactory;
import com.lishid.openinv.internal.paper26_3.container.OpenEnderChest;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.ContainerInput;
import net.minecraft.world.inventory.Slot;
@@ -12,12 +15,13 @@ import org.jspecify.annotations.NullMarked;
public class OpenEnderChestMenu extends OpenSyncMenu<OpenEnderChest> {
public OpenEnderChestMenu(
InventoryFactory<ServerPlayer, ItemStack, Container, Slot, EquipmentSlot> factory,
OpenEnderChest enderChest,
ServerPlayer viewer,
int containerId,
boolean viewOnly
) {
super(getChestMenuType(enderChest.getContainerSize()), containerId, enderChest, viewer, viewOnly);
super(getChestMenuType(enderChest.getContainerSize()), containerId, factory, enderChest, viewer, viewOnly);
}
@Override
@@ -0,0 +1,37 @@
package com.lishid.openinv.internal.paper26_3.container.menu;
import com.github.jikoo.openinv.internal.container.slot.InventoryFactory;
import com.lishid.openinv.internal.paper26_3.container.BaseOpenInventory;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.ContainerInput;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import org.jspecify.annotations.NullMarked;
@NullMarked
public class OpenInventoryMenu extends BaseOpenInventoryMenu {
public OpenInventoryMenu(
InventoryFactory<ServerPlayer, ItemStack, Container, Slot, EquipmentSlot> factory,
BaseOpenInventory inventory,
ServerPlayer viewer,
int i,
boolean viewOnly
) {
super(factory, inventory, viewer, i, viewOnly);
}
@Override
public void clicked(int slotIndex, int buttonNum, ContainerInput containerInput, Player player) {
if (viewOnly) {
if (containerInput == ContainerInput.QUICK_CRAFT) {
sendAllDataToRemote();
}
}
super.clicked(slotIndex, buttonNum, containerInput, player);
}
}
@@ -1,11 +1,13 @@
package com.lishid.openinv.internal.paper26_1.container.menu;
package com.lishid.openinv.internal.paper26_3.container.menu;
import com.google.common.base.Suppliers;
import com.lishid.openinv.internal.ISpecialInventory;
import com.lishid.openinv.internal.InternalOwned;
import com.lishid.openinv.internal.paper26_1.container.slot.SlotPlaceholder;
import com.github.jikoo.openinv.internal.container.InternalOwned;
import com.github.jikoo.openinv.internal.container.slot.InventoryFactory;
import com.lishid.openinv.internal.paper26_3.container.slot.SlotPlaceholder;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.inventory.ChestMenu;
import net.minecraft.world.inventory.ContainerListener;
@@ -41,11 +43,12 @@ public abstract class OpenSyncMenu<T extends Container & ISpecialInventory & Int
protected OpenSyncMenu(
MenuType<ChestMenu> type,
int containerCounter,
InventoryFactory<ServerPlayer, ItemStack, Container, Slot, EquipmentSlot> factory,
T container,
ServerPlayer viewer,
boolean viewOnly
) {
super(type, containerCounter, container, viewer, viewOnly);
super(type, containerCounter, factory, container, viewer, viewOnly);
}
@Override
@@ -89,7 +92,7 @@ public abstract class OpenSyncMenu<T extends Container & ISpecialInventory & Int
for (int index = 0; index < slots.size(); ++index) {
Slot slot = slots.get(index);
ItemStack itemStack = slot instanceof SlotPlaceholder placeholder ? placeholder.getOrDefault() : slot.getItem();
contentsCopy.add(itemStack);
contentsCopy.add(itemStack.copy());
this.remoteSlots.get(index).force(itemStack);
}
@@ -1,37 +1,36 @@
package com.lishid.openinv.internal.paper26_1.container.slot;
package com.lishid.openinv.internal.paper26_3.container.slot;
import com.lishid.openinv.internal.paper26_1.container.slot.placeholder.Placeholders;
import com.lishid.openinv.internal.paper26_1.player.OpenPlayer;
import com.github.jikoo.openinv.internal.container.slot.Content;
import com.lishid.openinv.internal.paper26_3.container.slot.placeholder.Placeholders;
import com.lishid.openinv.internal.paper26_3.player.OpenPlayer;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.util.Prediction;
import net.minecraft.world.Container;
import net.minecraft.world.ContainerHelper;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import org.bukkit.event.inventory.InventoryType;
import org.jetbrains.annotations.NotNull;
import org.jspecify.annotations.NullMarked;
import java.util.List;
/**
* A slot in a survival crafting inventory. Unavailable when not online in a survival mode.
*/
public class ContentCrafting implements Content {
@NullMarked
public class ContentCrafting implements Content<ServerPlayer, ItemStack, Container, Slot> {
private final int index;
private ServerPlayer holder;
private List<ItemStack> items;
protected ServerPlayer holder;
protected List<ItemStack> items;
public ContentCrafting(@NotNull ServerPlayer holder, int index) {
protected ContentCrafting(ServerPlayer holder, int index) {
setHolder(holder);
this.index = index;
}
private boolean isAvailable() {
return isAvailable(holder);
}
public static boolean isAvailable(@NotNull ServerPlayer holder) {
public static boolean isAvailable(ServerPlayer holder) {
// Player must be online and not in creative - since the creative client is (semi-)authoritative,
// it ignores changes without extra help, and will delete the item as a result.
// Spectator mode is technically possible but may cause the item to be dropped if the client opens an inventory.
@@ -39,7 +38,7 @@ public class ContentCrafting implements Content {
}
@Override
public void setHolder(@NotNull ServerPlayer holder) {
public void setHolder(ServerPlayer holder) {
this.holder = holder;
// Note: CraftingContainer#getItems is immutable! Be careful with updates.
this.items = holder.inventoryMenu.getCraftSlots().getContents();
@@ -47,12 +46,12 @@ public class ContentCrafting implements Content {
@Override
public ItemStack get() {
return isAvailable() ? items.get(index) : ItemStack.EMPTY;
return isAvailable(holder) ? items.get(index) : ItemStack.EMPTY;
}
@Override
public ItemStack remove() {
if (!this.isAvailable()) {
if (!isAvailable(holder)) {
return ItemStack.EMPTY;
}
ItemStack removed = items.remove(index);
@@ -65,7 +64,7 @@ public class ContentCrafting implements Content {
@Override
public ItemStack removePartial(int amount) {
if (!this.isAvailable()) {
if (!isAvailable(holder)) {
return ItemStack.EMPTY;
}
ItemStack removed = ContainerHelper.removeItem(items, index, amount);
@@ -78,11 +77,11 @@ public class ContentCrafting implements Content {
@Override
public void set(ItemStack itemStack) {
if (isAvailable()) {
if (isAvailable(holder)) {
items.set(index, itemStack);
holder.inventoryMenu.slotsChanged(holder.inventoryMenu.getCraftSlots());
} else {
holder.drop(itemStack, false);
this.holder.drop(itemStack, false, Prediction.SERVER_ONLY);
}
}
@@ -93,7 +92,7 @@ public class ContentCrafting implements Content {
@Override
public InventoryType.SlotType getSlotType() {
return isAvailable() ? InventoryType.SlotType.CRAFTING : InventoryType.SlotType.OUTSIDE;
return isAvailable(holder) ? InventoryType.SlotType.CRAFTING : InventoryType.SlotType.OUTSIDE;
}
public class SlotCrafting extends SlotPlaceholder {
@@ -104,27 +103,27 @@ public class ContentCrafting implements Content {
@Override
public ItemStack getOrDefault() {
return isAvailable() ? items.get(ContentCrafting.this.index) : Placeholders.survivalOnly(holder);
return isAvailable(holder) ? items.get(ContentCrafting.this.index) : Placeholders.survivalOnly(holder);
}
@Override
public boolean mayPickup(@NotNull Player player) {
return isAvailable();
public boolean mayPickup(Player player) {
return isAvailable(holder);
}
@Override
public boolean mayPlace(@NotNull ItemStack itemStack) {
return isAvailable();
public boolean mayPlace(ItemStack itemStack) {
return isAvailable(holder);
}
@Override
public boolean hasItem() {
return isAvailable() && super.hasItem();
return isAvailable(holder) && super.hasItem();
}
@Override
public boolean isFake() {
return !isAvailable();
return !isAvailable(holder);
}
}
@@ -1,22 +1,23 @@
package com.lishid.openinv.internal.paper26_1.container.slot;
package com.lishid.openinv.internal.paper26_3.container.slot;
import com.lishid.openinv.internal.paper26_1.container.slot.placeholder.Placeholders;
import com.lishid.openinv.internal.paper26_3.container.slot.placeholder.Placeholders;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
import net.minecraft.world.inventory.InventoryMenu;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import org.bukkit.event.inventory.InventoryType;
import org.jetbrains.annotations.NotNull;
import org.jspecify.annotations.NullMarked;
/**
* A slot allowing viewing of the crafting result.
*
* <p>Unmodifiable because I said so. Use your own crafting grid.</p>
*/
public class ContentCraftingResult extends ContentViewOnly {
@NullMarked
class ContentCraftingResult extends ContentViewOnly {
public ContentCraftingResult(@NotNull ServerPlayer holder) {
ContentCraftingResult(ServerPlayer holder) {
super(holder);
}
@@ -1,28 +1,31 @@
package com.lishid.openinv.internal.paper26_1.container.slot;
package com.lishid.openinv.internal.paper26_3.container.slot;
import com.lishid.openinv.internal.paper26_1.container.slot.placeholder.Placeholders;
import com.lishid.openinv.internal.paper26_1.player.OpenPlayer;
import com.github.jikoo.openinv.internal.container.slot.Content;
import com.lishid.openinv.internal.paper26_3.container.slot.placeholder.Placeholders;
import com.lishid.openinv.internal.paper26_3.player.OpenPlayer;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.util.Prediction;
import net.minecraft.world.Container;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import org.bukkit.event.inventory.InventoryType;
import org.jetbrains.annotations.NotNull;
import org.jspecify.annotations.NullMarked;
/**
* A slot wrapping the active menu's cursor. Unavailable when not online in a survival mode.
*/
public class ContentCursor implements Content {
@NullMarked
public class ContentCursor implements Content<ServerPlayer, ItemStack, Container, Slot> {
private @NotNull ServerPlayer holder;
protected ServerPlayer holder;
public ContentCursor(@NotNull ServerPlayer holder) {
protected ContentCursor(ServerPlayer holder) {
this.holder = holder;
}
@Override
public void setHolder(@NotNull ServerPlayer holder) {
public void setHolder(ServerPlayer holder) {
this.holder = holder;
}
@@ -56,11 +59,11 @@ public class ContentCursor implements Content {
if (isAvailable()) {
holder.containerMenu.setCarried(itemStack);
} else {
holder.drop(itemStack, false);
this.holder.drop(itemStack, false, Prediction.SERVER_ONLY);
}
}
private boolean isAvailable() {
protected boolean isAvailable() {
// Player must be online and not in creative - since the creative client is (semi-)authoritative,
// it ignores changes without extra help, and will delete the item as a result.
// Spectator mode is technically possible but may cause the item to be dropped if the client opens an inventory.
@@ -94,12 +97,12 @@ public class ContentCursor implements Content {
}
@Override
public boolean mayPickup(@NotNull Player player) {
public boolean mayPickup(Player player) {
return isAvailable();
}
@Override
public boolean mayPlace(@NotNull ItemStack itemStack) {
public boolean mayPlace(ItemStack itemStack) {
return isAvailable();
}
@@ -1,27 +1,30 @@
package com.lishid.openinv.internal.paper26_1.container.slot;
package com.lishid.openinv.internal.paper26_3.container.slot;
import com.lishid.openinv.internal.paper26_1.container.slot.placeholder.Placeholders;
import com.lishid.openinv.internal.paper26_1.player.OpenPlayer;
import com.github.jikoo.openinv.internal.container.slot.Content;
import com.lishid.openinv.internal.paper26_3.container.slot.placeholder.Placeholders;
import com.lishid.openinv.internal.paper26_3.player.OpenPlayer;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.util.Prediction;
import net.minecraft.world.Container;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import org.bukkit.event.inventory.InventoryType;
import org.jetbrains.annotations.NotNull;
import org.jspecify.annotations.NullMarked;
/**
* A fake slot used to drop items. Unavailable offline.
*/
public class ContentDrop implements Content {
@NullMarked
public class ContentDrop implements Content<ServerPlayer, ItemStack, Container, Slot> {
private ServerPlayer holder;
protected ServerPlayer holder;
public ContentDrop(@NotNull ServerPlayer holder) {
protected ContentDrop(ServerPlayer holder) {
this.holder = holder;
}
@Override
public void setHolder(@NotNull ServerPlayer holder) {
public void setHolder(ServerPlayer holder) {
this.holder = holder;
}
@@ -42,7 +45,7 @@ public class ContentDrop implements Content {
@Override
public void set(ItemStack itemStack) {
holder.drop(itemStack, true);
this.holder.drop(itemStack, false, Prediction.SERVER_ONLY);
}
@Override
@@ -70,7 +73,7 @@ public class ContentDrop implements Content {
}
@Override
public boolean mayPlace(@NotNull ItemStack itemStack) {
public boolean mayPlace(ItemStack itemStack) {
return OpenPlayer.isConnected(holder.connection);
}
@@ -1,6 +1,7 @@
package com.lishid.openinv.internal.paper26_1.container.slot;
package com.lishid.openinv.internal.paper26_3.container.slot;
import com.lishid.openinv.internal.paper26_1.container.slot.placeholder.Placeholders;
import com.github.jikoo.openinv.internal.container.slot.Content;
import com.lishid.openinv.internal.paper26_3.container.slot.placeholder.Placeholders;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
import net.minecraft.world.entity.EntityEquipment;
@@ -8,18 +9,19 @@ import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import org.bukkit.event.inventory.InventoryType;
import org.jetbrains.annotations.NotNull;
import org.jspecify.annotations.NullMarked;
/**
* A slot for equipment that displays placeholders if empty.
*/
public class ContentEquipment implements Content {
@NullMarked
class ContentEquipment implements Content<ServerPlayer, ItemStack, Container, Slot> {
private EntityEquipment equipment;
private final ItemStack placeholder;
private final EquipmentSlot equipmentSlot;
public ContentEquipment(ServerPlayer holder, EquipmentSlot equipmentSlot) {
ContentEquipment(ServerPlayer holder, EquipmentSlot equipmentSlot) {
setHolder(holder);
placeholder = switch (equipmentSlot) {
case HEAD -> Placeholders.emptyHelmet;
@@ -32,7 +34,7 @@ public class ContentEquipment implements Content {
}
@Override
public void setHolder(@NotNull ServerPlayer holder) {
public void setHolder(ServerPlayer holder) {
this.equipment = holder.getInventory().equipment;
}
@@ -62,7 +64,7 @@ public class ContentEquipment implements Content {
@Override
public Slot asSlot(Container container, int slot, int x, int y) {
return new SlotEquipment(container, slot, x, y);
return new SlotEquipment(container, slot, x, y, placeholder, equipmentSlot);
}
@Override
@@ -70,40 +72,4 @@ public class ContentEquipment implements Content {
return InventoryType.SlotType.ARMOR;
}
public class SlotEquipment extends SlotPlaceholder {
private ServerPlayer viewer;
SlotEquipment(Container container, int index, int x, int y) {
super(container, index, x, y);
}
@Override
public ItemStack getOrDefault() {
ItemStack itemStack = getItem();
if (!itemStack.isEmpty()) {
return itemStack;
}
return placeholder;
}
public EquipmentSlot getEquipmentSlot() {
return equipmentSlot;
}
public void onlyEquipmentFor(ServerPlayer viewer) {
this.viewer = viewer;
}
@Override
public boolean mayPlace(@NotNull ItemStack itemStack) {
if (viewer == null) {
return true;
}
return equipmentSlot == EquipmentSlot.OFFHAND || viewer.getEquipmentSlotForItem(itemStack) == equipmentSlot;
}
}
}
@@ -1,29 +1,39 @@
package com.lishid.openinv.internal.paper26_1.container.slot;
package com.lishid.openinv.internal.paper26_3.container.slot;
import com.github.jikoo.openinv.internal.container.slot.Content;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
import net.minecraft.world.ContainerHelper;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import org.bukkit.event.inventory.InventoryType;
import org.jspecify.annotations.NullMarked;
import java.util.List;
/**
* A normal slot backed by an item list.
*/
public abstract class ContentList implements Content {
@NullMarked
abstract class ContentList implements Content<ServerPlayer, ItemStack, Container, Slot> {
private final int index;
private final InventoryType.SlotType slotType;
protected List<ItemStack> items;
private List<ItemStack> items;
public ContentList(ServerPlayer holder, int index, InventoryType.SlotType slotType) {
ContentList(ServerPlayer holder, int index, InventoryType.SlotType slotType) {
this.index = index;
this.slotType = slotType;
setHolder(holder);
}
@Override
public void setHolder(ServerPlayer holder) {
this.items = getItems(holder);
}
protected abstract List<ItemStack> getItems(ServerPlayer holder);
@Override
public ItemStack get() {
return items.get(index);
@@ -32,7 +42,7 @@ public abstract class ContentList implements Content {
@Override
public ItemStack remove() {
ItemStack removed = items.remove(index);
return removed == null || removed.isEmpty() ? ItemStack.EMPTY : removed;
return removed.isEmpty() ? ItemStack.EMPTY : removed;
}
@Override
@@ -1,6 +1,7 @@
package com.lishid.openinv.internal.paper26_1.container.slot;
package com.lishid.openinv.internal.paper26_3.container.slot;
import com.lishid.openinv.internal.paper26_1.player.OpenPlayer;
import com.lishid.openinv.internal.paper26_3.container.slot.placeholder.Placeholders;
import com.lishid.openinv.internal.paper26_3.player.OpenPlayer;
import net.minecraft.network.protocol.game.ClientboundContainerSetSlotPacket;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
@@ -8,21 +9,25 @@ import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.inventory.InventoryMenu;
import net.minecraft.world.inventory.Slot;
import org.bukkit.event.inventory.InventoryType;
import org.jetbrains.annotations.NotNull;
import org.jspecify.annotations.NullMarked;
import java.util.Objects;
/**
* A slot for equipment that updates held items if necessary.
*/
public class ContentOffHand extends ContentEquipment {
@NullMarked
class ContentOffHand extends ContentEquipment {
private ServerPlayer holder;
public ContentOffHand(ServerPlayer holder) {
ContentOffHand(ServerPlayer holder) {
super(holder, EquipmentSlot.OFFHAND);
this.holder = holder; // Redundant, but silences nullity warning.
}
@Override
public void setHolder(@NotNull ServerPlayer holder) {
public void setHolder(ServerPlayer holder) {
super.setHolder(holder);
this.holder = holder;
}
@@ -34,10 +39,10 @@ public class ContentOffHand extends ContentEquipment {
@Override
public Slot asSlot(Container container, int slot, int x, int y) {
return new SlotEquipment(container, slot, x, y) {
return new SlotEquipment(container, slot, x, y, Placeholders.emptyOffHand, EquipmentSlot.OFFHAND) {
@Override
public void setChanged() {
if (OpenPlayer.isConnected(holder.connection) && holder.containerMenu != holder.inventoryMenu) {
if (OpenPlayer.isConnected(holder.connection) && !Objects.equals(holder.containerMenu, holder.inventoryMenu)) {
holder.connection.send(
new ClientboundContainerSetSlotPacket(
holder.inventoryMenu.containerId,
@@ -1,25 +1,28 @@
package com.lishid.openinv.internal.paper26_1.container.slot;
package com.lishid.openinv.internal.paper26_3.container.slot;
import com.github.jikoo.openinv.internal.container.slot.Content;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.util.Prediction;
import net.minecraft.world.Container;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import org.bukkit.event.inventory.InventoryType;
import org.jetbrains.annotations.NotNull;
import org.jspecify.annotations.NullMarked;
/**
* A view-only slot that can't be interacted with.
*/
public class ContentViewOnly implements Content {
@NullMarked
public class ContentViewOnly implements Content<ServerPlayer, ItemStack, Container, Slot> {
protected @NotNull ServerPlayer holder;
protected ServerPlayer holder;
public ContentViewOnly(@NotNull ServerPlayer holder) {
protected ContentViewOnly(ServerPlayer holder) {
this.holder = holder;
}
@Override
public void setHolder(@NotNull ServerPlayer holder) {
public void setHolder(ServerPlayer holder) {
this.holder = holder;
}
@@ -40,7 +43,7 @@ public class ContentViewOnly implements Content {
@Override
public void set(ItemStack itemStack) {
this.holder.drop(itemStack, false);
this.holder.drop(itemStack, false, Prediction.SERVER_ONLY);
}
@Override
@@ -0,0 +1,115 @@
package com.lishid.openinv.internal.paper26_3.container.slot;
import com.github.jikoo.openinv.internal.container.slot.Content;
import com.github.jikoo.openinv.internal.container.slot.InventoryFactory;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import org.bukkit.craftbukkit.inventory.CraftItemStack;
import org.bukkit.event.inventory.InventoryType;
import org.jspecify.annotations.NullMarked;
import java.util.List;
import java.util.function.Function;
@NullMarked
public class OpenInventoryFactory implements InventoryFactory<ServerPlayer, ItemStack, Container, Slot, EquipmentSlot> {
@Override
public Content<ServerPlayer, ItemStack, Container, Slot> newCrafting(ServerPlayer holder, int index) {
return new ContentCrafting(holder, index);
}
@Override
public Content<ServerPlayer, ItemStack, Container, Slot> newCraftingResult(ServerPlayer holder) {
return new ContentCraftingResult(holder);
}
@Override
public Content<ServerPlayer, ItemStack, Container, Slot> newCursor(ServerPlayer holder) {
return new ContentCursor(holder);
}
@Override
public Content<ServerPlayer, ItemStack, Container, Slot> newDrop(ServerPlayer holder) {
return new ContentDrop(holder);
}
@Override
public Content<ServerPlayer, ItemStack, Container, Slot> newEquipment(
ServerPlayer holder,
EquipmentSlot equipmentSlot
) {
return new ContentEquipment(holder, equipmentSlot);
}
@Override
public Content<ServerPlayer, ItemStack, Container, Slot> newList(
ServerPlayer holder,
int index,
InventoryType.SlotType slotType,
Function<ServerPlayer, List<ItemStack>> listSupplier
) {
return new ContentList(holder, index, slotType) {
@Override
protected List<ItemStack> getItems(ServerPlayer holder) {
return listSupplier.apply(holder);
}
};
}
@Override
public Content<ServerPlayer, ItemStack, Container, Slot> newOffHand(ServerPlayer holder) {
return new ContentOffHand(holder);
}
@Override
public Content<ServerPlayer, ItemStack, Container, Slot> newViewOnly(ServerPlayer holder) {
return new ContentViewOnly(holder);
}
@Override
public Content<ServerPlayer, ItemStack, Container, Slot> newViewOnly(ServerPlayer holder, ItemStack itemStack) {
return new ContentViewOnly(holder) {
@Override
public Slot asSlot(Container container, int slot, int x, int y) {
return new SlotViewOnly(container, slot, x, y) {
@Override
public ItemStack getOrDefault() {
return itemStack;
}
};
}
};
}
@Override
public Slot wrapViewOnly(Slot wrapped) {
SlotViewOnly wrapper;
if (wrapped instanceof SlotPlaceholder placeholder) {
wrapper = new SlotViewOnly(wrapped.container, wrapped.getContainerSlot(), wrapped.x, wrapped.y) {
@Override
public ItemStack getOrDefault() {
return placeholder.getOrDefault();
}
};
} else {
wrapper = new SlotViewOnly(wrapped.container, wrapped.getContainerSlot(), wrapped.x, wrapped.y) {
@Override
public ItemStack getOrDefault() {
return wrapped.getItem();
}
};
}
wrapper.index = wrapped.index;
return wrapper;
}
@Override
public org.bukkit.inventory.ItemStack asBukkitMirror(ItemStack itemStack) {
return CraftItemStack.asBukkitMirror(itemStack);
}
}
@@ -0,0 +1,49 @@
package com.lishid.openinv.internal.paper26_3.container.slot;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.item.ItemStack;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
@NullMarked
public class SlotEquipment extends SlotPlaceholder {
private final ItemStack placeholder;
private final EquipmentSlot equipmentSlot;
private @Nullable ServerPlayer viewer;
SlotEquipment(Container container, int index, int x, int y, ItemStack placeholder, EquipmentSlot equipmentSlot) {
super(container, index, x, y);
this.placeholder = placeholder;
this.equipmentSlot = equipmentSlot;
}
@Override
public ItemStack getOrDefault() {
ItemStack itemStack = getItem();
if (!itemStack.isEmpty()) {
return itemStack;
}
return placeholder;
}
public EquipmentSlot getEquipmentSlot() {
return equipmentSlot;
}
public void onlyEquipmentFor(ServerPlayer viewer) {
this.viewer = viewer;
}
@Override
public boolean mayPlace(ItemStack itemStack) {
if (viewer == null) {
return true;
}
return equipmentSlot == EquipmentSlot.OFFHAND || viewer.getEquipmentSlotForItem(itemStack) == equipmentSlot;
}
}
@@ -1,17 +1,19 @@
package com.github.jikoo.openinv.internal.spigot26_1.container.slot;
package com.lishid.openinv.internal.paper26_3.container.slot;
import net.minecraft.world.Container;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import org.jspecify.annotations.NullMarked;
/**
* An implementation of a slot as used by a menu that may have fake placeholder items.
*
* <p>Used to prevent plugins (particularly sorting plugins) from adding placeholders to inventories.</p>
*/
@NullMarked
public abstract class SlotPlaceholder extends Slot {
public SlotPlaceholder(Container container, int index, int x, int y) {
SlotPlaceholder(Container container, int index, int x, int y) {
super(container, index, x, y);
}
@@ -0,0 +1,131 @@
package com.lishid.openinv.internal.paper26_3.container.slot;
import com.lishid.openinv.internal.paper26_3.container.slot.placeholder.Placeholders;
import net.minecraft.world.Container;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import org.jspecify.annotations.NullMarked;
import java.util.Optional;
/**
* A view-only {@link Slot}. "Blank" by default, but can wrap another slot to display its content.
*/
@NullMarked
public class SlotViewOnly extends SlotPlaceholder {
public SlotViewOnly(Container container, int index, int x, int y) {
super(container, index, x, y);
}
@Override
public ItemStack getOrDefault() {
return Placeholders.notSlot;
}
@Override
public void onQuickCraft(ItemStack itemStack1, ItemStack itemStack2) {
}
@Override
public void onTake(Player player, ItemStack itemStack) {
}
@Override
public boolean mayPlace(ItemStack itemStack) {
return false;
}
@Override
public ItemStack getItem() {
return ItemStack.EMPTY;
}
@Override
public boolean hasItem() {
return false;
}
@Override
public void setByPlayer(ItemStack newStack) {
}
@Override
public void setByPlayer(ItemStack newStack, ItemStack oldStack) {
}
@Override
public void set(ItemStack itemStack) {
}
@Override
public void setChanged() {
}
@Override
public int getMaxStackSize() {
return 0;
}
@Override
public int getMaxStackSize(ItemStack itemStack) {
return 0;
}
@Override
public ItemStack remove(int amount) {
return ItemStack.EMPTY;
}
@Override
public boolean mayPickup(Player player) {
return false;
}
@Override
public boolean isActive() {
return false;
}
@Override
public Optional<ItemStack> tryRemove(int var0, int var1, Player player) {
return Optional.empty();
}
@Override
public ItemStack safeTake(int var0, int var1, Player player) {
return ItemStack.EMPTY;
}
@Override
public ItemStack safeClone(Player player) {
return ItemStack.EMPTY;
}
@Override
public ItemStack safeInsert(ItemStack itemStack) {
return itemStack;
}
@Override
public ItemStack safeInsert(ItemStack itemStack, int amount) {
return itemStack;
}
@Override
public boolean allowModification(Player player) {
return false;
}
@Override
public boolean isHighlightable() {
return false;
}
@Override
public boolean isFake() {
return true;
}
}
@@ -1,4 +1,4 @@
package com.lishid.openinv.internal.paper26_1.container.slot.placeholder;
package com.lishid.openinv.internal.paper26_3.container.slot.placeholder;
import com.mojang.serialization.DataResult;
import net.minecraft.core.Registry;
@@ -7,11 +7,15 @@ import net.minecraft.core.component.DataComponents;
import net.minecraft.core.registries.Registries;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtOps;
import net.minecraft.nbt.TagParser;
import net.minecraft.network.chat.Component;
import net.minecraft.world.item.DyeColor;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.component.CustomModelData;
import net.minecraft.world.item.component.DyedItemColor;
import net.minecraft.world.item.component.TooltipDisplay;
import net.minecraft.world.level.GameType;
import net.minecraft.world.level.ItemLike;
import net.minecraft.world.level.block.entity.BannerPattern;
@@ -19,12 +23,14 @@ import net.minecraft.world.level.block.entity.BannerPatternLayers;
import net.minecraft.world.level.block.entity.BannerPatterns;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.craftbukkit.CraftRegistry;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
import java.util.LinkedHashSet;
import java.util.List;
public abstract class PlaceholderLoaderBase {
@NullMarked
public class PlaceholderLoader {
public void load(@Nullable ConfigurationSection section) throws Exception {
Placeholders.craftingOutput = parse(section, "crafting-output", defaultCraftingOutput());
@@ -52,10 +58,10 @@ public abstract class PlaceholderLoaderBase {
Placeholders.BLOCKED_GAME_TYPE.put(GameType.SPECTATOR, parse(section, "blocked.spectator", Placeholders.BLOCKED_GAME_TYPE.get(GameType.SPECTATOR)));
}
protected @NotNull ItemStack parse(
protected ItemStack parse(
@Nullable ConfigurationSection section,
@NotNull String path,
@NotNull ItemStack defaultStack
String path,
ItemStack defaultStack
) throws Exception {
if (section == null) {
return defaultStack;
@@ -67,7 +73,7 @@ public abstract class PlaceholderLoaderBase {
return defaultStack;
}
CompoundTag compoundTag = parseTag(itemText);
CompoundTag compoundTag = TagParser.parseCompoundFully(itemText);
DataResult<ItemStack> parsed = ItemStack.CODEC.parse(CraftRegistry.getMinecraftRegistry().createSerializationContext(NbtOps.INSTANCE), compoundTag);
ItemStack itemStack;
try {
@@ -78,15 +84,18 @@ public abstract class PlaceholderLoaderBase {
return itemStack == null ? defaultStack : itemStack;
}
protected abstract @NotNull CompoundTag parseTag(@NotNull String itemText) throws Exception;
private static final CustomModelData DEFAULT_CUSTOM_MODEL_DATA = new CustomModelData(List.of(), List.of(), List.of("openinv:custom"), List.of());
private static final TooltipDisplay HIDE_TOOLTIP = new TooltipDisplay(true, new LinkedHashSet<>());
protected abstract void addModelData(@NotNull ItemStack itemStack);
protected void addModelData(ItemStack itemStack) {
itemStack.set(DataComponents.CUSTOM_MODEL_DATA, DEFAULT_CUSTOM_MODEL_DATA);
}
protected abstract void hideTooltip(@NotNull ItemStack itemStack);
protected void hideTooltip(ItemStack itemStack) {
itemStack.set(DataComponents.TOOLTIP_DISPLAY, HIDE_TOOLTIP);
}
protected abstract DyedItemColor getDye(int rgb);
protected @NotNull ItemStack defaultCraftingOutput() {
protected ItemStack defaultCraftingOutput() {
// Crafting table: "Crafting"
ItemStack itemStack = new ItemStack(Items.CRAFTING_TABLE);
itemStack.set(DataComponents.ITEM_NAME, Component.translatable("container.crafting"));
@@ -94,9 +103,9 @@ public abstract class PlaceholderLoaderBase {
return itemStack;
}
protected @NotNull ItemStack defaultCursor() {
protected ItemStack defaultCursor() {
// Cursor-like banner with no tooltip
ItemStack itemStack = new ItemStack(Items.WHITE_BANNER);
ItemStack itemStack = new ItemStack(getDefaultCursorItem());
RegistryAccess minecraftRegistry = CraftRegistry.getMinecraftRegistry();
Registry<BannerPattern> bannerPatterns = minecraftRegistry.lookupOrThrow(Registries.BANNER_PATTERN);
BannerPattern halfDiagBottomRight = bannerPatterns.getOrThrow(BannerPatterns.DIAGONAL_RIGHT).value();
@@ -114,7 +123,7 @@ public abstract class PlaceholderLoaderBase {
return itemStack;
}
protected @NotNull ItemStack defaultDrop() {
protected ItemStack defaultDrop() {
// Dropper: "Drop Selected Item"
ItemStack itemStack = new ItemStack(Items.DROPPER);
// Note: translatable component, not keybind component! We want the text identifying the keybind, not the key.
@@ -123,17 +132,17 @@ public abstract class PlaceholderLoaderBase {
return itemStack;
}
protected @NotNull ItemStack getEmptyArmor(@NotNull ItemLike item) {
protected ItemStack getEmptyArmor(ItemLike item) {
// Inventory-background-grey-ish leather armor with no tooltip
ItemStack itemStack = new ItemStack(item);
DyedItemColor color = getDye(0xC8C8C8);
DyedItemColor color = new DyedItemColor(0xC8C8C8);
itemStack.set(DataComponents.DYED_COLOR, color);
hideTooltip(itemStack);
addModelData(itemStack);
return itemStack;
}
protected @NotNull ItemStack defaultShield() {
protected ItemStack defaultShield() {
// Shield with "missing texture" pattern, magenta and black squares.
ItemStack itemStack = new ItemStack(Items.SHIELD);
itemStack.set(DataComponents.BASE_COLOR, DyeColor.MAGENTA);
@@ -158,15 +167,23 @@ public abstract class PlaceholderLoaderBase {
return itemStack;
}
protected @NotNull ItemStack defaultNotSlot() {
protected ItemStack defaultNotSlot() {
// White pane with no tooltip
ItemStack itemStack = new ItemStack(Items.WHITE_STAINED_GLASS_PANE);
ItemStack itemStack = new ItemStack(getDefaultNotSlotItem());
hideTooltip(itemStack);
addModelData(itemStack);
return itemStack;
}
protected @NotNull ItemStack defaultBlockedOffline() {
protected Item getDefaultCursorItem() {
return Items.BANNER.white();
}
protected Item getDefaultNotSlotItem() {
return Items.STAINED_GLASS_PANE.white();
}
protected ItemStack defaultBlockedOffline() {
// Barrier: "Not available - Offline"
ItemStack itemStack = new ItemStack(Items.BARRIER);
itemStack.set(DataComponents.ITEM_NAME,
@@ -0,0 +1,42 @@
package com.lishid.openinv.internal.paper26_3.container.slot.placeholder;
import com.lishid.openinv.internal.paper26_3.player.OpenPlayer;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.GameType;
import org.jspecify.annotations.NullMarked;
import java.util.EnumMap;
@NullMarked
public final class Placeholders {
// N.B. Slot#getNoItemIcon is still clientside, unfortunately.
// Worth checking every few updates just in case - it would be nice to
// get rid of some of the placeholders.
static final EnumMap<GameType, ItemStack> BLOCKED_GAME_TYPE = new EnumMap<>(GameType.class);
public static ItemStack craftingOutput = ItemStack.EMPTY;
public static ItemStack cursor = ItemStack.EMPTY;
public static ItemStack drop = ItemStack.EMPTY;
public static ItemStack emptyHelmet = ItemStack.EMPTY;
public static ItemStack emptyChestplate = ItemStack.EMPTY;
public static ItemStack emptyLeggings = ItemStack.EMPTY;
public static ItemStack emptyBoots = ItemStack.EMPTY;
public static ItemStack emptyOffHand = ItemStack.EMPTY;
public static ItemStack notSlot = ItemStack.EMPTY;
public static ItemStack blockedOffline = ItemStack.EMPTY;
public static ItemStack survivalOnly(ServerPlayer serverPlayer) {
if (!OpenPlayer.isConnected(serverPlayer.connection)) {
return blockedOffline;
}
return BLOCKED_GAME_TYPE.getOrDefault(serverPlayer.gameMode.getGameModeForPlayer(), ItemStack.EMPTY);
}
private Placeholders() {
throw new IllegalStateException("Cannot create instance of utility class.");
}
}
@@ -1,4 +1,4 @@
package com.lishid.openinv.internal.paper26_1.player;
package com.lishid.openinv.internal.paper26_3.player;
import com.lishid.openinv.event.OpenEvents;
import com.mojang.logging.LogUtils;
@@ -15,17 +15,17 @@ import net.minecraft.world.level.storage.ValueOutput;
import org.bukkit.craftbukkit.CraftServer;
import org.bukkit.craftbukkit.entity.CraftPlayer;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.Unmodifiable;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
import org.slf4j.Logger;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Optional;
import java.util.Set;
@NullMarked
public class OpenPlayer extends CraftPlayer {
/**
@@ -40,6 +40,7 @@ public class OpenPlayer extends CraftPlayer {
@Unmodifiable
protected static final Set<String> RESET_TAGS = Set.of(
// Entity#saveWithoutId(CompoundTag)
"invulnerable_time",
"CustomName",
"CustomNameVisible",
"Silent",
@@ -52,8 +53,8 @@ public class OpenPlayer extends CraftPlayer {
"Passengers",
// ServerPlayer#addAdditionalSaveData(CompoundTag)
// Intentional omissions to prevent mount loss: Attach, Entity, and RootVehicle
"warden_spawn_tracker", // No longer needed as of 1.21.11
"entered_nether_pos",
"last_explosion_impact_pos",
"respawn",
"raid_omen_position",
"ender_pearls",
@@ -98,9 +99,8 @@ public class OpenPlayer extends CraftPlayer {
try (ProblemReporter.ScopedCollector scopedCollector = new ProblemReporter.ScopedCollector(player.problemPath(), logger)) {
PlayerDataStorage worldNbtStorage = server.getServer().getPlayerList().playerIo;
CompoundTag oldData = isOnline()
? null
: load(worldNbtStorage, player, scopedCollector).orElse(null);
CompoundTag oldData;
oldData = isOnline() ? null : worldNbtStorage.load(player.nameAndId()).orElse(null);
CompoundTag playerData = getWritableTag(oldData);
ValueOutput valueOutput = TagValueOutput.createWrappingWithContext(scopedCollector, player.registryAccess(), playerData);
@@ -112,19 +112,11 @@ public class OpenPlayer extends CraftPlayer {
}
}
protected Optional<CompoundTag> load(
PlayerDataStorage storage,
ServerPlayer player,
ProblemReporter.ScopedCollector collector
) {
return storage.load(player.nameAndId());
}
protected void saveSafe(
@NotNull ServerPlayer player,
ServerPlayer player,
@Nullable CompoundTag oldData,
@NotNull CompoundTag playerData,
@NotNull PlayerDataStorage worldNbtStorage
CompoundTag playerData,
PlayerDataStorage worldNbtStorage
) throws IOException {
// Revert certain special data values when offline.
revertSpecialValues(playerData, oldData);
@@ -138,15 +130,15 @@ public class OpenPlayer extends CraftPlayer {
}
protected void safeReplaceFile(
@NotNull Path dataFile,
@NotNull Path tempFile,
@NotNull Path backupFile
Path dataFile,
Path tempFile,
Path backupFile
) {
net.minecraft.util.Util.safeReplaceFile(dataFile, tempFile, backupFile);
}
@Contract("null -> new")
protected @NotNull CompoundTag getWritableTag(@Nullable CompoundTag oldData) {
protected CompoundTag getWritableTag(@Nullable CompoundTag oldData) {
if (oldData == null) {
return new CompoundTag();
}
@@ -164,7 +156,7 @@ public class OpenPlayer extends CraftPlayer {
return oldData;
}
protected void revertSpecialValues(@NotNull CompoundTag newData, @Nullable CompoundTag oldData) {
protected void revertSpecialValues(CompoundTag newData, @Nullable CompoundTag oldData) {
if (oldData == null) {
return;
}
@@ -176,11 +168,11 @@ public class OpenPlayer extends CraftPlayer {
}
private <T extends Tag> void copyValue(
@NotNull CompoundTag source,
@NotNull CompoundTag target,
@NotNull String container,
@NotNull String key,
@SuppressWarnings("SameParameterValue") @NotNull Class<T> tagType
CompoundTag source,
CompoundTag target,
String container,
String key,
@SuppressWarnings("SameParameterValue") Class<T> tagType
) {
CompoundTag oldContainer = getTag(source, container, CompoundTag.class);
CompoundTag newContainer = getTag(target, container, CompoundTag.class);
@@ -196,8 +188,8 @@ public class OpenPlayer extends CraftPlayer {
private <T extends Tag> @Nullable T getTag(
@Nullable CompoundTag container,
@NotNull String key,
@NotNull Class<T> dataType
String key,
Class<T> dataType
) {
if (container == null) {
return null;
@@ -210,8 +202,8 @@ public class OpenPlayer extends CraftPlayer {
}
private <T extends Tag> void setTag(
@NotNull CompoundTag container,
@NotNull String key,
CompoundTag container,
String key,
@Nullable T data
) {
if (data == null) {
@@ -221,7 +213,7 @@ public class OpenPlayer extends CraftPlayer {
}
}
protected void remove(@NotNull CompoundTag tag, @NotNull String key) {
protected void remove(CompoundTag tag, String key) {
tag.remove(key);
}
@@ -0,0 +1,340 @@
package com.lishid.openinv.internal.paper26_3.player;
import com.lishid.openinv.internal.ISpecialInventory;
import com.github.jikoo.openinv.internal.container.slot.InventoryFactory;
import com.lishid.openinv.internal.paper26_3.container.BaseOpenInventory;
import com.lishid.openinv.internal.paper26_3.container.OpenEnderChest;
import com.lishid.openinv.internal.paper26_3.container.menu.OpenChestMenu;
import com.github.jikoo.openinv.util.JulLoggerAdapter;
import com.mojang.authlib.GameProfile;
import com.mojang.logging.LogUtils;
import io.papermc.paper.adventure.PaperAdventure;
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.ParticleStatus;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.util.ProblemReporter;
import net.minecraft.world.Container;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.entity.player.ChatVisiblity;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.storage.LevelData;
import net.minecraft.world.level.storage.TagValueInput;
import net.minecraft.world.level.storage.TagValueOutput;
import net.minecraft.world.level.storage.ValueInput;
import net.minecraft.world.level.storage.ValueOutput;
import net.minecraft.world.phys.Vec3;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.Server;
import org.bukkit.World;
import org.bukkit.craftbukkit.CraftServer;
import org.bukkit.craftbukkit.CraftWorld;
import org.bukkit.craftbukkit.entity.CraftPlayer;
import org.bukkit.craftbukkit.event.CraftEventFactory;
import org.bukkit.entity.Player;
import org.bukkit.inventory.InventoryView;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
import java.lang.reflect.Field;
import java.util.Optional;
import java.util.UUID;
import java.util.logging.Logger;
@NullMarked
public class PlayerManager implements com.github.jikoo.openinv.internal.PlayerManager {
protected final Logger logger;
protected final InventoryFactory<ServerPlayer, ItemStack, Container, Slot, EquipmentSlot> factory;
protected @Nullable Field bukkitEntity;
public PlayerManager(
Logger logger,
InventoryFactory<ServerPlayer, ItemStack, Container, Slot, EquipmentSlot> factory
) {
this.logger = logger;
this.factory = factory;
try {
bukkitEntity = Entity.class.getDeclaredField("bukkitEntity");
} catch (NoSuchFieldException e) {
logger.warning("Unable to obtain field to inject custom save process - certain player data may be lost when saving!");
logger.log(java.util.logging.Level.WARNING, e.getMessage(), e);
bukkitEntity = null;
}
}
public static ServerPlayer getHandle(final Player player) {
if (player instanceof CraftPlayer craftPlayer) {
return craftPlayer.getHandle();
}
Server server = player.getServer();
ServerPlayer nmsPlayer = null;
if (server instanceof CraftServer craftServer) {
nmsPlayer = craftServer.getHandle().getPlayer(player.getUniqueId());
}
if (nmsPlayer == null) {
// Could use reflection to examine fields, but it's honestly not worth the bother.
throw new RuntimeException("Unable to fetch EntityPlayer from Player implementation " + player.getClass().getName());
}
return nmsPlayer;
}
@Override
public @Nullable Player loadPlayer(final OfflinePlayer offline) {
if (!(Bukkit.getServer() instanceof CraftServer craftServer)) {
return null;
}
MinecraftServer server = craftServer.getServer();
ServerLevel worldServer = server.getLevel(Level.OVERWORLD);
if (worldServer == null) {
return null;
}
// Create a new ServerPlayer.
ServerPlayer entity = createNewPlayer(server, worldServer, offline);
// Stop listening for advancement progression - if this is not cleaned up, loading causes a memory leak.
removeAdvancementListeners(entity);
// Try to load the player's data.
if (loadData(server, entity)) {
// If data is loaded successfully, return the Bukkit entity.
return entity.getBukkitEntity();
}
return null;
}
protected ServerPlayer createNewPlayer(
MinecraftServer server,
ServerLevel worldServer,
final OfflinePlayer offline
) {
// See net.minecraft.server.players.PlayerList#canPlayerLogin(ServerLoginPacketListenerImpl, GameProfile)
// See net.minecraft.server.network.ServerLoginPacketListenerImpl#handleHello(ServerboundHelloPacket)
GameProfile profile = new GameProfile(offline.getUniqueId(),
offline.getName() != null ? offline.getName() : offline.getUniqueId().toString()
);
ClientInformation dummyInfo = new ClientInformation(
"en_us",
1, // Reduce distance just in case.
ChatVisiblity.HIDDEN, // Don't accept chat.
false,
ServerPlayer.DEFAULT_MODEL_CUSTOMIZATION,
ServerPlayer.DEFAULT_MAIN_HAND,
true,
false, // Don't list in player list (not that this player is in the list anyway).
ParticleStatus.MINIMAL
);
ServerPlayer entity = new ServerPlayer(server, worldServer, profile, dummyInfo);
try {
injectPlayer(server, entity);
} catch (IllegalAccessException e) {
logger.log(
java.util.logging.Level.WARNING,
e,
() -> "Unable to inject ServerPlayer, certain player data may be lost when saving!"
);
}
return entity;
}
protected void removeAdvancementListeners(ServerPlayer entity) {
entity.getAdvancements().clearTriggers();
}
protected boolean loadData(MinecraftServer server, ServerPlayer player) {
// See CraftPlayer#loadData
try (ProblemReporter.ScopedCollector scopedCollector = new ProblemReporter.ScopedCollector(player.problemPath(), new JulLoggerAdapter(logger))) {
CompoundTag loadedData = server.getPlayerList().playerIo.load(player.nameAndId()).orElse(null);
if (loadedData == null) {
// Exceptions with loading are logged.
return false;
}
ValueInput valueInput = TagValueInput.create(scopedCollector, player.registryAccess(), loadedData);
// Read basic data into the player.
player.load(valueInput);
// World is not loaded by ServerPlayer#load(CompoundTag) on Paper.
parseWorld(server, player, valueInput);
}
return true;
}
protected void parseWorld(
MinecraftServer server,
ServerPlayer player,
ValueInput loadedData
) {
// See PlayerList#placeNewPlayer
World bukkitWorld;
Optional<Long> msbs = loadedData.getLong("WorldUUIDMost");
Optional<Long> lsbs = loadedData.getLong("WorldUUIDLeast");
if (msbs.isPresent() && lsbs.isPresent()) {
// Modern Bukkit world.
bukkitWorld = Bukkit.getServer().getWorld(new UUID(msbs.get(), lsbs.get()));
} else {
bukkitWorld = loadedData.getString("world").map(Bukkit::getWorld).orElse(null);
}
if (bukkitWorld == null) {
spawnInDefaultWorld(server, player);
return;
}
player.setServerLevel(((CraftWorld) bukkitWorld).getHandle());
}
protected void spawnInDefaultWorld(MinecraftServer server, ServerPlayer player) {
ServerLevel level = server.getLevel(Level.OVERWORLD);
if (level != null) {
// Adjust player to default spawn (in keeping with Paper handling) when world not found.
LevelData.RespawnData respawnData = getRespawnData(level);
player.snapTo(getAdjustedSpawnLocation(player, level, respawnData), respawnData.yaw(), 0.0F);
setServerLevel(player, level);
} else {
logger.warning("Tried to load player with invalid world when no fallback was available!");
}
}
protected LevelData.RespawnData getRespawnData(ServerLevel level) {
return level.getRespawnData();
}
protected Vec3 getAdjustedSpawnLocation(
ServerPlayer player,
ServerLevel level,
LevelData.RespawnData respawnData
) {
return Vec3.atBottomCenterOf(player.adjustSpawnLocation(level, respawnData.pos()));
}
protected void setServerLevel(ServerPlayer player, ServerLevel level) {
player.setServerLevel(level);
}
protected void injectPlayer(MinecraftServer server, ServerPlayer player) throws IllegalAccessException {
if (bukkitEntity == null) {
return;
}
bukkitEntity.setAccessible(true);
bukkitEntity.set(player, new OpenPlayer(server.server, player, this));
}
@Override
public Player inject(Player player) {
try {
ServerPlayer nmsPlayer = getHandle(player);
if (nmsPlayer.getBukkitEntity() instanceof OpenPlayer openPlayer) {
return openPlayer;
}
org.slf4j.Logger logger = LogUtils.getLogger();
try (ProblemReporter.ScopedCollector scopedCollector = new ProblemReporter.ScopedCollector(nmsPlayer.problemPath(), logger)) {
CompoundTag extraData = new CompoundTag();
// Copy extra data out of existing player.
ValueOutput output = TagValueOutput.createWrappingWithContext(scopedCollector, nmsPlayer.registryAccess(), extraData);
nmsPlayer.getBukkitEntity().setExtraData(output);
MinecraftServer server = nmsPlayer.level().getServer();
injectPlayer(server, nmsPlayer);
CraftPlayer newPlayer = nmsPlayer.getBukkitEntity();
// Set extra data in new player.
ValueInput input = TagValueInput.create(scopedCollector, nmsPlayer.registryAccess(), extraData);
newPlayer.readExtraData(input);
return newPlayer;
}
} catch (IllegalAccessException e) {
logger.log(
java.util.logging.Level.WARNING,
e,
() -> "Unable to inject ServerPlayer, certain player data may be lost when saving!"
);
return player;
}
}
@Override
public @Nullable InventoryView openInventory(
Player bukkitPlayer,
ISpecialInventory inventory,
boolean viewOnly
) {
ServerPlayer player = getHandle(bukkitPlayer);
if (!OpenPlayer.isConnected(player.connection)) {
return null;
}
// See net.minecraft.server.level.ServerPlayer#openMenu(MenuProvider)
OpenChestMenu<?> menu;
Component title;
if (inventory instanceof BaseOpenInventory playerInv) {
menu = playerInv.createMenu(player, player.nextContainerCounter(), viewOnly);
title = playerInv.getTitle(player, menu);
} else if (inventory instanceof OpenEnderChest enderChest) {
menu = enderChest.createMenu(factory, player, player.nextContainerCounter(), viewOnly);
title = enderChest.getTitle(menu);
} else {
return null;
}
// Should never happen, player is a ServerPlayer with an active connection.
if (menu == null) {
return null;
}
// Set up title. Title can only be set once for a menu, and is set during the open process.
// Further title changes are a hack where the client is sent a "new" inventory with the same ID,
// resulting in a title change but no other state modifications (like cursor position).
menu.setTitle(title);
var pair = CraftEventFactory.callInventoryOpenEventWithTitle(player, menu);
AbstractContainerMenu opened = pair.getSecond();
// Menu is null if event is cancelled.
if (opened == null) {
return null;
}
var newTitle = pair.getFirst();
if (newTitle != null) {
title = PaperAdventure.asVanilla(newTitle);
}
player.containerMenu = opened;
player.connection.send(new ClientboundOpenScreenPacket(opened.containerId, opened.getType(), title));
player.initMenu(opened);
return opened.getBukkitView();
}
}
@@ -1,80 +0,0 @@
package com.github.jikoo.openinv.internal.spigot26_1;
import com.github.jikoo.openinv.internal.spigot26_1.container.AnySilentContainer;
import com.github.jikoo.openinv.internal.spigot26_1.container.OpenInventory;
import com.github.jikoo.openinv.internal.spigot26_1.container.OpenEnderChest;
import com.github.jikoo.openinv.internal.spigot26_1.container.slot.placeholder.PlaceholderLoader;
import com.github.jikoo.openinv.internal.spigot26_1.player.PlayerManager;
import com.lishid.openinv.internal.Accessor;
import com.lishid.openinv.internal.IAnySilentContainer;
import com.lishid.openinv.internal.ISpecialEnderChest;
import com.lishid.openinv.internal.ISpecialInventory;
import com.lishid.openinv.internal.ISpecialPlayerInventory;
import com.lishid.openinv.util.lang.LanguageManager;
import net.minecraft.world.Container;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.craftbukkit.inventory.CraftInventory;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.logging.Level;
import java.util.logging.Logger;
public class InternalAccessor implements Accessor {
protected final @NotNull Logger logger;
private final @NotNull PlayerManager manager;
private final @NotNull AnySilentContainer anySilentContainer;
public InternalAccessor(@NotNull Logger logger, @NotNull LanguageManager lang) {
this.logger = logger;
manager = new PlayerManager(logger);
anySilentContainer = new AnySilentContainer(logger, lang);
}
@Override
public @NotNull PlayerManager getPlayerManager() {
return manager;
}
@Override
public @NotNull IAnySilentContainer getAnySilentContainer() {
return anySilentContainer;
}
@Override
public @NotNull ISpecialPlayerInventory createPlayerInventory(@NotNull Player player) {
return new OpenInventory(player);
}
@Override
public @NotNull ISpecialEnderChest createEnderChest(@NotNull Player player) {
return new OpenEnderChest(player);
}
@Override
public <T extends ISpecialInventory> @Nullable T get(@NotNull Inventory bukkitInventory, @NotNull Class<T> clazz) {
if (!(bukkitInventory instanceof CraftInventory craftInventory)) {
return null;
}
Container container = craftInventory.getInventory();
if (clazz.isInstance(container)) {
return clazz.cast(container);
}
return null;
}
@Override
public void reload(@NotNull ConfigurationSection config) {
ConfigurationSection placeholders = config.getConfigurationSection("placeholders");
try {
// Reset placeholders to defaults and try to load configuration.
new PlaceholderLoader().load(placeholders);
} catch (Exception e) {
logger.log(Level.WARNING, "Caught exception loading placeholder overrides!", e);
}
}
}
Loaded 100 of 174 files, more files were not shown because too many files have changed in this diff. Show more