Compare commits

...
138 Commits
Author SHA1 Message Date
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
Jikoo 872e14e1a6 Bump version to 5.3.1 for release 2026-04-21 21:33:41 -04:00
Adam f5e534f758 Port Bukkit data fix to Spigot (#391) 2026-04-22 01:23:07 +00:00
Adam 16a4c8f71b Update to Minecraft 26.1 (#385)
Also fixes issues with saving default Bukkit data

Closes #384
Closes #389
2026-04-21 21:05:00 -04:00
dependabot[bot] 7f4216995c Bump softprops/action-gh-release from 2.5.0 to 2.6.1 (#386)
Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2.5.0 to 2.6.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/v2.5.0...v2.6.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-02 09:32:35 -04:00
dependabot[bot] f945868a21 Bump com.gradleup.shadow from 9.3.2 to 9.4.1 (#388) 2026-04-01 22:03:46 +00:00
Adam ee923c0881 Upgrade Gradle wrapper to 9.4.1 (#383) 2026-03-22 16:02:16 +00:00
Adam 80d48ed218 Improve InventoryHolder handling (#382)
* Improve InventoryHolder handling
  * Don't snapshot holder on Paper
  * Ensure viewer has silent enabled first to reduce impact on Spigot
* Remove BlockState reflection
2026-03-20 23:45:18 +00:00
dependabot[bot] 8de1f778f0 Bump net.md-5:SpecialSource from 1.11.5 to 1.11.6 (#379) 2026-03-02 17:24:53 +00:00
dependabot[bot] 8955882737 Bump com.google.errorprone:error_prone_core from 2.46.0 to 2.48.0 (#378) 2026-03-02 17:24:13 +00:00
dependabot[bot] 037b3f89cd Bump com.gradleup.shadow from 9.3.1 to 9.3.2 (#377) 2026-03-02 17:23:37 +00:00
dependabot[bot] 43b4360bb6 Bump org.jetbrains:annotations from 26.0.2-1 to 26.1.0 (#376) 2026-03-02 17:23:03 +00:00
dependabot[bot] e03c43555d Bump actions/upload-artifact from 6 to 7 (#375)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 6 to 7.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  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-03-02 12:20:05 -05:00
dependabot[bot] 43a13e6254 Bump actions/download-artifact from 7 to 8 (#374)
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 7 to 8.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v7...v8)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-version: '8'
  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-03-02 12:19:50 -05:00
Jikoo ab3dddbc19 Update Spigot revision
This appears to compile, but will need testing before a release. Spigot removed the renames they used to apply to all internal classes.
2026-03-02 10:47:10 -05:00
Jikoo d35fc6713b Add links to all official download sources
Some are still pending approval that probably won't happen until Monday, but oh well.
2026-01-18 01:05:43 -05:00
Jikoo 9c8a16adc9 Fix external releases
Add loaders to mc-publish
Fix version parsing for Hangar. The trim command was added in jq 1.8.
2026-01-18 00:37:10 -05:00
Jikoo b93900cf3b Bump version to 5.3.1-SNAPSHOT for development 2026-01-17 23:57:47 -05:00
Jikoo 63fccdcf91 Bump version to 5.3.0 for release 2026-01-17 23:57:38 -05:00
dependabot[bot]andJikoo 960acbfe9d Bump errorprone versions (#370)
* Bump com.google.errorprone:error_prone_core from 2.45.0 to 2.46.0

Bumps [com.google.errorprone:error_prone_core](https://github.com/google/error-prone) from 2.45.0 to 2.46.0.
- [Release notes](https://github.com/google/error-prone/releases)
- [Commits](https://github.com/google/error-prone/compare/v2.45.0...v2.46.0)

---
updated-dependencies:
- dependency-name: com.google.errorprone:error_prone_core
  dependency-version: 2.46.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

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

* Bump errorprone-gradle-plugin to 4.4.0

Resolves issues with missing flag required for errorprone-core

---------

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-01-18 02:37:20 +00:00
dependabot[bot] 3c904499c5 Bump com.gradleup.shadow from 9.2.2 to 9.3.1 (#369) 2026-01-17 16:54:05 +00:00
Adam baa6b3d734 Add profile cache (#368)
Instead of reading every single player file from disk when doing a fuzzy match, we now do a single lookup from a SQLite database. The database is populated once with all existing players' names on startup, then maintained when players join. This is a massive performance boost for servers that have high player counts or run on spinning disks.

Lookups now require that the first character of the target player's name be correct (case-insensitive). For Geyser-like names, this extends to the first character of their real name. For example, "XxXJohnMinecraftXxX" can no longer be located by a "johnminecraft" search. A search for "xjohnminecraft" could be used instead. A Geyser player ".FranMinecraft" would require ".f" to be eligible, i.e. "franmc" cannot produce the correct result while ".franmc" might.
2026-01-17 11:50:23 -05:00
Adam 7f6c339e06 Add option to check permissions with toggle (#361)
* Promote Permissions to API
* Document permissions
* Add method for checking a toggle with permissions
2026-01-03 12:20:58 -05:00
dependabot[bot] 00736c8d30 Bump actions/download-artifact from 6 to 7 (#364)
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 6 to 7.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  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-01-01 12:12:35 -05:00
dependabot[bot] cb1ec77c24 Bump actions/upload-artifact from 5 to 6 (#363)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 5 to 6.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  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-01-01 12:12:25 -05:00
dependabot[bot] c8c8a72819 Bump actions/cache from 4 to 5 (#362)
Bumps [actions/cache](https://github.com/actions/cache) from 4 to 5.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: '5'
  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-01-01 12:12:14 -05:00
Alessandro 16f91ddc9f feat: add use sub-permissions 2026-01-01 08:55:43 -05:00
Adam c83511e475 Upload to Modrinth and Hangar (#360)
* Replace curseforge-upload with mc-publish
* Use anchors for common configurations
* Convert script to a shared run step
2025-12-31 14:39:53 -05:00
Jikoo c095a78013 Bump version to 5.2.1-SNAPSHOT for development 2025-12-17 15:29:26 -05:00
Jikoo 4441cdcf04 Bump version to 5.2.0 for release 2025-12-17 15:29:23 -05:00
Adam 9940b9d840 Update to 1.21.11 (#358) 2025-12-17 15:11:56 -05:00
dependabot[bot] fb409f8024 Bump actions/checkout from 5 to 6 (#354)
Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6.
- [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/v5...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  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>
2025-12-14 17:48:28 -05:00
dependabot[bot] 08b43a8aca Bump softprops/action-gh-release from 2.4.1 to 2.5.0 (#355)
Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2.4.1 to 2.5.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.4.1...v2.5.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-14 17:08:30 -05:00
dependabot[bot] 3df3d41a7f Bump com.google.errorprone:error_prone_core from 2.43.0 to 2.45.0 (#356) 2025-12-01 20:21:44 +00:00
dependabot[bot] 16cba8a137 Bump actions/upload-artifact from 4 to 5 (#348)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 5.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '5'
  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>
2025-11-03 09:59:45 -05:00
dependabot[bot] f1b86b4eb1 Bump softprops/action-gh-release from 2.3.3 to 2.4.1 (#349)
Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2.3.3 to 2.4.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/v2.3.3...v2.4.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-03 09:59:24 -05:00
dependabot[bot] 172e1cd55b Bump actions/download-artifact from 5 to 6 (#350)
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 5 to 6.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  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>
2025-11-03 09:58:40 -05:00
Adam d8b5aa145b Improve equal access behaviors (#346)
* Extract common chest code, expose view-only state
* Add prefix for view-only state
* Add nodes for behavior of accessing equal
* Include permission-based denial feedback
2025-11-03 09:51:00 -05:00
dependabot[bot] 2ffff99acf Bump com.google.errorprone:error_prone_core from 2.42.0 to 2.43.0 (#351) 2025-11-01 17:24:15 +00:00
Jikoo 743c46f322 Bump version to 5.1.16-SNAPSHOT for development 2025-10-11 09:48:58 -04:00
Jikoo e3285a6d61 Bump version to 5.1.15 for release 2025-10-11 09:48:35 -04:00
Jikoo 806fb848a7 Add line break between server implementations
I noticed this was a problem the first update and have been forgetting and then manually fixing it on Bukkit since. A travesty.
2025-10-11 09:46:34 -04:00
Adam b269a5ed3c Fix /openinv on Spigot (#342) 2025-10-11 13:35:59 +00:00
Jikoo 8c102c4400 Bump version to 5.1.15-SNAPSHOT for development 2025-10-08 15:01:55 -04:00
Jikoo 080d02f93f Bump version to 5.1.14 for release 2025-10-08 15:01:31 -04:00
Adam 54f9d30a89 Update to 1.21.9/1.21.10 (#339) 2025-10-08 15:00:13 -04:00
dependabot[bot] 6063959362 Bump actions/github-script from 7 to 8 (#336)
Bumps [actions/github-script](https://github.com/actions/github-script) from 7 to 8.
- [Release notes](https://github.com/actions/github-script/releases)
- [Commits](https://github.com/actions/github-script/compare/v7...v8)

---
updated-dependencies:
- dependency-name: actions/github-script
  dependency-version: '8'
  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>
2025-10-03 09:03:50 -04:00
dependabot[bot] d80b8a6f4d Bump gradle/actions from 4 to 5 (#334)
Bumps [gradle/actions](https://github.com/gradle/actions) from 4 to 5.
- [Release notes](https://github.com/gradle/actions/releases)
- [Commits](https://github.com/gradle/actions/compare/v4...v5)

---
updated-dependencies:
- dependency-name: gradle/actions
  dependency-version: '5'
  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>
2025-10-03 09:03:22 -04:00
dependabot[bot] 13621901ad Bump softprops/action-gh-release from 2.3.2 to 2.3.3 (#335)
Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2.3.2 to 2.3.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/v2.3.2...v2.3.3)

---
updated-dependencies:
- dependency-name: softprops/action-gh-release
  dependency-version: 2.3.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>
2025-10-03 08:49:37 -04:00
dependabot[bot] 20cc6e004e Bump com.gradleup.shadow from 9.1.0 to 9.2.2 (#337) 2025-10-01 17:25:00 +00:00
dependabot[bot] 80585dfc07 Bump com.google.errorprone:error_prone_core from 2.41.0 to 2.42.0 (#338) 2025-10-01 17:24:53 +00:00
dependabot[bot] 9e37b42c63 Bump actions/download-artifact from 4 to 5 (#328)
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 4 to 5.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-version: '5'
  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>
2025-09-03 10:08:58 -04:00
dependabot[bot] 58be5cffc9 Bump actions/setup-java from 4 to 5 (#330)
Bumps [actions/setup-java](https://github.com/actions/setup-java) from 4 to 5.
- [Release notes](https://github.com/actions/setup-java/releases)
- [Commits](https://github.com/actions/setup-java/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/setup-java
  dependency-version: '5'
  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>
2025-09-03 13:14:45 +00:00
dependabot[bot] e8c8e625c0 Bump actions/checkout from 4 to 5 (#327)
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5.
- [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/v4...v5)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '5'
  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>
2025-09-03 09:10:37 -04:00
dependabot[bot] 0ee3f8c1b2 Bump com.gradleup.shadow from 8.3.8 to 9.1.0 (#331) 2025-09-02 12:15:47 +00:00
dependabot[bot] 99f9099981 Bump org.jetbrains:annotations from 26.0.2 to 26.0.2-1 (#329) 2025-09-02 11:19:21 +00:00
Jikoo 70ae432158 Fix missing override annotations 2025-08-07 09:06:35 -04:00
dependabot[bot] ad05f427fd Bump com.google.errorprone:error_prone_core from 2.39.0 to 2.41.0 (#325) 2025-08-01 17:36:44 +00:00
Adam 252e93f872 Update for new Paper changes (#324)
* Fix potential desync if a plugin triggers a slot update under certain circumstances
* Increase visibility to match changes to superclass
2025-07-28 12:55:08 +00:00
Jikoo 0267090cb6 Bump version to 5.1.14-SNAPSHOT for development 2025-07-18 11:43:17 -04:00
Jikoo 1e26c33311 Bump version to 5.1.13 for release 2025-07-18 11:43:15 -04:00
Adam 34f5319f48 Add 1.21.8 support (#321)
* Add 1.21.8 to supported versions
* Correct resource pack version range
2025-07-18 15:33:52 +00:00
Jikoo 642e287507 Bump version to 5.1.13-SNAPSHOT for development 2025-07-01 21:00:21 -04:00
Jikoo 4504ba2b99 Bump version to 5.1.12 for release 2025-07-01 20:59:38 -04:00
dependabot[bot] 9691d16506 Bump softprops/action-gh-release from 2.2.2 to 2.3.2 (#316)
Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2.2.2 to 2.3.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/v2.2.2...v2.3.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-01 20:58:24 -04:00
Adam 1897790db5 Update to 1.21.6/1.21.7 (#315) 2025-07-01 20:57:24 -04:00
dependabot[bot] cad76c6559 Bump com.google.errorprone:error_prone_core from 2.38.0 to 2.39.0 (#318) 2025-07-01 17:50:13 +00:00
dependabot[bot] 119ff48461 Bump com.gradleup.shadow from 8.3.6 to 8.3.8 (#317) 2025-07-01 17:49:19 +00:00
Jikoo b589f79e57 Update gradle wrapper to 8.14 2025-05-21 10:07:30 -04:00
Jikoo f742cf5b77 Update paperweight-userdev for new bundle version 2025-05-21 10:07:30 -04:00
Jikoo 19d030dd4d Improve clear parameter handling
Don't tab complete parameter for those who can't target others
Don't load players for those who can't target others
2025-05-21 10:07:30 -04:00
Jikoo 17dec41fea Bump version to 5.1.12-SNAPSHOT for development 2025-05-20 09:23:04 -04:00
Jikoo b5599450f5 Bump version to 5.1.11 for release 2025-05-20 09:23:01 -04:00
Adam b2c13b40e2 Improve feedback, code health (#312)
* Drop legacy translations
* Drop legacy viewer handling
* Improve lookup feedback
* Deprecate ISpecialInventory#setPlayerOffline
* Fix default value being provided
2025-05-20 13:22:35 +00:00
Adam 03c0fea7ab Search shulker boxes and bundles (#311) 2025-05-19 11:31:49 -04:00
Jikoo 9ca5b2d826 Apply errorprone suggestions 2025-05-19 06:20:03 -04:00
Jikoo 7a9bb11133 Re-add errorprone
Dropped during swap to Gradle
2025-05-19 05:40:43 -04:00
Jikoo 8647f8e156 Autoformat 2025-05-18 11:09:30 -04:00
Jikoo 2a07673b1a Add .gitattributes and .editorconfig 2025-05-18 11:09:16 -04:00
Adamandtrard 8418cb14fe Add /clearinv and /clearender (#310)
Co-authored-by: trard <trardone@gmail.com>
2025-05-18 08:57:31 -04:00
Jikoo 6647334072 Fix missed override
Closes #307
2025-05-02 00:52:42 -04:00
dependabot[bot] e479a5eaa7 Bump softprops/action-gh-release from 2.2.1 to 2.2.2 (#306)
Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2.2.1 to 2.2.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/v2.2.1...v2.2.2)

---
updated-dependencies:
- dependency-name: softprops/action-gh-release
  dependency-version: 2.2.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>
2025-05-01 13:35:11 -04:00
Jikoo 1c40091cfa Bump version to 5.1.11-SNAPSHOT for development 2025-04-25 11:50:11 -04:00
Jikoo 8c2ec2121a Bump version to 5.1.10 for release 2025-04-25 11:49:43 -04:00
Adam ad06b46b37 Update to 1.21.5 (#300) 2025-04-25 15:47:20 +00:00
dependabot[bot] 05ac5c92c8 Bump io.papermc.paperweight.userdev from 2.0.0-beta.14 to 2.0.0-beta.16 (#297) 2025-04-01 17:34:11 +00:00
dependabot[bot] 0c8c364c45 Bump org.spigotmc:spigot-api (#298) 2025-04-01 17:33:47 +00:00
Jikoo b89f34915e Hack around JitPack building Spigot
and publishing it, but we don't talk about that
2025-03-21 17:27:54 -04:00
mooviies ada819b30a Fix JitPack API build (#296) 2025-03-19 16:08:13 +00:00
Adam 7a2cef50ec Update SpecialSource to 1.11.5 (#295) 2025-03-11 10:37:32 -04:00
Jikoo 268e91007c Fix array types
In my defense, reading is really hard
2025-03-09 12:55:39 -04:00
Jikoo f4ed616fb8 Consider any class name format valid
This technically allows edge cases where the package (or class, for classes with no package) can start with L and be incorrectly matched, but that feels like a case of play stupid games, win stupid prizes. Classes should be packaged, and package names should be lower case.
Requires a much less hacky change to JarMapping to optimize.
2025-03-09 10:34:18 -04:00
Jikoo 456909966f Reduce remapping maintenance burden
I don't think SpecialSource is likely to make major changes to JarMapping, but RemapperProcessor hasn't been changed in 4 years.
This is less efficient in terms of lookup speed, but does not require the hacky JarMapping extension and doesn't require a second copy of the field mappings to be kept in memory.
2025-03-08 20:58:49 -05:00
Adam 51649bf2ab Remap reflection (#294)
* Hack around SpecialSource limitations with ProGuard mappings
* Relocate PlanarWrappers
* Use libs for shadow exclusion
2025-03-08 17:53:41 -05:00
Adam e44697f856 Parse CurseForge MC versions from release body (#293) 2025-03-07 20:04:17 +00:00
Adam 414b141b37 Improve Gradle build (#290)
* Fix warnings about task not being cacheable
* Add plugin to install Spigot
* Explicitly cache BuildTools products
* Move reobfuscation to a plugin
* Update compilation instructions
2025-03-02 15:55:00 +00:00
Elmar (Cody) BlumeandR00tB33rMan 0db487c6eb Add Folia support (#292)
* feat(folia): added folia support

* chore(folia): apply folia-supported: true label

---------

Co-authored-by: R00tB33rMan <rootb33rman@gmail.com>
2025-02-18 22:26:34 -05:00
Jikoo 3b907e1b37 Bump version to 5.1.10-SNAPSHOT for development 2025-02-14 11:33:47 -05:00
Jikoo d564773be6 Bump version to 5.1.9 for release 2025-02-14 11:33:31 -05:00
Adam e834e940e2 Convert to precompiled task (#289) 2025-02-14 01:02:24 -05:00
Jikoo cd73430bb2 Try to resolve remote cache issues
The weird part is that I don't have any of these problems locally. Remote is executing tasks in a really weird order, common adapter is building after Spigot adapter despite Spigot adapter depending on an output from it.
May just have to disable caching, but would like to avoid that if possible.
2025-02-13 12:19:03 -05:00
Jikoo 1c1dc9505b Bump version to 5.1.9-SNAPSHOT for development 2025-02-13 11:49:04 -05:00
Jikoo 1a76e6fd57 Bump version to 5.1.8 for release 2025-02-13 11:48:59 -05:00
Jikoo 48a61e6043 Update release script
Now I can do things like have unstaged changes on the master branch when I cut a release! Not good!
2025-02-13 11:48:47 -05:00
Adam 4de2c2fe6d Code health (#288)
* Fix some missing nullity/deprecation annotations
* Remove unnecessary Paper checks
* Respect title changes from InventoryOpenEvent
2025-02-13 16:08:59 +00:00
Adam e4fdd613ae Expand support back to Paper 1.21.1 (#287) 2025-02-12 13:01:53 +00:00
Jikoo 9f50de5dfe Unify Spigot dependency version information declaration
May be worth adding an entry for latest MC version that is shared between common Paper and current Spigot modules.
2025-02-11 10:44:25 -05:00
dependabot[bot] e9d63f31ed Bump com.gradleup.shadow from 8.3.5 to 8.3.6 (#285) 2025-02-10 03:45:09 +00:00
Jikoo 73e003bde4 Update dependabot ecosystem 2025-02-09 22:40:28 -05:00
Jikoo 04ed652073 Bump version to 5.1.8-SNAPSHOT for development 2025-02-09 22:38:34 -05:00
Jikoo 49e408ee7a Bump version to 5.1.7 for release 2025-02-09 22:38:13 -05:00
Jikoo 0df5e39a6d Correct release notes
Paper 1.21.3 requires changes to placeholders and is not currently supported.
2025-02-09 22:37:37 -05:00
Adam 7ab3e0c03f Compile against Paper (#282)
* Convert to Gradle
* Reduce script usage
* Simplify release process
2025-02-09 22:15:37 -05:00
dependabot[bot] 348fde9173 Bump org.jetbrains:annotations from 26.0.1 to 26.0.2 (#283) 2025-02-01 17:35:05 +00:00
Jikoo 6c69350c30 Bump version to 5.1.7-SNAPSHOT for development 2025-01-18 13:58:16 -05:00
Jikoo 19eafddfac Bump version to 5.1.6 for release 2025-01-18 13:57:48 -05:00
Adam 8a388b5620 Fix view-only ender chest (#278)
Fixes access permissions being checked against self
2025-01-18 17:45:44 +00:00
Adam 206fff2c0a Fix off-hand update relying on Spigot method (#277) 2025-01-18 11:56:22 -05:00
Jikoo 6656e431b6 Bump version to 5.1.6-SNAPSHOT for development 2025-01-10 11:02:10 -05:00
Jikoo f8df83b31d Bump version to 5.1.5 for release 2025-01-10 11:01:37 -05:00
Jikoo eb98bd79d3 Update release action 2025-01-10 11:01:18 -05:00
Adam e948fdc011 Improve compatibility of view-only inventories (#273) 2025-01-10 10:35:48 -05:00
Jikoo f5c31c32d3 Correct edit other permission
See discussion #266
2025-01-04 21:37:29 -05:00
dependabot[bot] 56f8cf4d71 Bump softprops/action-gh-release from 2.1.0 to 2.2.0 (#271)
Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2.1.0 to 2.2.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.1.0...v2.2.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-01 13:11:50 -05:00
dependabot[bot] 7f1f6ccc82 Bump com.github.jikoo:planarwrappers from 3.2.3 to 3.3.0 (#270) 2025-01-01 17:39:40 +00:00
Adam 9196ab3e61 Improve equal access (#269)
* Respect deny equals mode if viewer cannot edit
* Revert default mode to allow equals
2024-12-29 13:47:09 +00:00
Adam 0ac7575bad Re-add rpack support for 1.21.3 (#264)
* Re-add support for 1.21.3 to resource pack
* Add pack icon
2024-12-14 13:18:44 -05:00
Jikoo b0c7c8421e Bump version to 5.1.5-SNAPSHOT for development 2024-12-08 20:09:37 -05:00
219 changed files with 9679 additions and 5879 deletions

No files matched your search

+491
View File
@@ -0,0 +1,491 @@
[*]
charset = utf-8
end_of_line = lf
indent_size = 2
indent_style = space
insert_final_newline = false
max_line_length = 120
tab_width = 2
ij_continuation_indent_size = 4
ij_formatter_off_tag = @formatter:off
ij_formatter_on_tag = @formatter:on
ij_formatter_tags_enabled = true
ij_smart_tabs = false
ij_visual_guides =
ij_wrap_on_typing = false
[*.java]
ij_smart_tabs = true
ij_java_align_consecutive_assignments = false
ij_java_align_consecutive_variable_declarations = false
ij_java_align_group_field_declarations = false
ij_java_align_multiline_annotation_parameters = false
ij_java_align_multiline_array_initializer_expression = false
ij_java_align_multiline_assignment = false
ij_java_align_multiline_binary_operation = false
ij_java_align_multiline_chained_methods = false
ij_java_align_multiline_deconstruction_list_components = true
ij_java_align_multiline_extends_list = false
ij_java_align_multiline_for = true
ij_java_align_multiline_method_parentheses = false
ij_java_align_multiline_parameters = false
ij_java_align_multiline_parameters_in_calls = false
ij_java_align_multiline_parenthesized_expression = false
ij_java_align_multiline_records = true
ij_java_align_multiline_resources = true
ij_java_align_multiline_ternary_operation = false
ij_java_align_multiline_text_blocks = false
ij_java_align_multiline_throws_list = false
ij_java_align_subsequent_simple_methods = false
ij_java_align_throws_keyword = false
ij_java_align_types_in_multi_catch = true
ij_java_annotation_parameter_wrap = off
ij_java_array_initializer_new_line_after_left_brace = false
ij_java_array_initializer_right_brace_on_new_line = true
ij_java_array_initializer_wrap = off
ij_java_assert_statement_colon_on_next_line = false
ij_java_assert_statement_wrap = off
ij_java_assignment_wrap = off
ij_java_binary_operation_sign_on_next_line = false
ij_java_binary_operation_wrap = off
ij_java_blank_lines_after_anonymous_class_header = 0
ij_java_blank_lines_after_class_header = 0
ij_java_blank_lines_after_imports = 1
ij_java_blank_lines_after_package = 1
ij_java_blank_lines_around_class = 1
ij_java_blank_lines_around_field = 0
ij_java_blank_lines_around_field_in_interface = 0
ij_java_blank_lines_around_field_with_annotations = 0
ij_java_blank_lines_around_initializer = 1
ij_java_blank_lines_around_method = 1
ij_java_blank_lines_around_method_in_interface = 1
ij_java_blank_lines_before_class_end = 1
ij_java_blank_lines_before_imports = 1
ij_java_blank_lines_before_method_body = 0
ij_java_blank_lines_before_package = 0
ij_java_block_brace_style = end_of_line
ij_java_block_comment_add_space = false
ij_java_block_comment_at_first_column = true
ij_java_builder_methods =
ij_java_call_parameters_new_line_after_left_paren = false
ij_java_call_parameters_right_paren_on_new_line = true
ij_java_call_parameters_wrap = off
ij_java_case_statement_on_separate_line = true
ij_java_catch_on_new_line = false
ij_java_class_annotation_wrap = split_into_lines
ij_java_class_brace_style = end_of_line
ij_java_class_count_to_use_import_on_demand = 1000
ij_java_class_names_in_javadoc = 1
ij_java_deconstruction_list_wrap = normal
ij_java_do_not_indent_top_level_class_members = false
ij_java_do_not_wrap_after_single_annotation = false
ij_java_do_not_wrap_after_single_annotation_in_parameter = false
ij_java_do_while_brace_force = never
ij_java_doc_add_blank_line_after_description = true
ij_java_doc_add_blank_line_after_param_comments = false
ij_java_doc_add_blank_line_after_return = false
ij_java_doc_add_p_tag_on_empty_lines = true
ij_java_doc_align_exception_comments = false
ij_java_doc_align_param_comments = false
ij_java_doc_do_not_wrap_if_one_line = false
ij_java_doc_enable_formatting = true
ij_java_doc_enable_leading_asterisks = true
ij_java_doc_indent_on_continuation = false
ij_java_doc_keep_empty_lines = true
ij_java_doc_keep_empty_parameter_tag = true
ij_java_doc_keep_empty_return_tag = true
ij_java_doc_keep_empty_throws_tag = true
ij_java_doc_keep_invalid_tags = true
ij_java_doc_param_description_on_new_line = false
ij_java_doc_preserve_line_breaks = false
ij_java_doc_use_throws_not_exception_tag = true
ij_java_else_on_new_line = false
ij_java_enum_constants_wrap = off
ij_java_enum_field_annotation_wrap = off
ij_java_extends_keyword_wrap = off
ij_java_extends_list_wrap = off
ij_java_field_annotation_wrap = split_into_lines
ij_java_field_name_prefix =
ij_java_field_name_suffix =
ij_java_finally_on_new_line = false
ij_java_for_brace_force = never
ij_java_for_statement_new_line_after_left_paren = false
ij_java_for_statement_right_paren_on_new_line = true
ij_java_for_statement_wrap = off
ij_java_generate_final_locals = false
ij_java_generate_final_parameters = false
ij_java_generate_use_type_annotation_before_type = true
ij_java_if_brace_force = never
ij_java_imports_layout = @*,*,|,java.**,|,$*
ij_java_indent_case_from_switch = true
ij_java_insert_inner_class_imports = false
ij_java_insert_override_annotation = true
ij_java_keep_blank_lines_before_right_brace = 2
ij_java_keep_blank_lines_between_package_declaration_and_header = 2
ij_java_keep_blank_lines_in_code = 2
ij_java_keep_blank_lines_in_declarations = 2
ij_java_keep_builder_methods_indents = false
ij_java_keep_control_statement_in_one_line = true
ij_java_keep_first_column_comment = true
ij_java_keep_indents_on_empty_lines = false
ij_java_keep_line_breaks = true
ij_java_keep_multiple_expressions_in_one_line = false
ij_java_keep_simple_blocks_in_one_line = false
ij_java_keep_simple_classes_in_one_line = true
ij_java_keep_simple_lambdas_in_one_line = false
ij_java_keep_simple_methods_in_one_line = false
ij_java_label_indent_absolute = false
ij_java_label_indent_size = 0
ij_java_lambda_brace_style = end_of_line
ij_java_layout_on_demand_import_from_same_package_first = true
ij_java_layout_static_imports_separately = true
ij_java_line_comment_add_space = false
ij_java_line_comment_add_space_on_reformat = false
ij_java_line_comment_at_first_column = true
ij_java_local_variable_name_prefix =
ij_java_local_variable_name_suffix =
ij_java_method_annotation_wrap = split_into_lines
ij_java_method_brace_style = end_of_line
ij_java_method_call_chain_wrap = off
ij_java_method_parameters_new_line_after_left_paren = true
ij_java_method_parameters_right_paren_on_new_line = true
ij_java_method_parameters_wrap = on_every_item
ij_java_modifier_list_wrap = false
ij_java_multi_catch_types_wrap = normal
ij_java_names_count_to_use_import_on_demand = 10
ij_java_new_line_after_lparen_in_annotation = false
ij_java_new_line_after_lparen_in_deconstruction_pattern = true
ij_java_new_line_after_lparen_in_record_header = false
ij_java_new_line_when_body_is_presented = false
ij_java_packages_to_use_import_on_demand =
ij_java_parameter_annotation_wrap = off
ij_java_parameter_name_prefix =
ij_java_parameter_name_suffix =
ij_java_parentheses_expression_new_line_after_left_paren = false
ij_java_parentheses_expression_right_paren_on_new_line = true
ij_java_place_assignment_sign_on_next_line = false
ij_java_prefer_longer_names = true
ij_java_prefer_parameters_wrap = false
ij_java_preserve_module_imports = true
ij_java_record_components_wrap = normal
ij_java_repeat_synchronized = true
ij_java_replace_instanceof_and_cast = true
ij_java_replace_null_check = true
ij_java_replace_sum_lambda_with_method_ref = true
ij_java_resource_list_new_line_after_left_paren = false
ij_java_resource_list_right_paren_on_new_line = true
ij_java_resource_list_wrap = off
ij_java_rparen_on_new_line_in_annotation = true
ij_java_rparen_on_new_line_in_deconstruction_pattern = true
ij_java_rparen_on_new_line_in_record_header = true
ij_java_space_after_closing_angle_bracket_in_type_argument = false
ij_java_space_after_colon = true
ij_java_space_after_comma = true
ij_java_space_after_comma_in_type_arguments = true
ij_java_space_after_for_semicolon = true
ij_java_space_after_quest = true
ij_java_space_after_type_cast = true
ij_java_space_before_annotation_array_initializer_left_brace = false
ij_java_space_before_annotation_parameter_list = false
ij_java_space_before_array_initializer_left_brace = false
ij_java_space_before_catch_keyword = true
ij_java_space_before_catch_left_brace = true
ij_java_space_before_catch_parentheses = true
ij_java_space_before_class_left_brace = true
ij_java_space_before_colon = true
ij_java_space_before_colon_in_foreach = true
ij_java_space_before_comma = false
ij_java_space_before_deconstruction_list = false
ij_java_space_before_do_left_brace = true
ij_java_space_before_else_keyword = true
ij_java_space_before_else_left_brace = true
ij_java_space_before_finally_keyword = true
ij_java_space_before_finally_left_brace = true
ij_java_space_before_for_left_brace = true
ij_java_space_before_for_parentheses = true
ij_java_space_before_for_semicolon = false
ij_java_space_before_if_left_brace = true
ij_java_space_before_if_parentheses = true
ij_java_space_before_method_call_parentheses = false
ij_java_space_before_method_left_brace = true
ij_java_space_before_method_parentheses = false
ij_java_space_before_opening_angle_bracket_in_type_parameter = false
ij_java_space_before_quest = true
ij_java_space_before_switch_left_brace = true
ij_java_space_before_switch_parentheses = true
ij_java_space_before_synchronized_left_brace = true
ij_java_space_before_synchronized_parentheses = true
ij_java_space_before_try_left_brace = true
ij_java_space_before_try_parentheses = true
ij_java_space_before_type_parameter_list = false
ij_java_space_before_while_keyword = true
ij_java_space_before_while_left_brace = true
ij_java_space_before_while_parentheses = true
ij_java_space_inside_one_line_enum_braces = false
ij_java_space_within_empty_array_initializer_braces = false
ij_java_space_within_empty_method_call_parentheses = false
ij_java_space_within_empty_method_parentheses = false
ij_java_spaces_around_additive_operators = true
ij_java_spaces_around_annotation_eq = true
ij_java_spaces_around_assignment_operators = true
ij_java_spaces_around_bitwise_operators = true
ij_java_spaces_around_equality_operators = true
ij_java_spaces_around_lambda_arrow = true
ij_java_spaces_around_logical_operators = true
ij_java_spaces_around_method_ref_dbl_colon = false
ij_java_spaces_around_multiplicative_operators = true
ij_java_spaces_around_relational_operators = true
ij_java_spaces_around_shift_operators = true
ij_java_spaces_around_type_bounds_in_type_parameters = true
ij_java_spaces_around_unary_operator = false
ij_java_spaces_inside_block_braces_when_body_is_present = false
ij_java_spaces_within_angle_brackets = false
ij_java_spaces_within_annotation_parentheses = false
ij_java_spaces_within_array_initializer_braces = false
ij_java_spaces_within_braces = false
ij_java_spaces_within_brackets = false
ij_java_spaces_within_cast_parentheses = false
ij_java_spaces_within_catch_parentheses = false
ij_java_spaces_within_deconstruction_list = false
ij_java_spaces_within_for_parentheses = false
ij_java_spaces_within_if_parentheses = false
ij_java_spaces_within_method_call_parentheses = false
ij_java_spaces_within_method_parentheses = false
ij_java_spaces_within_parentheses = false
ij_java_spaces_within_record_header = false
ij_java_spaces_within_switch_parentheses = false
ij_java_spaces_within_synchronized_parentheses = false
ij_java_spaces_within_try_parentheses = false
ij_java_spaces_within_while_parentheses = false
ij_java_special_else_if_treatment = true
ij_java_static_field_name_prefix =
ij_java_static_field_name_suffix =
ij_java_subclass_name_prefix =
ij_java_subclass_name_suffix = Impl
ij_java_switch_expressions_wrap = normal
ij_java_ternary_operation_signs_on_next_line = false
ij_java_ternary_operation_wrap = off
ij_java_test_name_prefix =
ij_java_test_name_suffix = Test
ij_java_throws_keyword_wrap = off
ij_java_throws_list_wrap = off
ij_java_use_external_annotations = false
ij_java_use_fq_class_names = false
ij_java_use_relative_indents = false
ij_java_use_single_class_imports = true
ij_java_variable_annotation_wrap = off
ij_java_visibility = public
ij_java_while_brace_force = never
ij_java_while_on_new_line = false
ij_java_wrap_comments = false
ij_java_wrap_first_method_in_call_chain = false
ij_java_wrap_long_lines = false
ij_java_wrap_semicolon_after_call_chain = false
[*.properties]
ij_properties_align_group_field_declarations = false
ij_properties_keep_blank_lines = false
ij_properties_key_value_delimiter = equals
ij_properties_spaces_around_key_value_delimiter = false
[.editorconfig]
ij_editorconfig_align_group_field_declarations = false
ij_editorconfig_space_after_colon = false
ij_editorconfig_space_after_comma = true
ij_editorconfig_space_before_colon = false
ij_editorconfig_space_before_comma = false
ij_editorconfig_spaces_around_assignment_operators = true
[{*.ant,*.fxml,*.jhm,*.jnlp,*.jrxml,*.jspx,*.pom,*.rng,*.tagx,*.tld,*.wsdl,*.xml,*.xsd,*.xsl,*.xslt,*.xul}]
ij_xml_align_attributes = true
ij_xml_align_text = false
ij_xml_attribute_wrap = normal
ij_xml_block_comment_add_space = false
ij_xml_block_comment_at_first_column = true
ij_xml_keep_blank_lines = 2
ij_xml_keep_indents_on_empty_lines = false
ij_xml_keep_line_breaks = true
ij_xml_keep_line_breaks_in_text = true
ij_xml_keep_whitespaces = false
ij_xml_keep_whitespaces_around_cdata = preserve
ij_xml_keep_whitespaces_inside_cdata = false
ij_xml_line_comment_at_first_column = true
ij_xml_space_after_tag_name = false
ij_xml_space_around_equals_in_attribute = false
ij_xml_space_inside_empty_tag = false
ij_xml_text_wrap = normal
[{*.bash,*.sh,*.zsh}]
ij_shell_binary_ops_start_line = false
ij_shell_keep_column_alignment_padding = false
ij_shell_minify_program = false
ij_shell_redirect_followed_by_space = false
ij_shell_switch_cases_indented = false
ij_shell_use_unix_line_separator = true
[{*.kt,*.kts}]
ij_kotlin_align_in_columns_case_branch = false
ij_kotlin_align_multiline_binary_operation = false
ij_kotlin_align_multiline_extends_list = false
ij_kotlin_align_multiline_method_parentheses = false
ij_kotlin_align_multiline_parameters = true
ij_kotlin_align_multiline_parameters_in_calls = false
ij_kotlin_allow_trailing_comma = false
ij_kotlin_allow_trailing_comma_on_call_site = false
ij_kotlin_assignment_wrap = normal
ij_kotlin_blank_lines_after_class_header = 0
ij_kotlin_blank_lines_around_block_when_branches = 0
ij_kotlin_blank_lines_before_declaration_with_comment_or_annotation_on_separate_line = 1
ij_kotlin_block_comment_add_space = false
ij_kotlin_block_comment_at_first_column = true
ij_kotlin_call_parameters_new_line_after_left_paren = true
ij_kotlin_call_parameters_right_paren_on_new_line = true
ij_kotlin_call_parameters_wrap = on_every_item
ij_kotlin_catch_on_new_line = false
ij_kotlin_class_annotation_wrap = split_into_lines
ij_kotlin_code_style_defaults = KOTLIN_OFFICIAL
ij_kotlin_continuation_indent_for_chained_calls = false
ij_kotlin_continuation_indent_for_expression_bodies = false
ij_kotlin_continuation_indent_in_argument_lists = false
ij_kotlin_continuation_indent_in_elvis = false
ij_kotlin_continuation_indent_in_if_conditions = false
ij_kotlin_continuation_indent_in_parameter_lists = false
ij_kotlin_continuation_indent_in_supertype_lists = false
ij_kotlin_else_on_new_line = false
ij_kotlin_enum_constants_wrap = off
ij_kotlin_extends_list_wrap = normal
ij_kotlin_field_annotation_wrap = split_into_lines
ij_kotlin_finally_on_new_line = false
ij_kotlin_if_rparen_on_new_line = true
ij_kotlin_import_nested_classes = false
ij_kotlin_imports_layout = *,java.**,javax.**,kotlin.**,^
ij_kotlin_indent_before_arrow_on_new_line = true
ij_kotlin_insert_whitespaces_in_simple_one_line_method = true
ij_kotlin_keep_blank_lines_before_right_brace = 2
ij_kotlin_keep_blank_lines_in_code = 2
ij_kotlin_keep_blank_lines_in_declarations = 2
ij_kotlin_keep_first_column_comment = true
ij_kotlin_keep_indents_on_empty_lines = false
ij_kotlin_keep_line_breaks = true
ij_kotlin_lbrace_on_next_line = false
ij_kotlin_line_break_after_multiline_when_entry = true
ij_kotlin_line_comment_add_space = false
ij_kotlin_line_comment_add_space_on_reformat = false
ij_kotlin_line_comment_at_first_column = true
ij_kotlin_method_annotation_wrap = split_into_lines
ij_kotlin_method_call_chain_wrap = normal
ij_kotlin_method_parameters_new_line_after_left_paren = true
ij_kotlin_method_parameters_right_paren_on_new_line = true
ij_kotlin_method_parameters_wrap = on_every_item
ij_kotlin_name_count_to_use_star_import = 5
ij_kotlin_name_count_to_use_star_import_for_members = 3
ij_kotlin_packages_to_use_import_on_demand = java.util.*,kotlinx.android.synthetic.**,io.ktor.**
ij_kotlin_parameter_annotation_wrap = off
ij_kotlin_space_after_comma = true
ij_kotlin_space_after_extend_colon = true
ij_kotlin_space_after_type_colon = true
ij_kotlin_space_before_catch_parentheses = true
ij_kotlin_space_before_comma = false
ij_kotlin_space_before_extend_colon = true
ij_kotlin_space_before_for_parentheses = true
ij_kotlin_space_before_if_parentheses = true
ij_kotlin_space_before_lambda_arrow = true
ij_kotlin_space_before_type_colon = false
ij_kotlin_space_before_when_parentheses = true
ij_kotlin_space_before_while_parentheses = true
ij_kotlin_spaces_around_additive_operators = true
ij_kotlin_spaces_around_assignment_operators = true
ij_kotlin_spaces_around_elvis = true
ij_kotlin_spaces_around_equality_operators = true
ij_kotlin_spaces_around_function_type_arrow = true
ij_kotlin_spaces_around_logical_operators = true
ij_kotlin_spaces_around_multiplicative_operators = true
ij_kotlin_spaces_around_range = false
ij_kotlin_spaces_around_relational_operators = true
ij_kotlin_spaces_around_unary_operator = false
ij_kotlin_spaces_around_when_arrow = true
ij_kotlin_variable_annotation_wrap = off
ij_kotlin_while_on_new_line = false
ij_kotlin_wrap_elvis_expressions = 1
ij_kotlin_wrap_expression_body_functions = 1
ij_kotlin_wrap_first_method_in_call_chain = false
[{*.har,*.json,*.jsonc,*.mcmeta,.prettierrc}]
ij_json_array_wrapping = split_into_lines
ij_json_keep_blank_lines_in_code = 0
ij_json_keep_indents_on_empty_lines = false
ij_json_keep_line_breaks = true
ij_json_keep_trailing_comma = false
ij_json_object_wrapping = split_into_lines
ij_json_property_alignment = do_not_align
ij_json_space_after_colon = true
ij_json_space_after_comma = true
ij_json_space_before_colon = false
ij_json_space_before_comma = false
ij_json_spaces_within_braces = false
ij_json_spaces_within_brackets = false
ij_json_wrap_long_lines = false
[{*.htm,*.html,*.sht,*.shtm,*.shtml}]
ij_html_add_new_line_before_tags = body,div,p,form,h1,h2,h3
ij_html_align_attributes = true
ij_html_align_text = false
ij_html_attribute_wrap = normal
ij_html_block_comment_add_space = false
ij_html_block_comment_at_first_column = true
ij_html_do_not_align_children_of_min_lines = 0
ij_html_do_not_break_if_inline_tags = title,h1,h2,h3,h4,h5,h6,p
ij_html_do_not_indent_children_of_tags = html,body,thead,tbody,tfoot
ij_html_enforce_quotes = false
ij_html_inline_tags = a,abbr,acronym,b,basefont,bdo,big,br,cite,cite,code,dfn,em,font,i,img,input,kbd,label,q,s,samp,select,small,span,strike,strong,sub,sup,textarea,tt,u,var
ij_html_keep_blank_lines = 2
ij_html_keep_indents_on_empty_lines = false
ij_html_keep_line_breaks = true
ij_html_keep_line_breaks_in_text = true
ij_html_keep_whitespaces = false
ij_html_keep_whitespaces_inside = span,pre,textarea
ij_html_line_comment_at_first_column = true
ij_html_new_line_after_last_attribute = never
ij_html_new_line_before_first_attribute = never
ij_html_quote_style = double
ij_html_remove_new_line_before_tags = br
ij_html_space_after_tag_name = false
ij_html_space_around_equality_in_attribute = false
ij_html_space_inside_empty_tag = false
ij_html_text_wrap = normal
[{*.markdown,*.md}]
ij_markdown_force_one_space_after_blockquote_symbol = true
ij_markdown_force_one_space_after_header_symbol = true
ij_markdown_force_one_space_after_list_bullet = true
ij_markdown_force_one_space_between_words = true
ij_markdown_format_tables = true
ij_markdown_insert_quote_arrows_on_wrap = true
ij_markdown_keep_indents_on_empty_lines = false
ij_markdown_keep_line_breaks_inside_text_blocks = true
ij_markdown_max_lines_around_block_elements = 1
ij_markdown_max_lines_around_header = 1
ij_markdown_max_lines_between_paragraphs = 1
ij_markdown_min_lines_around_block_elements = 1
ij_markdown_min_lines_around_header = 1
ij_markdown_min_lines_between_paragraphs = 1
ij_markdown_wrap_text_if_long = true
ij_markdown_wrap_text_inside_blockquotes = true
[{*.toml,Cargo.lock,Cargo.toml.orig,Gopkg.lock,Pipfile,poetry.lock,uv.lock}]
ij_toml_keep_indents_on_empty_lines = false
[{*.yaml,*.yml}]
ij_yaml_align_values_properties = do_not_align
ij_yaml_autoinsert_sequence_marker = true
ij_yaml_block_mapping_on_new_line = false
ij_yaml_indent_sequence_value = true
ij_yaml_keep_indents_on_empty_lines = false
ij_yaml_keep_line_breaks = true
ij_yaml_line_comment_add_space = false
ij_yaml_line_comment_add_space_on_reformat = false
ij_yaml_line_comment_at_first_column = true
ij_yaml_sequence_on_new_line = false
ij_yaml_space_before_colon = false
ij_yaml_spaces_within_braces = true
ij_yaml_spaces_within_brackets = true
+11
View File
@@ -0,0 +1,11 @@
# Use lf endings by default.
* text=auto eol=lf
# Declare text file types just in case
*.java text
*.yml text
*.xml text
*.md text
# Exclude binary files
*.png binary
+10 -1
View File
@@ -4,7 +4,16 @@ updates:
directory: "/"
schedule:
interval: "monthly"
- package-ecosystem: "maven"
- package-ecosystem: "gradle"
directory: "/"
schedule:
interval: "monthly"
ignore:
# The SQLite JDBC is provided by Spigot and, for now, Paper.
# To ensure it is still present even if Paper stops providing it,
# we need to declare it as a library.
# To prevent needlessly downloading (and loading) a second copy,
# we want to ensure that we are using the provided version.
- dependency-name: "org.xerial:sqlite-jdbc"
# Spigot and Spigot API updates are manual.
- dependency-name: "org.spigotmc:*"
+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@v7
uses: actions/github-script@v9
with:
script: |
let allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({
+9 -18
View File
@@ -7,7 +7,7 @@ on:
tags-ignore:
- '**'
paths-ignore:
- resource-pack/**
- resource-pack/openinv-legibility-pack/**
# Enable running CI via other Actions, i.e. for drafting releases and handling PRs.
workflow_call:
@@ -15,31 +15,22 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- uses: actions/setup-java@v4
- uses: actions/setup-java@v5
with:
distribution: 'temurin'
java-version: '21'
cache: 'maven'
java-version: '25'
# Install Spigot dependencies if necessary.
- name: Install Spigot Dependencies
run: . scripts/install_spigot_dependencies.sh
- uses: gradle/actions/setup-gradle@v5
- name: Build With Maven
run: mvn -e clean package -am -P all
- name: Build with Gradle
run: ./gradlew build
# Upload artifacts
- name: Upload Distributable Jar
id: upload-final
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: dist
path: ./target/*.jar
- name: Upload API Jar
id: upload-api
uses: actions/upload-artifact@v4
with:
name: api
path: ./api/target/openinvapi*.jar
path: ./dist/*
+9 -18
View File
@@ -12,34 +12,25 @@ jobs:
needs: [ run-ci ]
runs-on: ubuntu-latest
steps:
# Fetch all history - used to assemble changelog.
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set Release Variables
run: bash ./scripts/set_release_env.sh
- name: Download build
uses: actions/download-artifact@v4
uses: actions/download-artifact@v8
with:
name: dist
path: dist
- name: Package resource pack
run: |-
pushd resource-pack
zip -r ../dist/openinv-legibility-pack.zip .
popd
- name: Create Release
id: create-release
uses: softprops/action-gh-release@v2.1.0
uses: softprops/action-gh-release@v3.0.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
name: ${{ env.VERSIONED_NAME }}
body: ${{ env.GENERATED_CHANGELOG }}
name: OpenInv ${{ env.VERSIONED_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
**Spigot:** TODO VERSION
TODO HELLO HUMAN, PRESS THE GENERATE CHANGELOG BUTTON PLEASE.
draft: true
prerelease: false
files: ./dist/**
+91 -19
View File
@@ -1,4 +1,4 @@
name: Release to CurseForge
name: Release to external sites
on:
release:
@@ -8,30 +8,102 @@ jobs:
curseforge_release:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v4
with:
fetch-depth: 0
- &parse-paper-versions
name: Parse Paper Versions
id: parse_versions
run: |
# Find line declaring Paper versions.
raw=$(grep "\\*\\*Paper:\\*\\*" <<< "${{ github.event.release.body }}")
- name: Fetch Github Release Asset
# Enable extended glob pattern to match 0 or more whitespace characters.
shopt -s extglob
# Trim Paper versions identifier prefix.
raw=${raw/*([[:space:]])'**'Paper:'**'*([[:space:]])/}
# Replace commas and optional spaces with a newline.
raw=${raw//,*([[:space:]])/$'\n'}
# Turn extglob back off.
shopt -u extglob
echo "$raw"
printf "PAPER_VERSIONS<<EOF\n%s\nEOF\n" "$raw" >> "$GITHUB_OUTPUT"
- &fetch-release-asset
name: Fetch Github Release Asset
uses: dsaltares/fetch-gh-release-asset@1.1.2
with:
token: ${{ secrets.GITHUB_TOKEN }}
version: ${{ github.event.release.id }}
file: OpenInv.jar
- name: Set CurseForge Variables
run: . scripts/set_curseforge_env.sh "${{ github.event.release.body }}"
- name: Create CurseForge Release
uses: itsmeow/curseforge-upload@v3
uses: Kira-NT/mc-publish@v3.3
with:
token: "${{ secrets.CURSEFORGE_TOKEN }}"
project_id: 31432
game_endpoint: minecraft
file_path: ./OpenInv.jar
display_name: "${{ github.event.release.name }}"
game_versions: "${{ env.CURSEFORGE_MINECRAFT_VERSIONS }}"
release_type: release
changelog_type: markdown
changelog: "${{ env.CURSEFORGE_CHANGELOG }}"
curseforge-token: ${{ secrets.CURSEFORGE_TOKEN }}
curseforge-id: 31432
files: ./OpenInv.jar
game-versions: "${{ steps.parse_versions.outputs.PAPER_VERSIONS }}"
game-version-filter: releases
loaders: |
spigot
paper
folia
modrinth_release:
runs-on: ubuntu-latest
steps:
- *parse-paper-versions
- *fetch-release-asset
- name: Create Modrinth Release
uses: Kira-NT/mc-publish@v3.3
with:
modrinth-id: 1UlvXbzL
modrinth-featured: false
modrinth-token: ${{ secrets.MODRINTH_TOKEN }}
files: ./OpenInv.jar
game-versions: "${{ steps.parse_versions.outputs.PAPER_VERSIONS }}"
game-version-filter: releases
loaders: |
spigot
paper
folia
hangar_release:
runs-on: ubuntu-latest
steps:
- name: Parse Hangar Paper Versions
id: parse_versions
run: |
# Find line declaring Paper versions.
raw=$(grep "\\*\\*Paper:\\*\\*" <<< "${{ github.event.release.body }}")
# Trim Paper versions identifier prefix.
raw="${raw/'**'Paper:'**'/}"
# Trim leading and trailing spaces.
raw="${raw##[[:space:]]}"
raw="${raw%%[[:space:]]}"
# Generate JSON for Hangar. GitHub Actions provides jq 1.7 in the 24.04 image.
# Split on comma and optional spaces.
hangar_versions=$(jq -nc --arg versions "$raw" '{"PAPER": $versions | split(", *";null)}')
echo "$hangar_versions"
echo "HANGAR_PAPER_VERSIONS=$hangar_versions" >> "$GITHUB_OUTPUT"
- *fetch-release-asset
- name: Create Hangar Release
uses: benwoo1110/hangar-upload-action@v1
with:
api_token: ${{ secrets.HANGAR_TOKEN }}
slug: 'OpenInv'
version: ${{ github.event.release.tag_name }}
channel: release
description: "${{ github.event.release.body }}"
files: |-
[
{
"path": "./OpenInv.jar",
"platforms": ["PAPER"]
}
]
platform_dependencies: "${{ steps.parse_versions.outputs.HANGAR_PAPER_VERSIONS }}"
+1 -1
View File
@@ -16,7 +16,7 @@ jobs:
run: |
mkdir -p ./pr
echo ${{ github.event.number }} > ./pr/pr_number
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v7
with:
name: pr_number
path: pr/
+4 -4
View File
@@ -7,18 +7,18 @@ on:
tags-ignore:
- '**'
paths:
- resource-pack/**
- resource-pack/openinv-legibility-pack/**
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Build resource pack
id: upload-resource-pack
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: openinv-legibility-pack
path: ./resource-pack/
path: ./resource-pack/openinv-legibility-pack/
compression-level: 9
+4
View File
@@ -2,9 +2,13 @@
**/.project
**/.classpath
**/.idea/
**/.gradle/
**.iml
**/target/
**/bin/
**/out/
**/build/
**/dist/
**/dependency-reduced-pom.xml
**/pom.xml.versionsBackup
**/.vscode/
+20 -12
View File
@@ -1,7 +1,12 @@
## About
OpenInv is a [Bukkit plugin](https://dev.bukkit.org/projects/openinv) which allows users to open and edit anyone's inventory or ender chest - online or not!
OpenInv is a Bukkit plugin which allows users to open and edit anyone's
inventory or ender chest - online or not!
Downloads are available on [GitHub](https://github.com/Jikoo/OpenInv/releases), [DBO](https://dev.bukkit.org/projects/openinv)/[CurseForge](https://www.curseforge.com/minecraft/bukkit-plugins/openinv), [Modrinth](https://modrinth.com/plugin/openinv), and [Hangar](https://hangar.papermc.io/jikoo/OpenInv).
## Features
- **OpenInv**: Open anyone's inventory, even if they're offline.
- Read-only mode! Don't grant edit permission.
- Cross-world support! Allow access only from the same world.
@@ -14,17 +19,22 @@ OpenInv is a [Bukkit plugin](https://dev.bukkit.org/projects/openinv) which allo
- Cross-world support! Allow access only from the same world.
- **SilentContainer**: Open containers without displaying an animation or making sound.
- **AnyContainer**: Open containers, even if blocked by ocelots or blocks.
- **Resource Pack Support**: Personalize view using resource pack tricks like custom fonts and characters. See [the wiki](https://github.com/Jikoo/OpenInv/wiki/Customizing-Inventories) for details.
## Commands
See [the wiki](https://github.com/Jikoo/OpenInv/wiki/Commands).
## Permissions
See [the wiki](https://github.com/Jikoo/OpenInv/wiki/Permissions)
## For Developers
### As a Dependency
The OpenInv API is available via [JitPack](https://jitpack.io/).
```xml
<repositories>
<repository>
@@ -33,25 +43,23 @@ The OpenInv API is available via [JitPack](https://jitpack.io/).
</repository>
</repositories>
```
```xml
<dependencies>
<dependency>
<groupId>com.github.jikoo.OpenInv</groupId>
<artifactId>openinvapi</artifactId>
<groupId>com.github.Jikoo</groupId>
<artifactId>OpenInv</artifactId>
<version>${openinv.version}</version>
</dependency>
</dependencies>
```
Note that since JitPack only builds the API now, the "full" OpenInv jar on JitPack is actually the openinvapi artifact.
This is a change from previous dependency declaration that I hope to revert.
### Compilation
To compile, the relevant Spigot jars must be installed in the local repository.
As OpenInv is compiled against Mojang's mappings, you must run BuildTools with the `--remapped` argument:
`java -jar BuildTools.jar --remapped --rev $serverVersion`
`$serverVersion` is the version of the server, i.e. `1.18.1`
To compile just the API, execute Maven as usual:
`mvn clean package`
Execute the gradle wrapper:
`./gradlew build`
To compile the full plugin, use the provided profile with `-P all`:
`mvn clean package -am -P all`
For more information, check out the [official Maven guide](http://maven.apache.org/guides/introduction/introduction-to-profiles.html).
If you encounter issues with building the Spigot module, try running BuildTools manually.
+31
View File
@@ -0,0 +1,31 @@
repositories {
mavenCentral()
maven("https://hub.spigotmc.org/nexus/content/groups/public/")
}
dependencies {
compileOnly(libs.spigotapi)
implementation(project(":openinvapi"))
}
java {
toolchain.languageVersion = JavaLanguageVersion.of(21)
}
tasks.withType<JavaCompile>().configureEach {
options.release = 21
}
tasks.processResources {
expand("version" to version)
}
tasks.register<Copy>("distributeAddons") {
into(rootProject.layout.projectDirectory.dir("dist"))
from(tasks.jar)
rename("openinvtogglepersist.*\\.jar", "OITogglePersist.jar")
}
tasks.assemble {
dependsOn(tasks.named("distributeAddons"))
}
-72
View File
@@ -1,72 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>openinvparent</artifactId>
<groupId>com.lishid</groupId>
<relativePath>../../pom.xml</relativePath>
<version>5.1.4</version>
</parent>
<artifactId>openinvtogglepersist</artifactId>
<dependencies>
<dependency>
<artifactId>annotations</artifactId>
<groupId>org.jetbrains</groupId>
</dependency>
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
</dependency>
<dependency>
<artifactId>openinvapi</artifactId>
<groupId>com.lishid</groupId>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<finalName>OITogglePersist</finalName>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<executions>
<execution>
<id>copy-final-jar</id>
<phase>package</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${project.parent.build.directory}</outputDirectory>
<resources>
<resource>
<directory>${project.build.directory}</directory>
<includes>
<include>${project.build.finalName}.jar</include>
</includes>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
@@ -64,13 +64,16 @@ public class TogglePersist extends JavaPlugin implements Listener {
}
private void set(UUID playerId, String toggleName) {
enabledToggles.compute(playerId, (uuid, toggles) -> {
if (toggles == null) {
toggles = new HashSet<>();
}
toggles.add(toggleName);
return toggles;
});
enabledToggles.compute(
playerId,
(uuid, toggles) -> {
if (toggles == null) {
toggles = new HashSet<>();
}
toggles.add(toggleName);
return toggles;
}
);
}
@Override
@@ -97,13 +100,16 @@ public class TogglePersist extends JavaPlugin implements Listener {
String idString = playerToggles.getKey().toString();
for (String toggleName : playerToggles.getValue()) {
// Add player ID to listing for each enabled toggle.
converted.compute(toggleName, (name, ids) -> {
if (ids == null) {
ids = new ArrayList<>();
}
ids.add(idString);
return ids;
});
converted.compute(
toggleName,
(name, ids) -> {
if (ids == null) {
ids = new ArrayList<>();
}
ids.add(idString);
return ids;
}
);
}
}
return converted;
@@ -133,10 +139,13 @@ public class TogglePersist extends JavaPlugin implements Listener {
if (event.isEnabled()) {
set(event.getPlayerId(), event.getToggle().getName());
} else {
enabledToggles.computeIfPresent(event.getPlayerId(), (uuid, toggles) -> {
toggles.remove(event.getToggle().getName());
return toggles.isEmpty() ? null : toggles;
});
enabledToggles.computeIfPresent(
event.getPlayerId(),
(uuid, toggles) -> {
toggles.remove(event.getToggle().getName());
return toggles.isEmpty() ? null : toggles;
}
);
}
}
@@ -1,6 +1,6 @@
name: OITogglePersist
main: com.github.jikoo.openinv.togglepersist.TogglePersist
version: ${project.version}
version: ${version}
author: Jikoo
description: An OpenInv addon allowing /anycontainer and /silentcontainer to persist across sessions.
api-version: "1.20"
+30
View File
@@ -0,0 +1,30 @@
plugins {
`maven-publish`
}
repositories {
mavenCentral()
maven("https://hub.spigotmc.org/nexus/content/groups/public/")
}
dependencies {
compileOnly(libs.spigotapi)
}
java {
toolchain.languageVersion = JavaLanguageVersion.of(21)
}
tasks.withType<JavaCompile>().configureEach {
options.release = 21
}
publishing {
publications {
create<MavenPublication>("jitpack") {
groupId = "com.github.Jikoo.OpenInv"
artifactId = "openinvapi"
from(components["java"])
}
}
}
-49
View File
@@ -1,49 +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/>.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>openinvparent</artifactId>
<groupId>com.lishid</groupId>
<version>5.1.4</version>
</parent>
<artifactId>openinvapi</artifactId>
<name>OpenInvAPI</name>
<dependencies>
<dependency>
<artifactId>annotations</artifactId>
<groupId>org.jetbrains</groupId>
</dependency>
<dependency>
<artifactId>spigot-api</artifactId>
<groupId>org.spigotmc</groupId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
+143 -130
View File
@@ -35,151 +35,164 @@ import java.util.logging.Logger;
*/
public interface IOpenInv {
/**
* Check if the server version is supported by OpenInv.
*
* @return true if the server version is supported
*/
boolean isSupportedVersion();
/**
* Check if the server version is supported by OpenInv.
*
* @return true if the server version is supported
*/
boolean isSupportedVersion();
/**
* Check the configuration value for whether OpenInv saves player data when unloading players. This is exclusively
* for users who do not allow editing of inventories, only viewing, and wish to prevent any possibility of bugs such
* as lishid#40. If true, OpenInv will not ever save any edits made to players.
*
* @return false unless configured otherwise
*/
boolean disableSaving();
/**
* Check the configuration value for whether OpenInv saves player data when unloading players. This is exclusively
* for users who do not allow editing of inventories, only viewing, and wish to prevent any possibility of bugs such
* as lishid#40. If true, OpenInv will not ever save any edits made to players.
*
* @return false unless configured otherwise
*/
boolean disableSaving();
/**
* Check the configuration value for whether OpenInv allows offline access. If true, OpenInv will not load or allow
* modification of players while they are not online. This does not prevent other plugins from using existing loaded
* players who have gone offline.
*
* @return false unless configured otherwise
* @since 4.2.0
*/
boolean disableOfflineAccess();
/**
* Check the configuration value for whether OpenInv allows offline access. If true, OpenInv will not load or allow
* modification of players while they are not online. This does not prevent other plugins from using existing loaded
* players who have gone offline.
*
* @return false unless configured otherwise
* @since 4.2.0
*/
boolean disableOfflineAccess();
/**
* Check the configuration value for whether OpenInv uses history for opening commands. If false, OpenInv will use
* the previous parameterized search when no parameters are provided.
*
* @return false unless configured otherwise
* @since 4.3.0
*/
boolean noArgsOpensSelf();
/**
* Check the configuration value for whether OpenInv uses history for opening commands. If false, OpenInv will use
* the previous parameterized search when no parameters are provided.
*
* @return false unless configured otherwise
* @since 4.3.0
*/
boolean noArgsOpensSelf();
/**
* Get the active {@link IAnySilentContainer} implementation.
*
* @return the active implementation for the server version
* @throws IllegalStateException if the server version is unsupported
*/
@NotNull IAnySilentContainer getAnySilentContainer();
/**
* Get the active {@link IAnySilentContainer} implementation.
*
* @return the active implementation for the server version
* @throws IllegalStateException if the server version is unsupported
*/
@NotNull IAnySilentContainer getAnySilentContainer();
/**
* Get whether a user has AnyContainer mode enabled.
*
* @param offline the user to obtain the state of
* @return true if AnyContainer mode is enabled
*/
boolean getAnyContainerStatus(@NotNull OfflinePlayer offline);
/**
* Get whether a user has AnyContainer mode enabled.
*
* @param offline the user to obtain the state of
* @return true if AnyContainer mode is enabled
*/
boolean getAnyContainerStatus(@NotNull OfflinePlayer offline);
/**
* Set whether a user has AnyContainer mode enabled.
*
* @param offline the user to set the state of
* @param status the state of the mode
*/
void setAnyContainerStatus(@NotNull OfflinePlayer offline, boolean status);
/**
* Set whether a user has AnyContainer mode enabled.
*
* @param offline the user to set the state of
* @param status the state of the mode
*/
void setAnyContainerStatus(@NotNull OfflinePlayer offline, boolean status);
/**
* Get whether a user has SilentContainer mode enabled.
*
* @param offline the user to obtain the state of
* @return true if SilentContainer mode is enabled
*/
boolean getSilentContainerStatus(@NotNull OfflinePlayer offline);
/**
* Get whether a user has SilentContainer mode enabled.
*
* @param offline the user to obtain the state of
* @return true if SilentContainer mode is enabled
*/
boolean getSilentContainerStatus(@NotNull OfflinePlayer offline);
/**
* Set whether a user has SilentContainer mode enabled.
*
* @param offline the user to set the state of
* @param status the state of the mode
*/
void setSilentContainerStatus(@NotNull OfflinePlayer offline, boolean status);
/**
* Set whether a user has SilentContainer mode enabled.
*
* @param offline the user to set the state of
* @param status the state of the mode
*/
void setSilentContainerStatus(@NotNull OfflinePlayer offline, boolean status);
/**
* Get an {@link ISpecialEnderChest} for a user.
*
* @param player the {@link Player} owning the inventory
* @param online whether the owner is currently online
* @return the created inventory
* @throws IllegalStateException if the server version is unsupported
* @throws InstantiationException if there was an issue creating the inventory
*/
@NotNull ISpecialEnderChest getSpecialEnderChest(@NotNull Player player, boolean online) throws InstantiationException;
/**
* Get an {@link ISpecialEnderChest} for a user.
*
* @param player the {@link Player} owning the inventory
* @param online whether the owner is currently online
* @return the created inventory
* @throws IllegalStateException if the server version is unsupported
* @throws InstantiationException if there was an issue creating the inventory
*/
@NotNull ISpecialEnderChest getSpecialEnderChest(
@NotNull Player player,
boolean online
) throws InstantiationException;
/**
* Get an {@link ISpecialPlayerInventory} for a user.
*
* @param player the {@link Player} owning the inventory
* @param online whether the owner is currently online
* @return the created inventory
* @throws IllegalStateException if the server version is unsupported
* @throws InstantiationException if there was an issue creating the inventory
*/
@NotNull ISpecialPlayerInventory getSpecialInventory(@NotNull Player player, boolean online) throws InstantiationException;
/**
* Get an {@link ISpecialPlayerInventory} for a user.
*
* @param player the {@link Player} owning the inventory
* @param online whether the owner is currently online
* @return the created inventory
* @throws IllegalStateException if the server version is unsupported
* @throws InstantiationException if there was an issue creating the inventory
*/
@NotNull ISpecialPlayerInventory getSpecialInventory(
@NotNull Player player,
boolean online
) throws InstantiationException;
/**
* Open an {@link ISpecialInventory} for a {@link Player}.
*
* @param player the viewer
* @param inventory the inventory to open
* @return the resulting {@link InventoryView}
*/
@Nullable InventoryView openInventory(@NotNull Player player, @NotNull ISpecialInventory inventory);
/**
* @deprecated Use {@link #openInventory(Player, ISpecialInventory, boolean)}
*/
@Deprecated(forRemoval = true, since = "5.2.0")
@Nullable InventoryView openInventory(@NotNull Player player, @NotNull ISpecialInventory inventory);
/**
* Check if a {@link Player} is currently loaded by OpenInv.
*
* @param playerUuid the {@link UUID} of the {@code Player}
* @return whether the {@code Player} is loaded
* @since 4.2.0
*/
boolean isPlayerLoaded(@NotNull UUID playerUuid);
/**
* Open an {@link ISpecialInventory} for a {@link Player}.
*
* @param player the viewer
* @param inventory the inventory to open
* @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);
/**
* Load a {@link Player} from an {@link OfflinePlayer}. If the user has not played before or the default world for
* the server is not loaded, this will return {@code null}.
*
* @param offline the {@code OfflinePlayer} to load a {@code Player} for
* @return the loaded {@code Player}
* @throws IllegalStateException if the server version is unsupported
*/
@Nullable Player loadPlayer(@NotNull final OfflinePlayer offline);
/**
* Check if a {@link Player} is currently loaded by OpenInv.
*
* @param playerUuid the {@link UUID} of the {@code Player}
* @return whether the {@code Player} is loaded
* @since 4.2.0
*/
boolean isPlayerLoaded(@NotNull UUID playerUuid);
/**
* Match an existing {@link OfflinePlayer}. If the name is a {@link UUID#toString() UUID string}, this will only
* return the user if they have actually played on the server before, unlike {@link Bukkit#getOfflinePlayer(UUID)}.
*
* <p>This method is potentially very heavily blocking. It should not ever be called on the
* main thread, and if it is, a stack trace will be displayed alerting server owners to the
* call.
*
* @param name the string to match
* @return the user with the closest matching name
*/
@Nullable OfflinePlayer matchPlayer(@NotNull String name);
/**
* Load a {@link Player} from an {@link OfflinePlayer}. If the user has not played before or the default world for
* the server is not loaded, this will return {@code null}.
*
* @param offline the {@code OfflinePlayer} to load a {@code Player} for
* @return the loaded {@code Player}
* @throws IllegalStateException if the server version is unsupported
*/
@Nullable Player loadPlayer(@NotNull final OfflinePlayer offline);
/**
* Forcibly close inventories of and unload any cached data for a user.
*
* @param offline the {@link OfflinePlayer} to unload
*/
void unload(@NotNull OfflinePlayer offline);
/**
* Match an existing {@link OfflinePlayer}. If the name is a {@link UUID#toString() UUID string}, this will only
* return the user if they have actually played on the server before, unlike {@link Bukkit#getOfflinePlayer(UUID)}.
*
* <p>This method is potentially very heavily blocking. It should not ever be called on the
* main thread, and if it is, a stack trace will be displayed alerting server owners to the
* call.
*
* @param name the string to match
* @return the user with the closest matching name
*/
@Nullable OfflinePlayer matchPlayer(@NotNull String name);
Logger getLogger();
/**
* Forcibly close inventories of and unload any cached data for a user.
*
* @param offline the {@link OfflinePlayer} to unload
*/
void unload(@NotNull OfflinePlayer offline);
Logger getLogger();
}
@@ -26,8 +26,8 @@ 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")
allowedOnPath = ".*/com/lishid/openinv/event/OpenEvents.java"
)
@ApiStatus.Internal
OpenPlayerSaveEvent(@NotNull Player player, @NotNull ISpecialInventory inventory) {
super(player);
@@ -27,8 +27,8 @@ 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")
allowedOnPath = ".*/com/lishid/openinv/event/(OpenPlayerSaveEvent|OpenEvents).java"
)
@ApiStatus.Internal
PlayerSaveEvent(@NotNull Player player) {
super(player);
@@ -22,8 +22,8 @@ public class PlayerToggledEvent extends Event {
@RestrictedApi(
explanation = "Constructor is not considered part of the API and may be subject to change.",
link = "",
allowedOnPath = ".*/com/lishid/openinv/event/OpenEvents.java")
allowedOnPath = ".*/com/lishid/openinv/event/OpenEvents.java"
)
@ApiStatus.Internal
PlayerToggledEvent(@NotNull PlayerToggle toggle, @NotNull UUID uuid, boolean enabled) {
this.toggle = toggle;
@@ -23,100 +23,109 @@ import org.bukkit.block.EnderChest;
import org.bukkit.block.data.Directional;
import org.bukkit.entity.Cat;
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;
public interface IAnySilentContainer {
/**
* Forcibly open the container at the given coordinates for the Player. This will open blocked containers! Be sure
* to check {@link #isAnyContainerNeeded(Block)} first if that is not desirable.
*
* @param player the {@link Player} opening the container
* @param silent whether the container's noise is to be silenced
* @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);
/**
* Forcibly open the container at the given coordinates for the Player. This will open blocked containers! Be sure
* to check {@link #isAnyContainerNeeded(Block)} first if that is not desirable.
*
* @param player the {@link Player} opening the container
* @param silent whether the container's noise is to be silenced
* @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);
/**
* Perform operations required to close the current container silently.
*
* @param player the {@link Player} closing a container
*/
void deactivateContainer(@NotNull Player player);
/**
* Perform operations required to close the current container silently.
*
* @param player the {@link Player} closing a container
*/
void deactivateContainer(@NotNull Player player);
/**
* Check if the container at the given coordinates is blocked.
*
* @param block the {@link Block} of the container
* @return true if the container is blocked
*/
boolean isAnyContainerNeeded(@NotNull Block block);
/**
* Check if the container at the given coordinates is blocked.
*
* @param block the {@link Block} of the container
* @return true if the container is blocked
*/
boolean isAnyContainerNeeded(@NotNull Block block);
/**
* Check if a shulker box block cannot be opened under ordinary circumstances.
*
* @param shulkerBox the shulker box block
* @return whether the container is blocked
*/
default boolean isShulkerBlocked(@NotNull Block shulkerBox) {
Directional directional = (Directional) shulkerBox.getBlockData();
BlockFace facing = directional.getFacing();
// Construct a new 1-block bounding box at the origin.
BoundingBox box = new BoundingBox(0, 0, 0, 1, 1, 1);
// Expand the box in the direction the shulker will open.
box.expand(facing, 0.5);
// Move the box away from the origin by a block so only the expansion intersects with a box around the origin.
box.shift(facing.getOppositeFace().getDirection());
// Check if the relative block's collision shape (which will be at the origin) intersects with the expanded box.
return shulkerBox.getRelative(facing).getCollisionShape().overlaps(box);
}
/**
* Check if a shulker box block cannot be opened under ordinary circumstances.
*
* @param shulkerBox the shulker box block
* @return whether the container is blocked
*/
default boolean isShulkerBlocked(@NotNull Block shulkerBox) {
Directional directional = (Directional) shulkerBox.getBlockData();
BlockFace facing = directional.getFacing();
// Construct a new 1-block bounding box at the origin.
BoundingBox box = new BoundingBox(0, 0, 0, 1, 1, 1);
// Expand the box in the direction the shulker will open.
box.expand(facing, 0.5);
// Move the box away from the origin by a block so only the expansion intersects with a box around the origin.
box.shift(facing.getOppositeFace().getDirection());
// Check if the relative block's collision shape (which will be at the origin) intersects with the expanded box.
return shulkerBox.getRelative(facing).getCollisionShape().overlaps(box);
}
/**
* Check if a chest cannot be opened under ordinary circumstances.
*
* @param chest the chest block
* @return whether the container is blocked
*/
default boolean isChestBlocked(@NotNull 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();
}
/**
* Check if a chest cannot be opened under ordinary circumstances.
*
* @param chest the chest block
* @return whether the container is blocked
*/
default boolean isChestBlocked(@NotNull 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();
}
/**
* Check if the given {@link Block} is a container which can be unblocked or silenced.
*
* @param block the potential container
* @return true if the type is a supported container
*/
boolean isAnySilentContainer(@NotNull Block block);
/**
* Check if the given {@link Block} is a container which can be unblocked or silenced.
*
* @param block the potential container
* @return true if the type is a supported container
*/
boolean isAnySilentContainer(@NotNull Block block);
/**
* Check if the given {@link BlockState} is a container which can be unblocked or silenced.
*
* @param blockState the potential container
* @return true if the type is a supported container
*/
default boolean isAnySilentContainer(@NotNull BlockState blockState) {
return (blockState instanceof InventoryHolder holder && isAnySilentContainer(holder))
|| blockState instanceof EnderChest;
}
/**
* Check if the given {@link BlockState} is a container which can be unblocked or silenced.
*
* @param blockState the potential container
* @return true if the type is a supported container
*/
default boolean isAnySilentContainer(@NotNull BlockState blockState) {
return (blockState instanceof InventoryHolder holder && isAnySilentContainer(holder))
|| blockState instanceof EnderChest;
}
/**
* Check if the given {@link InventoryHolder} is a container which can be unblocked or silenced.
*
* @param holder the potential container
* @return true if the type is a supported container
*/
default boolean isAnySilentContainer(@NotNull InventoryHolder holder) {
return holder instanceof org.bukkit.block.EnderChest
|| holder instanceof org.bukkit.block.Chest
|| holder instanceof org.bukkit.block.DoubleChest
|| holder instanceof org.bukkit.block.ShulkerBox
|| holder instanceof org.bukkit.block.Barrel;
}
/**
* Check if the given {@link InventoryHolder} is a container which can be unblocked or silenced.
*
* @param holder the potential container
* @return true if the type is a supported container
*/
default boolean isAnySilentContainer(@NotNull InventoryHolder holder) {
return holder instanceof org.bukkit.block.EnderChest
|| holder instanceof org.bukkit.block.Chest
|| holder instanceof org.bukkit.block.DoubleChest
|| holder instanceof org.bukkit.block.ShulkerBox
|| holder instanceof org.bukkit.block.Barrel;
}
/**
* Check if the given {@link Inventory} belongs to a container which can be unblocked or silenced.
*
* @param inventory the potential container inventory
* @return true if the type is a supported container
*/
boolean isAnySilentContainer(@NotNull Inventory inventory);
}
@@ -16,9 +16,17 @@
package com.lishid.openinv.internal;
import org.bukkit.event.inventory.InventoryType;
import org.jetbrains.annotations.NotNull;
/**
* An {@link ISpecialInventory} representing an ender chest.
*/
public interface ISpecialEnderChest extends ISpecialInventory {
@Override
default @NotNull InventoryType getBukkitType() {
return InventoryType.ENDER_CHEST;
}
}
@@ -18,6 +18,7 @@ package com.lishid.openinv.internal;
import org.bukkit.entity.HumanEntity;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.InventoryType;
import org.bukkit.inventory.Inventory;
import org.jetbrains.annotations.NotNull;
@@ -26,39 +27,49 @@ import org.jetbrains.annotations.NotNull;
*/
public interface ISpecialInventory {
/**
* Get the {@link Inventory} associated with this {@code ISpecialInventory}.
*
* @return the Bukkit inventory
*/
@NotNull Inventory getBukkitInventory();
/**
* Get the {@link Inventory} associated with this {@code ISpecialInventory}.
*
* @return the Bukkit inventory
*/
@NotNull Inventory getBukkitInventory();
/**
* Set the owning {@link Player} instance to a newly-joined user.
*
* @param player the user coming online
*/
void setPlayerOnline(@NotNull Player player);
/**
* Get the {@link InventoryType} corresponding to this {@code ISpecialInventory}.
*
* @return the type of Bukkit inventory
*/
@NotNull InventoryType getBukkitType();
/**
* Mark the owner of the inventory offline.
*/
void setPlayerOffline();
/**
* Set the owning {@link Player} instance to a newly-joined user.
*
* @param player the user coming online
*/
void setPlayerOnline(@NotNull Player player);
/**
* Get whether the inventory is being viewed by any users.
*
* @return true if the inventory is being viewed
*/
default boolean isInUse() {
return !getBukkitInventory().getViewers().isEmpty();
}
/**
* Mark the owner of the inventory offline.
*
* @deprecated No longer used by implementations.
*/
@Deprecated(forRemoval = true, since = "5.1.11")
default void setPlayerOffline() {}
/**
* Get the {@link Player} who owns the inventory.
*
* @return the {@link HumanEntity} who owns the inventory
*/
@NotNull HumanEntity getPlayer();
/**
* Get whether the inventory is being viewed by any users.
*
* @return true if the inventory is being viewed
*/
default boolean isInUse() {
return !getBukkitInventory().getViewers().isEmpty();
}
/**
* Get the {@link Player} who owns the inventory.
*
* @return the {@link HumanEntity} who owns the inventory
*/
@NotNull HumanEntity getPlayer();
}
@@ -16,35 +16,17 @@
package com.lishid.openinv.internal;
import java.util.List;
import org.bukkit.entity.HumanEntity;
import org.bukkit.inventory.Inventory;
import org.bukkit.event.inventory.InventoryType;
import org.jetbrains.annotations.NotNull;
/**
* An {@link ISpecialInventory} representing a player inventory.
*/
public interface ISpecialPlayerInventory extends ISpecialInventory {
/*
* Player inventory usage varies from all other inventories - as the inventory is technically open at all times,
* if the player is online or has been online while the inventory is open, they are in the viewer list.
*/
@Override
default boolean isInUse() {
Inventory inventory = getBukkitInventory();
List<HumanEntity> viewers = inventory.getViewers();
if (viewers.size() != 1) {
return !viewers.isEmpty();
}
HumanEntity viewer = viewers.get(0);
if (!viewer.getUniqueId().equals(getPlayer().getUniqueId())) {
return true;
}
return viewer.getOpenInventory().getTopInventory().equals(inventory);
}
@Override
default @NotNull InventoryType getBukkitType() {
return InventoryType.PLAYER;
}
}
@@ -29,76 +29,78 @@ import java.util.function.BiFunction;
public final class InventoryAccess {
private static @Nullable BiFunction<Inventory, Class<? extends ISpecialInventory>, ISpecialInventory> provider;
private static @Nullable BiFunction<Inventory, Class<? extends ISpecialInventory>, ISpecialInventory> provider;
public static boolean isUsable() {
return provider != null;
}
public static boolean isUsable() {
return provider != null;
}
/**
* Check if an {@link Inventory} is an {@link ISpecialPlayerInventory} implementation.
*
* @param inventory the Bukkit inventory
* @return true if backed by the correct implementation
*/
public static boolean isPlayerInventory(@NotNull Inventory inventory) {
return getPlayerInventory(inventory) != null;
}
/**
* Check if an {@link Inventory} is an {@link ISpecialPlayerInventory} implementation.
*
* @param inventory the Bukkit inventory
* @return true if backed by the correct implementation
*/
public static boolean isPlayerInventory(@NotNull Inventory inventory) {
return getPlayerInventory(inventory) != null;
}
/**
* Get the {@link ISpecialPlayerInventory} backing an {@link Inventory}. Returns {@code null} if the inventory is
* not backed by the correct class.
*
* @param inventory the Bukkit inventory
* @return the backing implementation if available
*/
public static @Nullable ISpecialPlayerInventory getPlayerInventory(@NotNull Inventory inventory) {
return provider == null ? null : (ISpecialPlayerInventory) provider.apply(inventory, ISpecialPlayerInventory.class);
}
/**
* Get the {@link ISpecialPlayerInventory} backing an {@link Inventory}. Returns {@code null} if the inventory is
* not backed by the correct class.
*
* @param inventory the Bukkit inventory
* @return the backing implementation if available
*/
public static @Nullable ISpecialPlayerInventory getPlayerInventory(@NotNull Inventory inventory) {
return provider == null ? null : (ISpecialPlayerInventory) provider.apply(inventory, ISpecialPlayerInventory.class);
}
/**
* Check if an {@link Inventory} is an {@link ISpecialEnderChest} implementation.
*
* @param inventory the Bukkit inventory
* @return true if backed by the correct implementation
*/
public static boolean isEnderChest(@NotNull Inventory inventory) {
return getEnderChest(inventory) != null;
}
/**
* Check if an {@link Inventory} is an {@link ISpecialEnderChest} implementation.
*
* @param inventory the Bukkit inventory
* @return true if backed by the correct implementation
*/
public static boolean isEnderChest(@NotNull Inventory inventory) {
return getEnderChest(inventory) != null;
}
/**
* Get the {@link ISpecialEnderChest} backing an {@link Inventory}. Returns {@code null} if the inventory is
* not backed by the correct class.
*
* @param inventory the Bukkit inventory
* @return the backing implementation if available
*/
public static @Nullable ISpecialEnderChest getEnderChest(@NotNull Inventory inventory) {
return provider == null ? null : (ISpecialEnderChest) provider.apply(inventory, ISpecialEnderChest.class);
}
/**
* Get the {@link ISpecialEnderChest} backing an {@link Inventory}. Returns {@code null} if the inventory is
* not backed by the correct class.
*
* @param inventory the Bukkit inventory
* @return the backing implementation if available
*/
public static @Nullable ISpecialEnderChest getEnderChest(@NotNull Inventory inventory) {
return provider == null ? null : (ISpecialEnderChest) provider.apply(inventory, ISpecialEnderChest.class);
}
/**
* Get a {@link ISpecialInventory} backing an {@link Inventory}. Returns {@code null} if the inventory is not backed
* by the correct class.
*
* @param inventory the Bukkit inventory
* @return the backing implementation if available
*/
public static @Nullable ISpecialInventory getInventory(@NotNull Inventory inventory) {
return provider == null ? null : provider.apply(inventory, ISpecialInventory.class);
}
/**
* Get a {@link ISpecialInventory} backing an {@link Inventory}. Returns {@code null} if the inventory is not backed
* by the correct class.
*
* @param inventory the Bukkit inventory
* @return the backing implementation if available
*/
public static @Nullable ISpecialInventory getInventory(@NotNull 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) {
InventoryAccess.provider = provider;
}
@RestrictedApi(
explanation = "Not part of the API.",
allowedOnPath = ".*/com/lishid/openinv/util/InternalAccessor.java"
)
@ApiStatus.Internal
static void setProvider(
@Nullable BiFunction<Inventory, Class<? extends ISpecialInventory>, ISpecialInventory> provider
) {
InventoryAccess.provider = provider;
}
private InventoryAccess() {
throw new IllegalStateException("Cannot create instance of utility class.");
}
private InventoryAccess() {
throw new IllegalStateException("Cannot create instance of utility class.");
}
}
@@ -0,0 +1,128 @@
/*
* 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;
import org.bukkit.permissions.Permissible;
import org.jetbrains.annotations.NotNull;
/**
* An enum containing all permissions directly checked by OpenInv.
*
* <p>Note that this is not an exhaustive list! This does not contain
* all permissions managed by Bukkit, largely parent nodes.</p>
*/
public enum Permissions {
/// Permission to open one's own inventory.
INVENTORY_OPEN_SELF("inventory.open.self"),
/// Permission to open someone else's inventory.
INVENTORY_OPEN_OTHER("inventory.open.other"),
/**
* Permission to edit one's own inventory.
*
* <p>Note that this does not guarantee that the user has access to open the inventory!
* Be sure to check {@link #INVENTORY_OPEN_SELF} first.</p>
*/
INVENTORY_EDIT_SELF("inventory.edit.self"),
/**
* Permission to edit someone else's inventory.
*
* <p>Note that this does not guarantee that the user has access to open the inventory!
* Be sure to check {@link #INVENTORY_OPEN_OTHER} first.</p>
*/
INVENTORY_EDIT_OTHER("inventory.edit.other"),
/// Permission to insert any item into the head slot.
INVENTORY_SLOT_HEAD_ANY("inventory.slot.head.any"),
/// Permission to insert any item into the chest slot.
INVENTORY_SLOT_CHEST_ANY("inventory.slot.chest.any"),
/// Permission to insert any item into the legs slot.
INVENTORY_SLOT_LEGS_ANY("inventory.slot.legs.any"),
/// Permission to insert any item into the feet slot.
INVENTORY_SLOT_FEET_ANY("inventory.slot.feet.any"),
/// Permission to drop items as the player via the drop slot.
INVENTORY_SLOT_DROP("inventory.slot.drop"),
/// Permission to open one's own ender chest.
ENDERCHEST_OPEN_SELF("enderchest.open.self"),
/// Permission to open someone else's ender chest.
ENDERCHEST_OPEN_OTHER("enderchest.open.other"),
/**
* Permission to edit one's own ender chest.
*
* <p>Note that this does not guarantee that the user has access to open the inventory!
* Be sure to check {@link #ENDERCHEST_OPEN_SELF} first.</p>
*/
ENDERCHEST_EDIT_SELF("enderchest.edit.self"),
/**
* Permission to edit someone else's ender chest.
*
* <p>Note that this does not guarantee that the user has access to open the inventory!
* Be sure to check {@link #ENDERCHEST_OPEN_OTHER} first.</p>
*/
ENDERCHEST_EDIT_OTHER("enderchest.edit.other"),
/// Permission to clear one's own inventory.
CLEAR_SELF("clear.self"),
/// Permission to clear someone else's inventory.
CLEAR_OTHER("clear.other"),
/// Permission to view inventories and ender chests from other worlds.
ACCESS_CROSSWORLD("access.crossworld"),
/// Permission to access offline players' inventories and ender chests.
ACCESS_OFFLINE("access.offline"),
/// Permission to access online players' inventories and ender chests.
ACCESS_ONLINE("access.online"),
/// Permission granting the ability to edit players with the same access level.
ACCESS_EQUAL_EDIT("access.equal.edit"),
/// Permission to view, but not edit, players with the same access level.
ACCESS_EQUAL_VIEW("access.equal.view"),
/// Permission to deny access to players with the same access level.
ACCESS_EQUAL_DENY("access.equal.deny"),
/// Permission to perform inventory interaction while in spectator mode.
SPECTATE_CLICK("spectate.click"),
/// Permission to use the AnyContainer feature.
CONTAINER_ANY("container.any"),
/// Permission to use the AnyContainer feature but not the command to toggle it.
CONTAINER_ANY_USE("container.any.use"),
/// Permission to use the SilentContainer feature.
CONTAINER_SILENT("container.silent"),
/// Permission to use the SilentContainer feature but not the command to toggle it.
CONTAINER_SILENT_USE("container.silent.use"),
/// Permission to search inventories and ender chests.
SEARCH_INVENTORY("search.inventory"),
/// Permission to search containers.
SEARCH_CONTAINER("search.container");
private final String permission;
Permissions(String permission) {
this.permission = "openinv." + permission;
}
/**
* Check if a {@link Permissible} has this permission.
*
* @param permissible the Permissible
* @return true if the permission is granted
*/
public boolean hasPermission(@NotNull Permissible permissible) {
return permissible.hasPermission(permission);
}
}
@@ -1,5 +1,6 @@
package com.lishid.openinv.util.setting;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.util.UUID;
@@ -24,6 +25,15 @@ public interface PlayerToggle {
*/
boolean is(@NotNull UUID uuid);
/**
* Get the state of the toggle for a particular {@link Player},
* accounting for permissions required to use the feature.
*
* @param player the player
* @return true if the setting is enabled and the player has the required permissions
*/
boolean is(@NotNull Player player);
/**
* Set the state of the toggle for a particular player ID.
*
@@ -1,5 +1,7 @@
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;
@@ -19,8 +21,20 @@ import java.util.UUID;
public final class PlayerToggles {
private static final Map<String, PlayerToggle> TOGGLES = new HashMap<>();
private static final PlayerToggle ANY = add(new MemoryToggle("AnyContainer"));
private static final PlayerToggle SILENT = add(new MemoryToggle("SilentContainer"));
private static final PlayerToggle ANY = add(new MemoryToggle("AnyContainer") {
@Override
public boolean is(@NotNull 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) {
return is(player.getUniqueId())
&& (Permissions.CONTAINER_SILENT.hasPermission(player) || Permissions.CONTAINER_SILENT_USE.hasPermission(player));
}
});
/**
* Get the AnyContainer toggle.
@@ -72,7 +86,7 @@ public final class PlayerToggles {
throw new IllegalStateException("Cannot create instance of utility class.");
}
private static class MemoryToggle implements PlayerToggle {
private static abstract class MemoryToggle implements PlayerToggle {
private final @NotNull Set<UUID> enabled;
private final @NotNull String name;
+47
View File
@@ -0,0 +1,47 @@
plugins {
`java-library`
alias(libs.plugins.paperweight) apply false
alias(libs.plugins.shadow) apply false
alias(libs.plugins.errorprone.gradle)
}
repositories {
maven("https://repo.papermc.io/repository/maven-public/")
}
// Allow submodules to target higher Java release versions.
java.disableAutoTargetJvm()
subprojects {
apply(plugin = "java-library")
apply(plugin = rootProject.libs.plugins.errorprone.gradle.get().pluginId)
repositories {
mavenCentral()
}
dependencies {
compileOnly(rootProject.libs.jspecify)
compileOnly(rootProject.libs.annotations)
errorprone(rootProject.libs.errorprone.core)
}
tasks {
withType<JavaCompile>().configureEach {
options.encoding = Charsets.UTF_8.name()
}
withType<Javadoc>().configureEach {
options.encoding = Charsets.UTF_8.name()
}
}
}
// Task to delete ./dist where final files are output.
tasks.register("cleanDist") {
delete("dist")
}
tasks.clean {
// Also delete distribution folder when cleaning.
dependsOn(tasks.named("cleanDist"))
}
+8
View File
@@ -0,0 +1,8 @@
plugins {
`kotlin-dsl`
}
repositories {
gradlePluginPortal()
mavenCentral()
}
View File
Whitespace-only changes.
@@ -0,0 +1,107 @@
package com.github.jikoo.openinv
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.provider.Property
import org.gradle.api.provider.ValueSource
import org.gradle.api.provider.ValueSourceParameters
import org.gradle.process.ExecOperations
import java.io.File
import java.net.URI
import java.nio.file.Files
import javax.inject.Inject
abstract class BuildToolsValueSource : ValueSource<File, BuildToolsValueSource.Parameters> {
@get:Inject
abstract val exec: ExecOperations
interface Parameters : ValueSourceParameters {
val installDir: DirectoryProperty
val workingDir: DirectoryProperty
val spigotVersion: Property<String>
val spigotRevision: Property<String>
val ignoreCached: Property<Boolean>
val javaHome: DirectoryProperty
val javaExecutable: Property<String>
}
override fun obtain(): File {
val version = parameters.spigotVersion.get()
val revision = parameters.spigotRevision.get()
val spigotLocation = parameters.installDir.get().asFile.resolve("spigot-$version.jar")
// If Spigot is already installed, don't reinstall.
if (!parameters.ignoreCached.get() && spigotLocation.exists()) {
println("Skipping Spigot installation, $version is present")
return spigotLocation
}
val buildTools = installBuildTools(parameters.workingDir.get().asFile)
println("Installing Spigot $version (rev $revision)")
exec.javaexec {
environment["JAVA_HOME"] = parameters.javaHome.get()
executable = parameters.javaExecutable.get()
workingDir = buildTools.parentFile
classpath(buildTools)
args = listOf("--nogui", "--rev", revision)
}.rethrowFailure()
val spigotBuild = buildTools.parentFile.resolve("Spigot/Spigot-Server/target/spigot-$version.jar")
if (!spigotBuild.exists()) {
throw IllegalStateException(
"Spigot build completed, but $spigotBuild does not exist! Did BuildTools change output location?"
)
}
spigotBuild.copyTo(spigotLocation, overwrite = true)
// Mark work for delete.
cleanUp(buildTools.parentFile)
if (!spigotLocation.exists()) {
throw IllegalStateException(
"Failed to install Spigot $version from $revision. Does the revision point to a different version?"
)
}
return spigotLocation
}
private fun installBuildTools(workingDir: File): File {
val buildTools = workingDir.resolve("BuildTools.jar")
if (buildTools.exists()) {
return buildTools
}
workingDir.mkdirs()
val buildToolsUrl =
"https://hub.spigotmc.org/jenkins/job/BuildTools/lastSuccessfulBuild/artifact/target/BuildTools.jar"
println("Downloading $buildToolsUrl")
val stream = URI.create(buildToolsUrl).toURL().openStream()
Files.copy(stream, buildTools.toPath())
stream.close()
return buildTools
}
private fun cleanUp(dir: File) {
dir.deleteOnExit()
if (!dir.isDirectory) {
return
}
dir.listFiles()?.forEach {
if (it.isDirectory) {
cleanUp(it)
} else {
it.deleteOnExit()
}
}
}
}
@@ -0,0 +1,19 @@
package com.github.jikoo.openinv
import org.gradle.api.model.ObjectFactory
import org.gradle.jvm.toolchain.JavaToolchainSpec
abstract class SpigotDependencyExtension(
objects: ObjectFactory
) {
val version = objects.property(String::class.java)
val revision = objects.property(String::class.java)
.convention(version.map {
it.replace("-R\\d+\\.\\d+-SNAPSHOT".toRegex(), "")
})
val classifier = objects.property(String::class.java).convention("")
val java = objects.property(JavaToolchainSpec::class.java)
val ignoreCached = objects.property(Boolean::class.java).convention(false)
}
@@ -0,0 +1,50 @@
package com.github.jikoo.openinv
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.plugins.JavaPluginExtension
import org.gradle.jvm.toolchain.JavaToolchainService
import java.io.File
import javax.inject.Inject
abstract class SpigotSetup : Plugin<Project> {
@get:Inject
abstract val javaToolchainService: JavaToolchainService
override fun apply(target: Project) {
target.plugins.apply("java")
// Set up extension for configuring Spigot dependency.
val spigotExt = target.dependencies.extensions.findByType(SpigotDependencyExtension::class.java)
?: target.dependencies.extensions.create(
"spigot",
SpigotDependencyExtension::class.java,
target.objects
)
target.afterEvaluate {
// Get Java requirements, defaulting to version used for compilation.
spigotExt.java.convention(target.extensions.getByType(JavaPluginExtension::class.java).toolchain)
val launcher = javaToolchainService.launcherFor(spigotExt.java.get()).get()
// Install Spigot with BuildTools.
val spigot: File = target.providers.of(BuildToolsValueSource::class.java) {
parameters {
installDir.set(target.gradle.gradleUserHomeDir.resolve("caches/spigot"))
workingDir.set(target.layout.buildDirectory.dir("tmp/buildtools"))
spigotVersion.set(spigotExt.version)
spigotRevision.set(spigotExt.revision)
ignoreCached.set(spigotExt.ignoreCached)
javaHome.set(launcher.metadata.installationPath)
javaExecutable.set(launcher.executablePath.asFile.path)
}
}.get()
// Add Spigot dependency.
val dependency = target.dependencies.create(files(spigot))
target.dependencies.add("compileOnly", dependency)
}
}
}
+20
View File
@@ -0,0 +1,20 @@
repositories {
maven("https://hub.spigotmc.org/nexus/content/groups/public/")
}
dependencies {
compileOnly(libs.spigotapi)
}
java {
toolchain.languageVersion = JavaLanguageVersion.of(21)
}
tasks.withType<JavaCompile>().configureEach {
options.release = 21
}
dependencies {
implementation(project(":openinvapi"))
compileOnly(libs.slf4j.api)
}
-30
View File
@@ -1,30 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.lishid</groupId>
<artifactId>openinvparent</artifactId>
<version>5.1.4</version>
</parent>
<artifactId>openinvcommon</artifactId>
<dependencies>
<dependency>
<artifactId>annotations</artifactId>
<groupId>org.jetbrains</groupId>
</dependency>
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
</dependency>
<dependency>
<artifactId>openinvapi</artifactId>
<groupId>com.lishid</groupId>
</dependency>
</dependencies>
</project>
@@ -8,41 +8,15 @@ 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;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Method;
public abstract class AnySilentContainerBase implements IAnySilentContainer {
private static final @Nullable Method BLOCK_GET_STATE_BOOLEAN;
static {
@Nullable Method getState;
try {
//noinspection JavaReflectionMemberAccess
getState = Block.class.getMethod("getState", boolean.class);
} catch (NoSuchMethodException e) {
getState = null;
}
BLOCK_GET_STATE_BOOLEAN = getState;
}
private static BlockState getBlockState(Block block) {
// Paper: Get state without snapshotting.
if (BLOCK_GET_STATE_BOOLEAN != null) {
try {
return (BlockState) BLOCK_GET_STATE_BOOLEAN.invoke(block, false);
} catch (ReflectiveOperationException ignored) {
// If we encounter an issue, fall through to regular snapshotting method.
}
}
return block.getState();
}
@Override
public boolean isAnyContainerNeeded(@NotNull Block block) {
BlockState blockState = getBlockState(block);
BlockState blockState = getState(block);
// Barrels do not require AnyContainer.
if (blockState instanceof Barrel) {
@@ -100,7 +74,16 @@ public abstract class AnySilentContainerBase implements IAnySilentContainer {
@Override
public boolean isAnySilentContainer(@NotNull Block block) {
return isAnySilentContainer(getBlockState(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,48 +0,0 @@
package com.lishid.openinv.internal;
import com.lishid.openinv.util.lang.LanguageManager;
import com.lishid.openinv.util.lang.Replacement;
import org.bukkit.entity.HumanEntity;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Objects;
public enum InventoryViewTitle {
PLAYER_INVENTORY("container.player", "'s Inventory"),
ENDER_CHEST("container.enderchest", "'s Ender Chest");
private final String localizationKey;
private final String defaultSuffix;
InventoryViewTitle(String localizationKey, String defaultSuffix) {
this.localizationKey = localizationKey;
this.defaultSuffix = defaultSuffix;
}
public @NotNull String getTitle(
@NotNull LanguageManager lang,
@NotNull Player viewer,
@NotNull ISpecialInventory inventory) {
HumanEntity owner = inventory.getPlayer();
String localTitle = lang.getLocalizedMessage(
viewer,
localizationKey,
new Replacement("%player%", owner.getName()));
return Objects.requireNonNullElseGet(localTitle, () -> owner.getName() + defaultSuffix);
}
public static @Nullable InventoryViewTitle of(@NotNull ISpecialInventory inventory) {
if (inventory instanceof ISpecialPlayerInventory) {
return PLAYER_INVENTORY;
} else if (inventory instanceof ISpecialEnderChest) {
return ENDER_CHEST;
} else {
return null;
}
}
}
@@ -24,31 +24,31 @@ import org.jetbrains.annotations.Nullable;
public interface PlayerManager {
/**
* Loads a Player for an OfflinePlayer.
* </p>
* This method is potentially blocking, and should not be called on the main thread.
*
* @param offline the OfflinePlayer
* @return the Player loaded
*/
@Nullable Player loadPlayer(@NotNull OfflinePlayer offline);
/**
* Loads a Player for an OfflinePlayer.
* </p>
* This method is potentially blocking, and should not be called on the main thread.
*
* @param offline the OfflinePlayer
* @return the Player loaded
*/
@Nullable Player loadPlayer(@NotNull 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);
/**
* Creates a new Player from an existing one that will function slightly better offline.
*
* @return the Player
*/
@NotNull Player inject(@NotNull Player player);
/**
* Opens an ISpecialInventory for a Player.
*
* @param player the Player opening the ISpecialInventory
* @param inventory the Inventory
*`
* @return the InventoryView opened
*/
@Nullable InventoryView openInventory(@NotNull Player player, @NotNull ISpecialInventory inventory, boolean viewOnly);
/**
* Opens an ISpecialInventory for a Player.
*
* @param player the Player opening the ISpecialInventory
* @param inventory the Inventory
*
* @return the InventoryView opened
*/
@Nullable InventoryView openInventory(@NotNull Player player, @NotNull ISpecialInventory inventory, boolean viewOnly);
}
@@ -0,0 +1,134 @@
package com.lishid.openinv.util;
import org.slf4j.Marker;
import org.slf4j.helpers.LegacyAbstractLogger;
import org.slf4j.helpers.MessageFormatter;
import org.slf4j.helpers.NormalizedParameters;
import org.slf4j.spi.LocationAwareLogger;
import java.util.logging.Level;
import java.util.logging.LogRecord;
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.
*/
public class JulLoggerAdapter extends LegacyAbstractLogger implements LocationAwareLogger {
private final Logger wrapped;
public JulLoggerAdapter(Logger wrapped) {
this.wrapped = wrapped;
this.name = wrapped.getName();
}
@Override
protected String getFullyQualifiedCallerName() {
return JulLoggerAdapter.class.getName();
}
@Override
protected void handleNormalizedLoggingCall(
org.slf4j.event.Level slf4jLevel,
Marker marker,
String msg,
Object[] args,
Throwable thrown
) {
Level level = fromSlf4jLevel(slf4jLevel);
if (wrapped.isLoggable(level)) {
normalizedLog(getFullyQualifiedCallerName(), level, msg, args, thrown);
}
}
private void normalizedLog(String fqcn, Level level, String msg, Object[] args, Throwable thrown) {
String formatted = MessageFormatter.basicArrayFormat(msg, args);
LogRecord logRecord = new LogRecord(level, formatted);
logRecord.setLoggerName(getName());
logRecord.setThrown(thrown);
addSource(fqcn, logRecord);
wrapped.log(logRecord);
}
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());
}
}
private boolean isIgnored(String className, String fqcn) {
if (className.equals(fqcn)) {
return true;
}
// Ignore slf4j classes - they shouldn't be the source.
if (className.startsWith("org.slf4j.")) {
return true;
}
return className.equals(getFullyQualifiedCallerName());
}
@Override
public void log(Marker marker, String callerFqn, int levelInt, String msg, Object[] args, Throwable thrown) {
Level level = fromSlf4jLevel(org.slf4j.event.Level.intToLevel(levelInt));
if (!wrapped.isLoggable(level)) {
return;
}
NormalizedParameters params = NormalizedParameters.normalize(msg, args, thrown);
normalizedLog(callerFqn, level, params.getMessage(), params.getArguments(), params.getThrowable());
}
private Level fromSlf4jLevel(org.slf4j.event.Level level) {
return switch (level) {
case TRACE -> Level.FINEST;
case DEBUG -> Level.FINE;
case INFO -> Level.INFO;
case WARN -> Level.WARNING;
case ERROR -> Level.SEVERE;
};
}
@Override
public boolean isTraceEnabled() {
return wrapped.isLoggable(Level.FINEST);
}
@Override
public boolean isDebugEnabled() {
return wrapped.isLoggable(Level.FINE);
}
@Override
public boolean isInfoEnabled() {
return wrapped.isLoggable(Level.INFO);
}
@Override
public boolean isWarnEnabled() {
return wrapped.isLoggable(Level.WARNING);
}
@Override
public boolean isErrorEnabled() {
return wrapped.isLoggable(Level.SEVERE);
}
}
@@ -1,60 +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;
import org.bukkit.permissions.Permissible;
import org.jetbrains.annotations.NotNull;
public enum Permissions {
INVENTORY_OPEN_SELF("inventory.open.self"),
INVENTORY_OPEN_OTHER("inventory.open.other"),
INVENTORY_EDIT_SELF("inventory.edit.self"),
INVENTORY_EDIT_OTHER("inventory.edit.other"),
INVENTORY_SLOT_HEAD_ANY("inventory.slot.head.any"),
INVENTORY_SLOT_CHEST_ANY("inventory.slot.chest.any"),
INVENTORY_SLOT_LEGS_ANY("inventory.slot.legs.any"),
INVENTORY_SLOT_FEET_ANY("inventory.slot.feet.any"),
INVENTORY_SLOT_DROP("inventory.slot.drop"),
ENDERCHEST_OPEN_SELF("enderchest.open.self"),
ENDERCHEST_OPEN_OTHER("enderchest.open.other"),
ENDERCHEST_EDIT_SELF("enderchest.edit.self"),
ENDERCHEST_EDIT_OTHER("enderchest.edit.other"),
ACCESS_CROSSWORLD("access.crossworld"),
ACCESS_OFFLINE("access.offline"),
ACCESS_ONLINE("access.online"),
SPECTATE_CLICK("spectate.click"),
CONTAINER_ANY("container.any"),
CONTAINER_SILENT("container.silent"),
SEARCH_INVENTORY("search.inventory"),
SEARCH_CONTAINER("search.container");
private final String permission;
Permissions(String permission) {
this.permission = "openinv." + permission;
}
public boolean hasPermission(@NotNull Permissible permissible) {
return permissible.hasPermission(permission);
}
}
@@ -25,54 +25,54 @@ import java.lang.reflect.Field;
*/
public final class ReflectionHelper {
/**
* Grab an {@link Object} stored in a {@link Field} of another {@code Object}.
*
* <p>This casts the field to the correct class. Any issues will result in a {@code null} return value.
*
* @param fieldType the {@link Class} of {@code Object} stored in the {@code Field}
* @param holder the containing {@code Object}
* @param <T> the type of stored {@code Object}
* @return the first matching {@code Object} or {@code null} if none match
*/
public static <T> @Nullable T grabObjectByType(final Object holder, final Class<T> fieldType) {
Field field = grabFieldByType(holder.getClass(), fieldType);
/**
* Grab an {@link Object} stored in a {@link Field} of another {@code Object}.
*
* <p>This casts the field to the correct class. Any issues will result in a {@code null} return value.
*
* @param fieldType the {@link Class} of {@code Object} stored in the {@code Field}
* @param holder the containing {@code Object}
* @param <T> the type of stored {@code Object}
* @return the first matching {@code Object} or {@code null} if none match
*/
public static <T> @Nullable T grabObjectByType(final Object holder, final Class<T> fieldType) {
Field field = grabFieldByType(holder.getClass(), fieldType);
if (field != null) {
try {
return fieldType.cast(field.get(holder));
} catch (IllegalAccessException ignored) {
// Ignore issues obtaining field
}
}
return null;
if (field != null) {
try {
return fieldType.cast(field.get(holder));
} catch (IllegalAccessException ignored) {
// Ignore issues obtaining field
}
}
/**
* Grab a {@link Field} of an {@link Object}
*
* @param fieldType the {@link Class} of the object
* @param holderType the containing {@code Class}
* @return the first matching object or {@code null} if none match
*/
public static @Nullable Field grabFieldByType(Class<?> holderType, Class<?> fieldType) {
for (Field field : holderType.getDeclaredFields()) {
if (fieldType.isAssignableFrom(field.getType())) {
field.setAccessible(true);
return field;
}
}
return null;
}
if (holderType.getSuperclass() != null) {
return grabFieldByType(fieldType, holderType.getSuperclass());
}
return null;
/**
* Grab a {@link Field} of an {@link Object}
*
* @param fieldType the {@link Class} of the object
* @param holderType the containing {@code Class}
* @return the first matching object or {@code null} if none match
*/
public static @Nullable Field grabFieldByType(Class<?> holderType, Class<?> fieldType) {
for (Field field : holderType.getDeclaredFields()) {
if (fieldType.isAssignableFrom(field.getType())) {
field.setAccessible(true);
return field;
}
}
private ReflectionHelper() {
throw new IllegalStateException("Cannot create instance of utility class.");
if (holderType.getSuperclass() != null) {
return grabFieldByType(fieldType, holderType.getSuperclass());
}
return null;
}
private ReflectionHelper() {
throw new IllegalStateException("Cannot create instance of utility class.");
}
}
@@ -49,271 +49,278 @@ import java.util.logging.Level;
*/
public class LanguageManager {
private final Plugin plugin;
private final File folder;
private final String defaultLocale;
private final Map<String, YamlConfiguration> locales;
private final Plugin plugin;
private final File folder;
private final String defaultLocale;
private final Map<String, YamlConfiguration> locales;
public LanguageManager(@NotNull Plugin plugin, @NotNull String defaultLocale) {
this.plugin = plugin;
this.defaultLocale = defaultLocale;
this.locales = new HashMap<>();
this.folder = new File(plugin.getDataFolder(), "locale");
public LanguageManager(@NotNull Plugin plugin, @NotNull String defaultLocale) {
this.plugin = plugin;
this.defaultLocale = defaultLocale;
this.locales = new HashMap<>();
this.folder = new File(plugin.getDataFolder(), "locale");
if (!folder.exists() && !folder.mkdirs()) {
plugin.getLogger().warning(() -> "Unable to create " + folder.getPath() + "! Languages may not be editable.");
}
reload();
if (!folder.exists() && !folder.mkdirs()) {
plugin.getLogger().warning(() -> "Unable to create " + folder.getPath() + "! Languages may not be editable.");
}
public void reload() {
this.locales.clear();
getOrLoadLocale(defaultLocale);
reload();
}
public void reload() {
this.locales.clear();
getOrLoadLocale(defaultLocale);
}
private @NotNull YamlConfiguration getOrLoadLocale(@NotNull String locale) {
YamlConfiguration loaded = locales.get(locale);
if (loaded != null) {
return loaded;
}
private @NotNull YamlConfiguration getOrLoadLocale(@NotNull String locale) {
YamlConfiguration loaded = locales.get(locale);
if (loaded != null) {
return loaded;
}
LangLocation lang = bestMatch(locale, null);
LangLocation lang = bestMatch(locale, null);
// If a parent was a better match, check if it is already loaded.
if (!locale.equals(lang.locale)) {
loaded = locales.get(lang.locale);
if (loaded != null) {
locales.put(locale, loaded);
return loaded;
}
}
// Load locale config from disk and bundled locale defaults.
YamlConfiguration localeConfig = loadLocale(lang);
// If the locale is not the default locale, also handle any missing translations from the default locale.
if (!locale.equals(defaultLocale)) {
addTranslationFallthrough(lang, localeConfig);
if (plugin.getConfig().getBoolean("settings.secret.warn-about-guess-section", true)
&& localeConfig.isConfigurationSection("guess")) {
// Warn that guess section exists. This should run once per language per server restart
// when accessed by a user to hint to server owners that they can make UX improvements.
plugin.getLogger().info(() -> "[LanguageManager] Missing translations from " + lang.locale + ".yml! Check the guess section!");
}
}
locales.put(locale, localeConfig);
locales.put(lang.locale, localeConfig);
return localeConfig;
// If a parent was a better match, check if it is already loaded.
if (!locale.equals(lang.locale)) {
loaded = locales.get(lang.locale);
if (loaded != null) {
locales.put(locale, loaded);
return loaded;
}
}
private @NotNull LangLocation bestMatch(@NotNull String locale, @Nullable LangLocation initial) {
File file = new File(folder, locale + ".yml");
InputStream bundled = plugin.getResource("locale/" + locale + ".yml");
// Load locale config from disk and bundled locale defaults.
YamlConfiguration localeConfig = loadLocale(lang);
if (file.exists() || bundled != null) {
return new LangLocation(locale, file, bundled);
}
// If the locale is not the default locale, also handle any missing translations from the default locale.
if (!locale.equals(defaultLocale)) {
addTranslationFallthrough(lang, localeConfig);
if (initial == null) {
initial = new LangLocation(locale, file, null);
}
int lastSeparator = locale.lastIndexOf('_');
// Must be at least some content before separator.
if (lastSeparator < 1) {
return initial;
}
return bestMatch(locale.substring(0, lastSeparator), initial);
if (plugin.getConfig().getBoolean("settings.secret.warn-about-guess-section", true)
&& localeConfig.isConfigurationSection("guess")) {
// Warn that guess section exists. This should run once per language per server restart
// when accessed by a user to hint to server owners that they can make UX improvements.
plugin.getLogger().info(() -> "[LanguageManager] Missing translations from " + lang.locale + ".yml! Check the guess section!");
}
}
private @NotNull YamlConfiguration loadLocale(@NotNull LangLocation lang) {
YamlConfiguration localeConfigDefaults;
if (lang.bundled == null) {
localeConfigDefaults = new YamlConfiguration();
} else {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(lang.bundled, StandardCharsets.UTF_8))) {
localeConfigDefaults = YamlConfiguration.loadConfiguration(reader);
} catch (IOException e) {
plugin.getLogger().log(Level.WARNING, e, () -> "[LanguageManager] Unable to load resource " + lang.locale + ".yml");
localeConfigDefaults = new YamlConfiguration();
}
}
locales.put(locale, localeConfig);
locales.put(lang.locale, localeConfig);
if (!lang.file.exists()) {
// If the file does not exist on disk, save bundled defaults.
try {
localeConfigDefaults.save(lang.file);
} catch (IOException e) {
plugin.getLogger().log(Level.WARNING, e, () -> "[LanguageManager] Unable to save resource " + lang.locale + ".yml");
}
// Return loaded bundled locale.
return localeConfigDefaults;
}
return localeConfig;
}
// If the file does exist on disk, load it.
YamlConfiguration localeConfig = YamlConfiguration.loadConfiguration(lang.file);
// Check for missing translations from the bundled file.
List<String> newKeys = getMissingKeys(localeConfigDefaults, localeConfig::isSet);
private @NotNull LangLocation bestMatch(@NotNull String locale, @Nullable LangLocation initial) {
File file = new File(folder, locale + ".yml");
InputStream bundled = plugin.getResource("locale/" + locale + ".yml");
if (newKeys.isEmpty()) {
return localeConfig;
}
// Get guess section for missing keys.
ConfigurationSection guess = localeConfig.getConfigurationSection("guess");
for (String newKey : newKeys) {
// Set all missing keys to defaults.
localeConfig.set(newKey, localeConfigDefaults.get(newKey));
// Delete relevant guess keys in case this is a new translation.
if (guess != null) {
guess.set(newKey, null);
}
}
// If guess section is empty, delete it.
if (guess != null && guess.getKeys(false).isEmpty()) {
localeConfig.set("guess", null);
}
plugin.getLogger().info(() -> "[LanguageManager] Added new translation keys to " + lang.locale + ".yml: " + String.join(", ", newKeys));
// Write new keys to disk.
try {
localeConfig.save(lang.file);
} catch (IOException e) {
plugin.getLogger().log(Level.WARNING, e, () -> "[LanguageManager] Unable to save resource " + lang.locale + ".yml");
}
return localeConfig;
if (file.exists() || bundled != null) {
return new LangLocation(locale, file, bundled);
}
private void addTranslationFallthrough(@NotNull LangLocation location, @NotNull 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.
List<String> missingKeys = getMissingKeys(
defaultLocaleConfig,
key -> localeConfig.isSet(key) || localeConfig.isSet("guess." + key));
if (!missingKeys.isEmpty()) {
// Set up guess section for missing keys.
for (String key : missingKeys) {
localeConfig.set("guess." + key, defaultLocaleConfig.get(key));
}
// Write modified guess section to disk.
try {
localeConfig.save(location.file);
} catch (IOException e) {
plugin.getLogger().log(Level.WARNING, e, () -> "[LanguageManager] Unable to save resource " + location.locale + ".yml");
}
}
// Fall through to default locale.
localeConfig.setDefaults(defaultLocaleConfig);
if (initial == null) {
initial = new LangLocation(locale, file, null);
}
private @NotNull List<String> getMissingKeys(
@NotNull Configuration configurationDefault,
@NotNull Predicate<String> nodeSetPredicate) {
List<String> missingKeys = new ArrayList<>();
for (String key : configurationDefault.getKeys(true)) {
if (!configurationDefault.isConfigurationSection(key) && !nodeSetPredicate.test(key)) {
// Missing keys are non-section keys that fail the predicate.
missingKeys.add(key);
}
}
return missingKeys;
int lastSeparator = locale.lastIndexOf('_');
// Must be at least some content before separator.
if (lastSeparator < 1) {
return initial;
}
public @Nullable String getValue(@NotNull String key, @Nullable String locale) {
String value = getOrLoadLocale(locale == null ? defaultLocale : locale.toLowerCase(Locale.ENGLISH)).getString(key);
if (value == null || value.isBlank()) {
return null;
}
return bestMatch(locale.substring(0, lastSeparator), initial);
}
value = ChatColor.translateAlternateColorCodes('&', value);
return value;
private @NotNull YamlConfiguration loadLocale(@NotNull LangLocation lang) {
YamlConfiguration localeConfigDefaults;
if (lang.bundled == null) {
localeConfigDefaults = new YamlConfiguration();
} else {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(lang.bundled, StandardCharsets.UTF_8))) {
localeConfigDefaults = YamlConfiguration.loadConfiguration(reader);
} catch (IOException e) {
plugin.getLogger().log(Level.WARNING, e, () -> "[LanguageManager] Unable to load resource " + lang.locale + ".yml");
localeConfigDefaults = new YamlConfiguration();
}
}
public @Nullable String getValue(@NotNull String key, @Nullable String locale, Replacement @NotNull ... replacements) {
String value = getValue(key, locale);
if (value == null) {
return null;
}
for (Replacement replacement : replacements) {
value = value.replace(replacement.placeholder(), replacement.value());
}
return value;
if (!lang.file.exists()) {
// If the file does not exist on disk, save bundled defaults.
try {
localeConfigDefaults.save(lang.file);
} catch (IOException e) {
plugin.getLogger().log(Level.WARNING, e, () -> "[LanguageManager] Unable to save resource " + lang.locale + ".yml");
}
// Return loaded bundled locale.
return localeConfigDefaults;
}
public @Nullable String getLocalizedMessage(@NotNull CommandSender sender, @NotNull String key) {
return getValue(key, getLocale(sender));
// If the file does exist on disk, load it.
YamlConfiguration localeConfig = YamlConfiguration.loadConfiguration(lang.file);
// Check for missing translations from the bundled file.
List<String> newKeys = getMissingKeys(localeConfigDefaults, localeConfig::isSet);
if (newKeys.isEmpty()) {
return localeConfig;
}
public @Nullable String getLocalizedMessage(
@NotNull CommandSender sender,
@NotNull String key,
Replacement @NotNull ... replacements) {
return getValue(key, getLocale(sender), replacements);
// Get guess section for missing keys.
ConfigurationSection guess = localeConfig.getConfigurationSection("guess");
for (String newKey : newKeys) {
// Set all missing keys to defaults.
localeConfig.set(newKey, localeConfigDefaults.get(newKey));
// Delete relevant guess keys in case this is a new translation.
if (guess != null) {
guess.set(newKey, null);
}
}
private @NotNull String getLocale(@NotNull CommandSender sender) {
if (sender instanceof Player) {
return ((Player) sender).getLocale();
} else {
return plugin.getConfig().getString("settings.locale", "en");
}
// If guess section is empty, delete it.
if (guess != null && guess.getKeys(false).isEmpty()) {
localeConfig.set("guess", null);
}
public void sendMessage(@NotNull CommandSender sender, @NotNull String key) {
String message = getLocalizedMessage(sender, key);
plugin.getLogger().info(() -> "[LanguageManager] Added new translation keys to " + lang.locale + ".yml: " + String.join(", ", newKeys));
if (message != null && !message.isEmpty()) {
sender.sendMessage(message);
}
// Write new keys to disk.
try {
localeConfig.save(lang.file);
} catch (IOException e) {
plugin.getLogger().log(Level.WARNING, e, () -> "[LanguageManager] Unable to save resource " + lang.locale + ".yml");
}
public void sendMessage(@NotNull CommandSender sender, @NotNull String key, Replacement @NotNull... replacements) {
String message = getLocalizedMessage(sender, key, replacements);
return localeConfig;
}
if (message != null && !message.isEmpty()) {
sender.sendMessage(message);
}
private void addTranslationFallthrough(@NotNull LangLocation location, @NotNull 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.
List<String> missingKeys = getMissingKeys(
defaultLocaleConfig,
key -> localeConfig.isSet(key) || localeConfig.isSet("guess." + key)
);
if (!missingKeys.isEmpty()) {
// Set up guess section for missing keys.
for (String key : missingKeys) {
localeConfig.set("guess." + key, defaultLocaleConfig.get(key));
}
// Write modified guess section to disk.
try {
localeConfig.save(location.file);
} catch (IOException e) {
plugin.getLogger().log(Level.WARNING, e, () -> "[LanguageManager] Unable to save resource " + location.locale + ".yml");
}
}
public void sendSystemMessage(@NotNull Player player, @NotNull String key) {
String message = getLocalizedMessage(player, key);
// Fall through to default locale.
localeConfig.setDefaults(defaultLocaleConfig);
}
if (message == null) {
return;
}
private @NotNull List<String> getMissingKeys(
@NotNull Configuration configurationDefault,
@NotNull Predicate<String> nodeSetPredicate
) {
List<String> missingKeys = new ArrayList<>();
for (String key : configurationDefault.getKeys(true)) {
if (!configurationDefault.isConfigurationSection(key) && !nodeSetPredicate.test(key)) {
// Missing keys are non-section keys that fail the predicate.
missingKeys.add(key);
}
}
return missingKeys;
}
int newline = message.indexOf('\n');
if (newline != -1) {
// No newlines in action bar chat.
message = message.substring(0, newline);
}
if (message.isEmpty()) {
return;
}
player.spigot().sendMessage(ChatMessageType.ACTION_BAR, TextComponent.fromLegacy(message));
public @Nullable String getValue(@NotNull String key, @Nullable String locale) {
String value = getOrLoadLocale(locale == null ? defaultLocale : locale.toLowerCase(Locale.ENGLISH)).getString(key);
if (value == null || value.isBlank()) {
return null;
}
private record LangLocation(@NotNull String locale, @NotNull File file, @Nullable InputStream bundled) {}
value = ChatColor.translateAlternateColorCodes('&', value);
return value;
}
public @Nullable String getValue(
@NotNull String key,
@Nullable String locale,
Replacement @NotNull ... replacements
) {
String value = getValue(key, locale);
if (value == null) {
return null;
}
for (Replacement replacement : replacements) {
value = value.replace(replacement.placeholder(), replacement.value());
}
return value;
}
public @Nullable String getLocalizedMessage(@NotNull CommandSender sender, @NotNull String key) {
return getValue(key, getLocale(sender));
}
public @Nullable String getLocalizedMessage(
@NotNull CommandSender sender,
@NotNull String key,
Replacement @NotNull ... replacements
) {
return getValue(key, getLocale(sender), replacements);
}
private @NotNull String getLocale(@NotNull CommandSender sender) {
if (sender instanceof Player player) {
return player.getLocale();
} else {
return plugin.getConfig().getString("settings.locale", "en");
}
}
public void sendMessage(@NotNull CommandSender sender, @NotNull String key) {
String message = getLocalizedMessage(sender, key);
if (message != null && !message.isEmpty()) {
sender.sendMessage(message);
}
}
public void sendMessage(@NotNull CommandSender sender, @NotNull String key, Replacement @NotNull ... replacements) {
String message = getLocalizedMessage(sender, key, replacements);
if (message != null && !message.isEmpty()) {
sender.sendMessage(message);
}
}
public void sendSystemMessage(@NotNull Player player, @NotNull String key) {
String message = getLocalizedMessage(player, key);
if (message == null) {
return;
}
int newline = message.indexOf('\n');
if (newline != -1) {
// No newlines in action bar chat.
message = message.substring(0, newline);
}
if (message.isEmpty()) {
return;
}
player.spigot().sendMessage(ChatMessageType.ACTION_BAR, TextComponent.fromLegacy(message));
}
private record LangLocation(@NotNull String locale, @NotNull File file, @Nullable InputStream bundled) {}
}
+9
View File
@@ -0,0 +1,9 @@
# Project meta
group=com.lishid.openinv
version=5.3.2
description=A Bukkit plugin for opening normally-inaccessible inventories.
# Gradle configuration
org.gradle.parallel=true
org.gradle.caching=true
org.gradle.configuration-cache=true
+27
View File
@@ -0,0 +1,27 @@
[versions]
spigotapi = "1.21.5-R0.1-SNAPSHOT"
planarwrappers = "3.3.0"
jspecify = "1.0.0"
annotations = "26.1.0"
paperweight = "2.0.0-beta.21"
shadow = "9.4.1"
folia-scheduler-wrapper = "v0.0.3"
errorprone-core = "2.49.0"
errorprone-gradle = "5.1.0"
slf4j = "2.0.17"
sqlite-jdbc = "3.49.1.0"
[libraries]
spigotapi = { module = "org.spigotmc:spigot-api", version.ref = "spigotapi" }
planarwrappers = { module = "com.github.jikoo:planarwrappers", version.ref = "planarwrappers" }
jspecify = { module = "org.jspecify:jspecify", version.ref = "jspecify" }
annotations = { module = "org.jetbrains:annotations", version.ref = "annotations" }
folia-scheduler-wrapper = { module = "com.github.NahuLD.folia-scheduler-wrapper:folia-scheduler-wrapper", version.ref = "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" }
[plugins]
paperweight = { id = "io.papermc.paperweight.userdev", version.ref = "paperweight" }
shadow = { id = "com.gradleup.shadow", version.ref = "shadow" }
errorprone-gradle = { id = "net.ltgt.errorprone", version.ref = "errorprone-gradle" }
Binary file not shown.
+9
View File
@@ -0,0 +1,9 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.0-bin.zip
networkTimeout=10000
retries=0
retryBackOffMs=500
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Vendored Executable
+248
View File
@@ -0,0 +1,248 @@
#!/bin/sh
#
# 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.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# 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/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"
Vendored
+82
View File
@@ -0,0 +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, 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 Gradle
@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
@@ -0,0 +1,28 @@
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")
}
@@ -0,0 +1,31 @@
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);
}
}
@@ -0,0 +1,59 @@
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;
}
}
@@ -0,0 +1,35 @@
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);
}
}
@@ -0,0 +1,26 @@
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));
}
}
+25
View File
@@ -0,0 +1,25 @@
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")
}
paperweight.paperDevBundle("1.21.11-R0.1-SNAPSHOT")
}
@@ -0,0 +1,29 @@
package com.lishid.openinv.internal.paper1_21_11;
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 java.util.logging.Logger;
public class InternalAccessor extends com.lishid.openinv.internal.paper26_1.InternalAccessor {
public InternalAccessor(@NotNull Logger logger, @NotNull LanguageManager lang) {
super(logger, lang);
}
@Override
public @NotNull ISpecialEnderChest createEnderChest(@NotNull Player player) {
return new OpenEnderChest(player);
}
@Override
public @NotNull ISpecialPlayerInventory createPlayerInventory(@NotNull Player player) {
return new OpenInventory(player);
}
}
@@ -0,0 +1,25 @@
package com.lishid.openinv.internal.paper1_21_11.container;
import com.lishid.openinv.internal.paper1_21_11.container.menu.OpenEnderChestMenu;
import com.lishid.openinv.internal.paper26_1.container.menu.OpenChestMenu;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.player.Player;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.NullMarked;
@NullMarked
public class OpenEnderChest extends com.lishid.openinv.internal.paper26_1.container.OpenEnderChest {
public OpenEnderChest(org.bukkit.entity.Player player) {
super(player);
}
@Override
public @Nullable OpenChestMenu<?> createMenu(Player player, int i, boolean viewOnly) {
if (player instanceof ServerPlayer serverPlayer) {
return new OpenEnderChestMenu(this, serverPlayer, i, viewOnly);
}
return null;
}
}
@@ -0,0 +1,25 @@
package com.lishid.openinv.internal.paper1_21_11.container;
import com.lishid.openinv.internal.paper1_21_11.container.menu.OpenInventoryMenu;
import com.lishid.openinv.internal.paper26_1.container.menu.OpenChestMenu;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.player.Player;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.NullMarked;
@NullMarked
public class OpenInventory extends com.lishid.openinv.internal.paper26_1.container.OpenInventory {
public OpenInventory(org.bukkit.entity.Player bukkitPlayer) {
super(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 null;
}
}
@@ -0,0 +1,68 @@
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 net.minecraft.server.level.ServerPlayer;
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 OpenEnderChestMenu extends OpenSyncMenu<OpenEnderChest> {
public OpenEnderChestMenu(
OpenEnderChest enderChest,
ServerPlayer viewer,
int containerId,
boolean viewOnly
) {
super(getChestMenuType(enderChest.getContainerSize()), containerId, enderChest, viewer, viewOnly);
}
@Override
public ItemStack quickMoveStack(Player player, int index) {
if (viewOnly) {
return ItemStack.EMPTY;
}
// See ChestMenu
Slot slot = this.slots.get(index);
if (slot.isFake() || !slot.hasItem()) {
return ItemStack.EMPTY;
}
ItemStack itemStack = slot.getItem();
ItemStack original = itemStack.copy();
if (index < topSize) {
if (!this.moveItemStackTo(itemStack, topSize, this.slots.size(), true)) {
return ItemStack.EMPTY;
}
} else if (!this.moveItemStackTo(itemStack, 0, topSize, false)) {
return ItemStack.EMPTY;
}
if (itemStack.isEmpty()) {
slot.setByPlayer(ItemStack.EMPTY);
} else {
slot.setChanged();
}
return original;
}
@Override
public void clicked(int i, int j, ClickType clickType, Player player) {
if (viewOnly) {
if (clickType == ClickType.QUICK_CRAFT) {
sendAllDataToRemote();
}
return;
}
super.clicked(i, j, clickType, player);
}
}
@@ -0,0 +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 net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.ClickType;
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 i, int j, ClickType clickType, Player player) {
if (viewOnly) {
if (clickType == ClickType.QUICK_CRAFT) {
sendAllDataToRemote();
}
return;
}
super.clicked(i, j, clickType, player);
}
}
+28
View File
@@ -0,0 +1,28 @@
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")
}
@@ -0,0 +1,31 @@
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);
}
}
@@ -0,0 +1,58 @@
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;
}
}
@@ -0,0 +1,44 @@
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);
}
}
@@ -0,0 +1,100 @@
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;
}
}
}
+68
View File
@@ -0,0 +1,68 @@
import org.gradle.api.internal.attributes.DefaultCompatibilityRuleChain
plugins {
alias(libs.plugins.paperweight)
}
repositories {
maven("https://repo.papermc.io/repository/maven-public/")
}
java {
toolchain.languageVersion.set(JavaLanguageVersion.of(25))
}
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()
}
}
}
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")
}
paperweight.paperDevBundle("26.1.2.build.5-alpha")
}
tasks.reobfJar {
enabled = false
}
configurations.reobf.get().outgoing.artifacts.clear()
@@ -1,19 +1,19 @@
package com.lishid.openinv.internal.v1_21_R3;
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.lishid.openinv.internal.v1_21_R3.container.AnySilentContainer;
import com.lishid.openinv.internal.v1_21_R3.container.OpenEnderChest;
import com.lishid.openinv.internal.v1_21_R3.container.OpenInventory;
import com.lishid.openinv.internal.v1_21_R3.container.Placeholders;
import com.lishid.openinv.internal.v1_21_R3.player.PlayerManager;
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 net.minecraft.world.Container;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.craftbukkit.v1_21_R3.inventory.CraftInventory;
import org.bukkit.craftbukkit.inventory.CraftInventory;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import org.jetbrains.annotations.NotNull;
@@ -24,7 +24,7 @@ import java.util.logging.Logger;
public class InternalAccessor implements Accessor {
private final @NotNull Logger logger;
protected final @NotNull Logger logger;
private final @NotNull PlayerManager manager;
private final @NotNull AnySilentContainer anySilentContainer;
@@ -69,12 +69,11 @@ public class InternalAccessor implements Accessor {
@Override
public void reload(@NotNull ConfigurationSection config) {
ConfigurationSection placeholders = config.getConfigurationSection("placeholders");
if (placeholders != null) {
try {
Placeholders.load(placeholders);
} catch (Exception e) {
logger.log(Level.WARNING, "Caught exception loading placeholder overrides!", e);
}
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);
}
}
@@ -0,0 +1,228 @@
/*
* 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 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 java.lang.reflect.Field;
import java.util.logging.Logger;
public class AnySilentContainer extends AnySilentContainerBase {
private final @NotNull Logger logger;
private final @NotNull LanguageManager lang;
private @Nullable Field serverPlayerGameModeGameType;
public AnySilentContainer(@NotNull Logger logger, @NotNull LanguageManager lang) {
this.logger = logger;
this.lang = lang;
try {
try {
this.serverPlayerGameModeGameType = ServerPlayerGameMode.class.getDeclaredField("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(
@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);
}
}
@@ -1,54 +1,55 @@
package com.lishid.openinv.internal.v1_21_R2.container;
package com.lishid.openinv.internal.paper26_1.container;
import com.lishid.openinv.internal.ISpecialPlayerInventory;
import com.lishid.openinv.internal.InternalOwned;
import com.lishid.openinv.internal.v1_21_R2.container.bukkit.OpenPlayerInventory;
import com.lishid.openinv.internal.v1_21_R2.container.menu.OpenInventoryMenu;
import com.lishid.openinv.internal.v1_21_R2.container.slot.Content;
import com.lishid.openinv.internal.v1_21_R2.container.slot.ContentCrafting;
import com.lishid.openinv.internal.v1_21_R2.container.slot.ContentCraftingResult;
import com.lishid.openinv.internal.v1_21_R2.container.slot.ContentCursor;
import com.lishid.openinv.internal.v1_21_R2.container.slot.ContentDrop;
import com.lishid.openinv.internal.v1_21_R2.container.slot.ContentEquipment;
import com.lishid.openinv.internal.v1_21_R2.container.slot.ContentList;
import com.lishid.openinv.internal.v1_21_R2.container.slot.ContentOffHand;
import com.lishid.openinv.internal.v1_21_R2.container.slot.ContentViewOnly;
import com.lishid.openinv.internal.v1_21_R2.container.slot.SlotViewOnly;
import com.lishid.openinv.internal.v1_21_R2.player.PlayerManager;
import net.minecraft.ChatFormatting;
import 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 it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import net.minecraft.core.NonNullList;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.MutableComponent;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import org.bukkit.Location;
import org.bukkit.craftbukkit.v1_21_R2.entity.CraftHumanEntity;
import org.bukkit.craftbukkit.v1_21_R2.inventory.CraftInventory;
import org.bukkit.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 java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
public class OpenInventory implements Container, InternalOwned<ServerPlayer>, ISpecialPlayerInventory {
public abstract class BaseOpenInventory implements Container, InternalOwned<ServerPlayer>, ISpecialPlayerInventory {
private final List<Content> slots;
protected final List<Content> slots;
private final int size;
private ServerPlayer owner;
protected ServerPlayer owner;
private int maxStackSize = 99;
private CraftInventory bukkitEntity;
protected CraftInventory bukkitEntity;
public List<HumanEntity> transaction = new ArrayList<>();
public OpenInventory(@NotNull org.bukkit.entity.Player bukkitPlayer) {
public BaseOpenInventory(@NotNull org.bukkit.entity.Player bukkitPlayer) {
owner = PlayerManager.getHandle(bukkitPlayer);
// Get total size, rounding up to nearest 9 for client compatibility.
@@ -59,16 +60,15 @@ public class OpenInventory implements Container, InternalOwned<ServerPlayer>, IS
setupSlots();
}
private void setupSlots() {
protected void setupSlots() {
// Top of inventory: Regular contents.
int nextIndex = addMainInventory();
// If inventory is expected size, we can arrange slots to be pretty.
Inventory ownerInv = owner.getInventory();
if (ownerInv.items.size() == 36
&& ownerInv.armor.size() == 4
&& ownerInv.offhand.size() == 1
&& owner.inventoryMenu.getCraftSlots().getContainerSize() == 4) {
if (ownerInv.getNonEquipmentItems().size() == 36
&& owner.inventoryMenu.getCraftSlots().getContainerSize() == 4
&& (Inventory.EQUIPMENT_SLOT_MAPPING.size() == 5 || Inventory.EQUIPMENT_SLOT_MAPPING.size() == 7)) {
// Armor slots: Bottom left.
addArmor(36);
// Off-hand: Below chestplate.
@@ -94,7 +94,7 @@ public class OpenInventory implements Container, InternalOwned<ServerPlayer>, IS
}
private int addMainInventory() {
int listSize = owner.getInventory().items.size();
int listSize = owner.getInventory().getNonEquipmentItems().size();
// Hotbar slots are 0-8. We want those to appear on the bottom of the inventory like a normal player inventory,
// so everything else needs to move up a row.
int hotbarDiff = listSize - 9;
@@ -109,61 +109,50 @@ public class OpenInventory implements Container, InternalOwned<ServerPlayer>, IS
invIndex = localIndex - hotbarDiff;
}
slots.set(localIndex, new ContentList(owner, invIndex, type) {
@Override
public void setHolder(@NotNull ServerPlayer holder) {
items = holder.getInventory().items;
}
});
slots.set(
localIndex,
new ContentList(owner, invIndex, type) {
@Override
public void setHolder(@NotNull ServerPlayer holder) {
items = holder.getInventory().getNonEquipmentItems();
}
}
);
}
return listSize;
}
private int addArmor(int startIndex) {
int listSize = owner.getInventory().armor.size();
for (int i = 0; i < listSize; ++i) {
// Armor slots go bottom to top; boots are slot 0, helmet is slot 3.
// Since we have to display horizontally due to space restrictions,
// making the left side the "top" is more user-friendly.
int armorIndex;
EquipmentSlot slot;
switch (i) {
case 3 -> {
armorIndex = 0;
slot = EquipmentSlot.FEET;
}
case 2 -> {
armorIndex = 1;
slot = EquipmentSlot.LEGS;
}
case 1 -> {
armorIndex = 2;
slot = EquipmentSlot.CHEST;
}
case 0 -> {
armorIndex = 3;
slot = EquipmentSlot.HEAD;
}
default -> {
// In the event that new armor slots are added, they can be placed at the end.
armorIndex = i;
slot = EquipmentSlot.MAINHAND;
}
// Armor slots go bottom to top; boots are first and helmet is last.
// Since we have to display horizontally due to space restrictions,
// making the left side the "top" is more user-friendly.
EquipmentSlot[] sorted = Inventory.EQUIPMENT_SLOT_MAPPING.int2ObjectEntrySet()
.stream()
.sorted(Comparator.comparingInt(Int2ObjectMap.Entry::getIntKey))
.map(Map.Entry::getValue)
.toArray(EquipmentSlot[]::new);
int localIndex = 0;
for (int i = sorted.length - 1; i >= 0; --i) {
// Skip off-hand, handled separately. Also skip non-player slots.
if (sorted[i].getType() != EquipmentSlot.Type.HUMANOID_ARMOR) {
continue;
}
slots.set(startIndex + i, new ContentEquipment(owner, armorIndex, slot));
slots.set(startIndex + localIndex, new ContentEquipment(owner, sorted[i]));
++localIndex;
}
return startIndex + listSize;
return startIndex + localIndex;
}
private int addOffHand(int startIndex) {
int listSize = owner.getInventory().offhand.size();
for (int localIndex = 0; localIndex < listSize; ++localIndex) {
slots.set(startIndex + localIndex, new ContentOffHand(owner, localIndex));
// No off-hand?
if (!Inventory.EQUIPMENT_SLOT_MAPPING.containsValue(EquipmentSlot.OFFHAND)) {
return startIndex;
}
return startIndex + listSize;
slots.set(startIndex, new ContentOffHand(owner));
return startIndex + 1;
}
private int addCrafting(int startIndex, boolean pretty) {
@@ -181,22 +170,27 @@ public class OpenInventory implements Container, InternalOwned<ServerPlayer>, IS
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;
public Slot asSlot(Container container, int slot, int x, int y) {
return new SlotViewOnly(container, slot, x, y) {
@Override
public ItemStack getOrDefault() {
return Placeholders.craftingOutput;
}
};
}
};
}
});
slots.set(startIndex + 11, new ContentCraftingResult(owner));
}
);
slots.set(startIndex + 11, getCraftingResult(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);
}
@@ -205,28 +199,7 @@ public class OpenInventory implements Container, InternalOwned<ServerPlayer>, IS
return slots.get(index).getSlotType();
}
public @NotNull Component getTitle(@Nullable ServerPlayer viewer) {
MutableComponent component = Component.empty();
// Prefix for use with custom bitmap image fonts.
if (owner.equals(viewer)) {
component.append(
Component.translatableWithFallback("openinv.container.inventory.self", "")
.withStyle(style -> style
.withFont(ResourceLocation.parse("openinv:font/inventory"))
.withColor(ChatFormatting.WHITE)));
} else {
component.append(
Component.translatableWithFallback("openinv.container.inventory.other", "")
.withStyle(style -> style
.withFont(ResourceLocation.parse("openinv:font/inventory"))
.withColor(ChatFormatting.WHITE)));
}
// Normal title: "Inventory - OwnerName"
component.append(Component.translatableWithFallback("openinv.container.inventory.prefix", "", owner.getName()))
.append(Component.translatable("container.inventory"))
.append(Component.translatableWithFallback("openinv.container.inventory.suffix", " - %s", owner.getName()));
return component;
}
public abstract Component getTitle(ServerPlayer player, @Nullable OpenChestMenu<?> menu);
@Override
public ServerPlayer getOwnerHandle() {
@@ -251,9 +224,6 @@ public class OpenInventory implements Container, InternalOwned<ServerPlayer>, IS
slots.forEach(slot -> slot.setHolder(newOwner));
}
@Override
public void setPlayerOffline() {}
@Override
public boolean isInUse() {
return !transaction.isEmpty();
@@ -275,22 +245,22 @@ public class OpenInventory implements Container, InternalOwned<ServerPlayer>, IS
}
@Override
public ItemStack getItem(int index) {
public @NotNull ItemStack getItem(int index) {
return slots.get(index).get();
}
@Override
public ItemStack removeItem(int index, int amount) {
public @NotNull ItemStack removeItem(int index, int amount) {
return slots.get(index).removePartial(amount);
}
@Override
public ItemStack removeItemNoUpdate(int index) {
public @NotNull ItemStack removeItemNoUpdate(int index) {
return slots.get(index).remove();
}
@Override
public void setItem(int index, ItemStack itemStack) {
public void setItem(int index, @NotNull ItemStack itemStack) {
slots.get(index).set(itemStack);
}
@@ -305,15 +275,16 @@ public class OpenInventory implements Container, InternalOwned<ServerPlayer>, IS
}
@Override
public void setChanged() {}
public void setChanged() {
}
@Override
public boolean stillValid(Player player) {
public boolean stillValid(@NotNull Player player) {
return true;
}
@Override
public List<ItemStack> getContents() {
public @NotNull List<ItemStack> getContents() {
NonNullList<ItemStack> contents = NonNullList.withSize(getContainerSize(), ItemStack.EMPTY);
for (int i = 0; i < getContainerSize(); ++i) {
contents.set(i, getItem(i));
@@ -322,22 +293,22 @@ public class OpenInventory implements Container, InternalOwned<ServerPlayer>, IS
}
@Override
public void onOpen(CraftHumanEntity viewer) {
public void onOpen(@NotNull CraftHumanEntity viewer) {
transaction.add(viewer);
}
@Override
public void onClose(CraftHumanEntity viewer) {
public void onClose(@NotNull CraftHumanEntity viewer) {
transaction.remove(viewer);
}
@Override
public List<HumanEntity> getViewers() {
public @NotNull List<HumanEntity> getViewers() {
return transaction;
}
@Override
public org.bukkit.entity.Player getOwner() {
public @NotNull org.bukkit.entity.Player getOwner() {
return owner.getBukkitEntity();
}
@@ -354,7 +325,7 @@ public class OpenInventory implements Container, InternalOwned<ServerPlayer>, IS
owner.containerMenu.setCarried(ItemStack.EMPTY);
}
public @Nullable AbstractContainerMenu createMenu(Player player, int i, boolean viewOnly) {
public @Nullable OpenChestMenu<?> createMenu(Player player, int i, boolean viewOnly) {
if (player instanceof ServerPlayer serverPlayer) {
return new OpenInventoryMenu(this, serverPlayer, i, viewOnly);
}
@@ -1,22 +1,23 @@
package com.lishid.openinv.internal.v1_21_R2.container;
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.v1_21_R2.container.menu.OpenEnderChestMenu;
import com.lishid.openinv.internal.v1_21_R2.player.PlayerManager;
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.AbstractContainerMenu;
import net.minecraft.world.inventory.StackedContentsCompatible;
import net.minecraft.world.item.ItemStack;
import org.bukkit.Location;
import org.bukkit.craftbukkit.v1_21_R2.entity.CraftHumanEntity;
import org.bukkit.craftbukkit.v1_21_R2.inventory.CraftInventory;
import org.bukkit.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;
@@ -71,9 +72,6 @@ public class OpenEnderChest implements Container, StackedContentsCompatible, Int
items = activeItems;
}
@Override
public void setPlayerOffline() {}
@Override
public @NotNull org.bukkit.entity.Player getPlayer() {
return owner.getBukkitEntity();
@@ -90,12 +88,12 @@ public class OpenEnderChest implements Container, StackedContentsCompatible, Int
}
@Override
public ItemStack getItem(int index) {
public @NotNull ItemStack getItem(int index) {
return index >= 0 && index < items.size() ? items.get(index) : ItemStack.EMPTY;
}
@Override
public ItemStack removeItem(int index, int amount) {
public @NotNull ItemStack removeItem(int index, int amount) {
ItemStack itemstack = ContainerHelper.removeItem(items, index, amount);
if (!itemstack.isEmpty()) {
@@ -106,12 +104,12 @@ public class OpenEnderChest implements Container, StackedContentsCompatible, Int
}
@Override
public ItemStack removeItemNoUpdate(int index) {
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, ItemStack itemStack) {
public void setItem(int index, @NotNull ItemStack itemStack) {
if (index >= 0 && index < items.size()) {
items.set(index, itemStack);
}
@@ -128,27 +126,27 @@ public class OpenEnderChest implements Container, StackedContentsCompatible, Int
}
@Override
public boolean stillValid(Player player) {
public boolean stillValid(@NotNull Player player) {
return true;
}
@Override
public List<ItemStack> getContents() {
public @NotNull List<ItemStack> getContents() {
return items;
}
@Override
public void onOpen(CraftHumanEntity craftHumanEntity) {
public void onOpen(@NotNull CraftHumanEntity craftHumanEntity) {
transaction.add(craftHumanEntity);
}
@Override
public void onClose(CraftHumanEntity craftHumanEntity) {
public void onClose(@NotNull CraftHumanEntity craftHumanEntity) {
transaction.remove(craftHumanEntity);
}
@Override
public List<HumanEntity> getViewers() {
public @NotNull List<HumanEntity> getViewers() {
return transaction;
}
@@ -174,19 +172,26 @@ public class OpenEnderChest implements Container, StackedContentsCompatible, Int
}
@Override
public void fillStackedContents(StackedItemContents stackedContents) {
public void fillStackedContents(@NotNull StackedItemContents stackedContents) {
for (ItemStack itemstack : items) {
stackedContents.accountStack(itemstack);
}
}
public Component getTitle() {
return Component.translatableWithFallback("openinv.container.enderchest.prefix", "", owner.getName())
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 AbstractContainerMenu createMenu(Player player, int i, boolean viewOnly) {
public @Nullable OpenChestMenu<?> createMenu(Player player, int i, boolean viewOnly) {
if (player instanceof ServerPlayer serverPlayer) {
return new OpenEnderChestMenu(this, serverPlayer, i, viewOnly);
}
@@ -0,0 +1,49 @@
package com.lishid.openinv.internal.paper26_1.container;
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.Identifier;
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(Identifier.parse("openinv:font/inventory")))
.withColor(ChatFormatting.WHITE)));
} else {
component.append(
Component.translatableWithFallback("openinv.container.inventory.other", "")
.withStyle(style -> style
.withFont(new FontDescription.Resource(Identifier.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;
}
}
@@ -1,9 +1,10 @@
package com.lishid.openinv.internal.v1_21_R2.container.bukkit;
package com.lishid.openinv.internal.paper26_1.container.bukkit;
import com.lishid.openinv.internal.ViewOnly;
import net.minecraft.world.Container;
import org.bukkit.Material;
import org.bukkit.craftbukkit.v1_21_R2.inventory.CraftInventory;
import org.bukkit.craftbukkit.inventory.CraftInventory;
import org.bukkit.event.inventory.InventoryType;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -17,8 +18,16 @@ import java.util.ListIterator;
*/
public class OpenDummyInventory extends CraftInventory implements ViewOnly {
public OpenDummyInventory(Container inventory) {
private final InventoryType type;
public OpenDummyInventory(Container inventory, InventoryType type) {
super(inventory);
this.type = type;
}
@Override
public @NotNull InventoryType getType() {
return type;
}
@Override
@@ -53,7 +62,7 @@ public class OpenDummyInventory extends CraftInventory implements ViewOnly {
}
@Override
public ItemStack[] getContents() {
public ItemStack @NotNull [] getContents() {
return new ItemStack[getSize()];
}
@@ -63,7 +72,7 @@ public class OpenDummyInventory extends CraftInventory implements ViewOnly {
}
@Override
public @NotNull ItemStack[] getStorageContents() {
public @NotNull ItemStack @NotNull [] getStorageContents() {
return new ItemStack[getSize()];
}
@@ -100,7 +109,8 @@ public class OpenDummyInventory extends CraftInventory implements ViewOnly {
@SuppressWarnings("NonApiType")
@Override
public @NotNull HashMap<Integer, ItemStack> all(
@NotNull Material material) throws IllegalArgumentException {
@NotNull Material material
) throws IllegalArgumentException {
return new HashMap<>();
}
@@ -0,0 +1,137 @@
package com.lishid.openinv.internal.paper26_1.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;
public class OpenDummyPlayerInventory extends OpenDummyInventory implements PlayerInventory {
public OpenDummyPlayerInventory(Container inventory) {
super(inventory, InventoryType.PLAYER);
}
@Override
public HumanEntity getHolder() {
return (HumanEntity) super.getHolder();
}
@Override
public @NotNull ItemStack @NotNull [] getArmorContents() {
return new ItemStack[4];
}
@Override
public @NotNull ItemStack @NotNull [] getExtraContents() {
return new ItemStack[4];
}
@Override
public @Nullable ItemStack getHelmet() {
return null;
}
@Override
public @Nullable ItemStack getChestplate() {
return null;
}
@Override
public @Nullable ItemStack getLeggings() {
return null;
}
@Override
public @Nullable ItemStack getBoots() {
return null;
}
@Override
public void setItem(@NotNull EquipmentSlot slot, @Nullable ItemStack item) {
}
@Override
public @NotNull ItemStack getItem(@NotNull EquipmentSlot slot) {
return new ItemStack(Material.AIR);
}
@Override
public void setArmorContents(ItemStack @NotNull [] items) {
}
@Override
public void setExtraContents(ItemStack @NotNull [] items) {
}
@Override
public void setHelmet(@Nullable ItemStack helmet) {
}
@Override
public void setChestplate(@Nullable ItemStack chestplate) {
}
@Override
public void setLeggings(@Nullable ItemStack leggings) {
}
@Override
public void setBoots(@Nullable ItemStack boots) {
}
@Override
public @NotNull ItemStack getItemInMainHand() {
return new ItemStack(Material.AIR);
}
@Override
public void setItemInMainHand(@Nullable ItemStack item) {
}
@Override
public @NotNull ItemStack getItemInOffHand() {
return new ItemStack(Material.AIR);
}
@Override
public void setItemInOffHand(@Nullable ItemStack item) {
}
@SuppressWarnings("InlineMeSuggester")
@Deprecated
@Override
public @NotNull ItemStack getItemInHand() {
return new ItemStack(Material.AIR);
}
@Deprecated
@Override
public void setItemInHand(@Nullable ItemStack stack) {
}
@Override
public int getHeldItemSlot() {
return 0;
}
@Override
public void setHeldItemSlot(int slot) {
}
}
@@ -1,12 +1,12 @@
package com.lishid.openinv.internal.v1_21_R2.container.bukkit;
package com.lishid.openinv.internal.paper26_1.container.bukkit;
import com.google.common.base.Preconditions;
import com.lishid.openinv.internal.v1_21_R2.container.OpenInventory;
import com.lishid.openinv.internal.paper26_1.container.BaseOpenInventory;
import net.minecraft.core.NonNullList;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.entity.player.Inventory;
import org.bukkit.craftbukkit.v1_21_R2.inventory.CraftInventory;
import org.bukkit.craftbukkit.v1_21_R2.inventory.CraftItemStack;
import org.bukkit.craftbukkit.inventory.CraftInventory;
import org.bukkit.craftbukkit.inventory.CraftItemStack;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.InventoryType;
import org.bukkit.inventory.ItemStack;
@@ -14,19 +14,21 @@ import org.bukkit.inventory.PlayerInventory;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
public class OpenPlayerInventory extends CraftInventory implements PlayerInventory {
public OpenPlayerInventory(@NotNull OpenInventory inventory) {
public OpenPlayerInventory(@NotNull BaseOpenInventory inventory) {
super(inventory);
}
@Override
public @NotNull OpenInventory getInventory() {
return (OpenInventory) super.getInventory();
public @NotNull BaseOpenInventory getInventory() {
return (BaseOpenInventory) super.getInventory();
}
@Override
public ItemStack[] getContents() {
public ItemStack @NotNull [] getContents() {
return asCraftMirror(getInventory().getOwnerHandle().getInventory().getContents());
}
@@ -46,13 +48,13 @@ public class OpenPlayerInventory extends CraftInventory implements PlayerInvento
}
@Override
public ItemStack[] getStorageContents() {
return asCraftMirror(getInventory().getOwnerHandle().getInventory().items);
public ItemStack @NotNull [] getStorageContents() {
return asCraftMirror(getInventory().getOwnerHandle().getInventory().getNonEquipmentItems());
}
@Override
public void setStorageContents(ItemStack[] items) throws IllegalArgumentException {
NonNullList<net.minecraft.world.item.ItemStack> list = getInventory().getOwnerHandle().getInventory().items;
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);
for (int index = 0; index < items.length; ++index) {
@@ -71,110 +73,116 @@ public class OpenPlayerInventory extends CraftInventory implements PlayerInvento
}
@Override
public @NotNull ItemStack[] getArmorContents() {
return asCraftMirror(getInventory().getOwnerHandle().getInventory().armor);
public @NotNull ItemStack @NotNull [] getArmorContents() {
return asCraftMirror(getInventory().getOwnerHandle().getInventory().getArmorContents());
}
@Override
public void setArmorContents(@Nullable ItemStack[] items) {
NonNullList<net.minecraft.world.item.ItemStack> list = getInventory().getOwnerHandle().getInventory().armor;
int size = list.size();
public void setArmorContents(ItemStack @NotNull [] 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) {
list.set(index, CraftItemStack.asNMSCopy(items[index]));
getInventory().getOwnerHandle().getInventory().equipment.set(
Inventory.EQUIPMENT_SLOTS_SORTED_BY_INDEX[index],
CraftItemStack.asNMSCopy(items[index])
);
}
}
@Override
public @NotNull ItemStack[] getExtraContents() {
return asCraftMirror(getInventory().getOwnerHandle().getInventory().offhand);
public @NotNull ItemStack @NotNull [] getExtraContents() {
return asCraftMirror(List.of(getInventory().getOwnerHandle().getInventory().equipment.get(EquipmentSlot.OFFHAND)));
}
@Override
public void setExtraContents(@Nullable ItemStack[] items) {
NonNullList<net.minecraft.world.item.ItemStack> list = getInventory().getOwnerHandle().getInventory().offhand;
int size = list.size();
Preconditions.checkArgument(items.length <= size, "items.length must be <= %s", size);
for (int index = 0; index < items.length; ++index) {
list.set(index, CraftItemStack.asNMSCopy(items[index]));
public void setExtraContents(ItemStack @NotNull [] 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));
}
}
@Override
public @Nullable ItemStack getHelmet() {
return CraftItemStack.asCraftMirror(getInventory().getOwnerHandle().getInventory()
.getArmor(EquipmentSlot.HEAD.getIndex()));
public @NotNull ItemStack getHelmet() {
return CraftItemStack.asCraftMirror(getInventory().getOwnerHandle().getInventory().equipment
.get(EquipmentSlot.HEAD));
}
@Override
public void setHelmet(@Nullable ItemStack helmet) {
getInventory().getOwnerHandle().getInventory().armor
.set(EquipmentSlot.HEAD.getIndex(), CraftItemStack.asNMSCopy(helmet));
getInventory().getOwnerHandle().getInventory().equipment
.set(EquipmentSlot.HEAD, CraftItemStack.asNMSCopy(helmet));
}
@Override
public @Nullable ItemStack getChestplate() {
return CraftItemStack.asCraftMirror(getInventory().getOwnerHandle().getInventory()
.getArmor(EquipmentSlot.HEAD.getIndex()));
public @NotNull ItemStack getChestplate() {
return CraftItemStack.asCraftMirror(getInventory().getOwnerHandle().getInventory().equipment
.get(EquipmentSlot.CHEST));
}
@Override
public void setChestplate(@Nullable ItemStack chestplate) {
getInventory().getOwnerHandle().getInventory().armor
.set(EquipmentSlot.CHEST.getIndex(), CraftItemStack.asNMSCopy(chestplate));
getInventory().getOwnerHandle().getInventory().equipment
.set(EquipmentSlot.CHEST, CraftItemStack.asNMSCopy(chestplate));
}
@Override
public @Nullable ItemStack getLeggings() {
return CraftItemStack.asCraftMirror(getInventory().getOwnerHandle().getInventory()
.getArmor(EquipmentSlot.LEGS.getIndex()));
public @NotNull ItemStack getLeggings() {
return CraftItemStack.asCraftMirror(getInventory().getOwnerHandle().getInventory().equipment
.get(EquipmentSlot.LEGS));
}
@Override
public void setLeggings(@Nullable ItemStack leggings) {
getInventory().getOwnerHandle().getInventory().armor
.set(EquipmentSlot.LEGS.getIndex(), CraftItemStack.asNMSCopy(leggings));
getInventory().getOwnerHandle().getInventory().equipment
.set(EquipmentSlot.LEGS, CraftItemStack.asNMSCopy(leggings));
}
@Override
public @Nullable ItemStack getBoots() {
return CraftItemStack.asCraftMirror(getInventory().getOwnerHandle().getInventory()
.getArmor(EquipmentSlot.FEET.getIndex()));
public @NotNull ItemStack getBoots() {
return CraftItemStack.asCraftMirror(getInventory().getOwnerHandle().getInventory().equipment
.get(EquipmentSlot.FEET));
}
@Override
public void setBoots(@Nullable ItemStack boots) {
getInventory().getOwnerHandle().getInventory().armor
.set(EquipmentSlot.FEET.getIndex(), CraftItemStack.asNMSCopy(boots));
getInventory().getOwnerHandle().getInventory().equipment
.set(EquipmentSlot.FEET, CraftItemStack.asNMSCopy(boots));
}
@Override
public @NotNull ItemStack getItemInMainHand() {
Inventory internal = getInventory().getOwnerHandle().getInventory();
return CraftItemStack.asCraftMirror(internal.getItem(internal.selected));
return CraftItemStack.asCraftMirror(internal.getSelectedItem());
}
@Override
public void setItemInMainHand(@Nullable ItemStack item) {
Inventory internal = getInventory().getOwnerHandle().getInventory();
internal.setItem(internal.selected, CraftItemStack.asNMSCopy(item));
internal.setSelectedItem(CraftItemStack.asNMSCopy(item));
}
@Override
public @NotNull ItemStack getItemInOffHand() {
return CraftItemStack.asCraftMirror(getInventory().getOwnerHandle().getInventory().offhand.getFirst());
return CraftItemStack.asCraftMirror(getInventory().getOwnerHandle().getInventory().equipment
.get(EquipmentSlot.OFFHAND));
}
@Override
public void setItemInOffHand(@Nullable ItemStack item) {
getInventory().getOwnerHandle().getInventory().offhand.set(0, CraftItemStack.asNMSCopy(item));
getInventory().getOwnerHandle().getInventory().equipment
.set(EquipmentSlot.OFFHAND, CraftItemStack.asNMSCopy(item));
}
@SuppressWarnings("InlineMeSuggester")
@Deprecated
@Override
public @NotNull ItemStack getItemInHand() {
return getItemInMainHand();
}
@SuppressWarnings("InlineMeSuggester")
@Deprecated
@Override
public void setItemInHand(@Nullable ItemStack stack) {
setItemInMainHand(stack);
@@ -183,17 +191,17 @@ public class OpenPlayerInventory extends CraftInventory implements PlayerInvento
@Override
public int getHeldItemSlot() {
Inventory internal = getInventory().getOwnerHandle().getInventory();
return internal.items.size() - 9 + internal.selected;
return internal.getNonEquipmentItems().size() - 9 + internal.getSelectedSlot();
}
@Override
public void setHeldItemSlot(int slot) {
slot %= 9;
getInventory().getOwnerHandle().getInventory().selected = slot;
getInventory().getOwnerHandle().getInventory().setSelectedSlot(slot);
}
@Override
public @Nullable ItemStack getItem(@NotNull org.bukkit.inventory.EquipmentSlot slot) {
public @NotNull ItemStack getItem(@NotNull org.bukkit.inventory.EquipmentSlot slot) {
return switch (slot) {
case HAND -> getItemInMainHand();
case OFF_HAND -> getItemInOffHand();
@@ -1,6 +1,6 @@
package com.lishid.openinv.internal.v1_21_R3.container.bukkit;
package com.lishid.openinv.internal.paper26_1.container.bukkit;
import com.lishid.openinv.internal.v1_21_R3.container.OpenInventory;
import com.lishid.openinv.internal.paper26_1.container.BaseOpenInventory;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
@@ -8,7 +8,7 @@ public class OpenPlayerInventorySelf extends OpenPlayerInventory {
private final int offset;
public OpenPlayerInventorySelf(@NotNull OpenInventory inventory, int offset) {
public OpenPlayerInventorySelf(@NotNull BaseOpenInventory inventory, int offset) {
super(inventory);
this.offset = offset;
}
@@ -1,12 +1,12 @@
package com.lishid.openinv.internal.v1_21_R3.container.menu;
package com.lishid.openinv.internal.paper26_1.container.menu;
import com.google.common.base.Preconditions;
import com.lishid.openinv.internal.v1_21_R3.container.OpenInventory;
import com.lishid.openinv.internal.v1_21_R3.container.bukkit.OpenDummyInventory;
import com.lishid.openinv.internal.v1_21_R3.container.bukkit.OpenPlayerInventorySelf;
import com.lishid.openinv.internal.v1_21_R3.container.slot.ContentDrop;
import com.lishid.openinv.internal.v1_21_R3.container.slot.ContentEquipment;
import com.lishid.openinv.internal.v1_21_R3.container.slot.SlotViewOnly;
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.lishid.openinv.util.Permissions;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.EquipmentSlot;
@@ -15,27 +15,28 @@ import net.minecraft.world.inventory.ChestMenu;
import net.minecraft.world.inventory.MenuType;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import org.bukkit.craftbukkit.v1_21_R3.inventory.CraftInventoryView;
import org.bukkit.craftbukkit.v1_21_R3.inventory.CraftItemStack;
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;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
public class OpenInventoryMenu extends OpenChestMenu<OpenInventory> {
@NullMarked
public class BaseOpenInventoryMenu extends OpenSyncMenu<BaseOpenInventory> {
private int offset;
public OpenInventoryMenu(OpenInventory inventory, ServerPlayer viewer, int i, boolean viewOnly) {
public BaseOpenInventoryMenu(BaseOpenInventory inventory, ServerPlayer viewer, int i, boolean viewOnly) {
super(getMenuType(inventory, viewer), i, inventory, viewer, viewOnly);
}
private static MenuType<ChestMenu> getMenuType(OpenInventory inventory, ServerPlayer viewer) {
private static MenuType<ChestMenu> getMenuType(BaseOpenInventory inventory, ServerPlayer viewer) {
int size = inventory.getContainerSize();
// Disallow duplicate access to own main inventory contents.
if (inventory.getOwnerHandle().equals(viewer)) {
size -= viewer.getInventory().items.size();
size -= viewer.getInventory().getNonEquipmentItems().size();
size = ((int) Math.ceil(size / 9.0)) * 9;
}
@@ -44,11 +45,11 @@ public class OpenInventoryMenu extends OpenChestMenu<OpenInventory> {
@Override
protected void preSlotSetup() {
offset = ownContainer ? viewer.getInventory().items.size() : 0;
offset = ownContainer ? viewer.getInventory().getNonEquipmentItems().size() : 0;
}
@Override
protected @NotNull Slot getUpperSlot(int index, int x, int y) {
protected Slot getUpperSlot(int index, int x, int y) {
index += offset;
Slot slot = container.getMenuSlot(index, x, y);
@@ -99,10 +100,10 @@ public class OpenInventoryMenu extends OpenChestMenu<OpenInventory> {
}
@Override
protected @NotNull CraftInventoryView<OpenChestMenu<OpenInventory>, Inventory> createBukkitEntity() {
protected CraftInventoryView<OpenChestMenu<BaseOpenInventory>, Inventory> createBukkitEntity() {
org.bukkit.inventory.Inventory bukkitInventory;
if (viewOnly) {
bukkitInventory = new OpenDummyInventory(container);
bukkitInventory = new OpenDummyPlayerInventory(container);
} else if (ownContainer) {
bukkitInventory = new OpenPlayerInventorySelf(container, offset);
} else {
@@ -111,7 +112,7 @@ public class OpenInventoryMenu extends OpenChestMenu<OpenInventory> {
return new CraftInventoryView<>(viewer.getBukkitEntity(), bukkitInventory, this) {
@Override
public org.bukkit.inventory.ItemStack getItem(int index) {
public org.bukkit.inventory.@Nullable ItemStack getItem(int index) {
if (viewOnly || index < 0) {
return null;
}
@@ -133,8 +134,11 @@ public class OpenInventoryMenu extends OpenChestMenu<OpenInventory> {
if (rawSlot == InventoryView.OUTSIDE || rawSlot == -1) {
return null;
}
Preconditions.checkArgument(rawSlot >= 0 && rawSlot < topSize + offset + BOTTOM_INVENTORY_SIZE,
"Slot %s outside of inventory", rawSlot);
Preconditions.checkArgument(
rawSlot >= 0 && rawSlot < topSize + offset + BOTTOM_INVENTORY_SIZE,
"Slot %s outside of inventory",
rawSlot
);
if (rawSlot > topSize) {
return getBottomInventory();
}
@@ -173,7 +177,7 @@ public class OpenInventoryMenu extends OpenChestMenu<OpenInventory> {
}
@Override
public @NotNull InventoryType.SlotType getSlotType(int slot) {
public InventoryType.SlotType getSlotType(int slot) {
if (viewOnly || slot < 0) {
return InventoryType.SlotType.OUTSIDE;
}
@@ -184,7 +188,7 @@ public class OpenInventoryMenu extends OpenChestMenu<OpenInventory> {
}
return InventoryType.SlotType.CONTAINER;
}
return OpenInventoryMenu.this.container.getSlotType(offset + slot);
return BaseOpenInventoryMenu.this.container.getSlotType(offset + slot);
}
@Override
@@ -221,7 +225,7 @@ public class OpenInventoryMenu extends OpenChestMenu<OpenInventory> {
// If this is gear, try to move it to the correct slot first.
case OFFHAND, FEET, LEGS, CHEST, HEAD -> {
// Locate the correct slot in the contents following the main inventory.
for (int extra = container.getOwnerHandle().getInventory().items.size() - offset; extra < topSize; ++extra) {
for (int extra = container.getOwnerHandle().getInventory().getNonEquipmentItems().size() - offset; extra < topSize; ++extra) {
Slot extraSlot = getSlot(extra);
if (extraSlot instanceof ContentEquipment.SlotEquipment equipSlot
&& equipSlot.getEquipmentSlot() == equipmentSlot) {
@@ -244,7 +248,7 @@ public class OpenInventoryMenu extends OpenChestMenu<OpenInventory> {
}
} else {
// If we didn't move to a gear slot, try to move to a main inventory slot.
if (!movedGear && !this.moveItemStackTo(itemStack, 0, container.getOwnerHandle().getInventory().items.size(), true)) {
if (!movedGear && !this.moveItemStackTo(itemStack, 0, container.getOwnerHandle().getInventory().getNonEquipmentItems().size(), true)) {
return ItemStack.EMPTY;
}
}
@@ -0,0 +1,276 @@
package com.lishid.openinv.internal.paper26_1.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 net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.inventory.ChestMenu;
import net.minecraft.world.inventory.MenuType;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import org.bukkit.craftbukkit.inventory.CraftInventoryView;
import org.bukkit.event.inventory.InventoryType;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryView;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
/**
* An extension of {@link AbstractContainerMenu} storing and managing data common to all special inventories.
*/
@NullMarked
public abstract class OpenChestMenu<T extends Container & ISpecialInventory & InternalOwned<ServerPlayer>>
extends AbstractContainerMenu {
protected static final int BOTTOM_INVENTORY_SIZE = 36;
protected final T container;
protected final ServerPlayer viewer;
protected final boolean viewOnly;
protected final boolean ownContainer;
protected final int topSize;
private @Nullable CraftInventoryView<OpenChestMenu<T>, Inventory> bukkitEntity;
protected OpenChestMenu(
MenuType<ChestMenu> type,
int containerCounter,
T container,
ServerPlayer viewer,
boolean viewOnly
) {
super(type, containerCounter);
this.container = container;
this.viewer = viewer;
this.viewOnly = viewOnly;
ownContainer = container.getOwnerHandle().equals(viewer);
topSize = getTopSize(viewer);
preSlotSetup();
int upperRows = topSize / 9;
// View's upper inventory - our container
for (int row = 0; row < upperRows; ++row) {
for (int col = 0; col < 9; ++col) {
// x and y for client purposes, but hey, we're thorough here.
// Adapted from net.minecraft.world.inventory.ChestMenu
int x = 8 + col * 18;
int y = 18 + row * 18;
int index = row * 9 + col;
// Guard against weird inventory sizes.
if (index >= container.getContainerSize()) {
addSlot(new SlotViewOnly(container, index, x, y));
continue;
}
Slot slot = getUpperSlot(index, x, y);
addSlot(slot);
}
}
// View's lower inventory - viewer inventory
int playerInvPad = (upperRows - 4) * 18;
for (int row = 0; row < 3; ++row) {
for (int col = 0; col < 9; ++col) {
int x = 8 + col * 18;
int y = playerInvPad + row * 18 + 103;
addSlot(new Slot(viewer.getInventory(), row * 9 + col + 9, x, y));
}
}
// Hotbar
for (int col = 0; col < 9; ++col) {
int x = 8 + col * 18;
int y = playerInvPad + 161;
addSlot(new Slot(viewer.getInventory(), col, x, y));
}
}
public static MenuType<ChestMenu> getChestMenuType(int inventorySize) {
inventorySize = ((int) Math.ceil(inventorySize / 9.0)) * 9;
return switch (inventorySize) {
case 9 -> MenuType.GENERIC_9x1;
case 18 -> MenuType.GENERIC_9x2;
case 27 -> MenuType.GENERIC_9x3;
case 36 -> MenuType.GENERIC_9x4;
case 45 -> MenuType.GENERIC_9x5;
case 54 -> MenuType.GENERIC_9x6;
default -> throw new IllegalArgumentException("Inventory size unsupported: " + inventorySize);
};
}
protected void preSlotSetup() {
}
protected Slot getUpperSlot(int index, int x, int y) {
Slot slot = new Slot(container, index, x, y);
if (viewOnly) {
return SlotViewOnly.wrap(slot);
}
return slot;
}
public boolean isViewOnly() {
return viewOnly;
}
@Override
public final CraftInventoryView<OpenChestMenu<T>, Inventory> getBukkitView() {
if (bukkitEntity == null) {
bukkitEntity = createBukkitEntity();
}
return bukkitEntity;
}
protected CraftInventoryView<OpenChestMenu<T>, Inventory> createBukkitEntity() {
Inventory top;
if (viewOnly) {
top = new OpenDummyInventory(container, container.getBukkitType());
} else {
top = container.getBukkitInventory();
}
return new CraftInventoryView<>(viewer.getBukkitEntity(), top, this) {
@Override
public @Nullable Inventory getInventory(int rawSlot) {
if (viewOnly) {
return null;
}
return super.getInventory(rawSlot);
}
@Override
public int convertSlot(int rawSlot) {
if (viewOnly) {
return InventoryView.OUTSIDE;
}
return super.convertSlot(rawSlot);
}
@Override
public InventoryType.SlotType getSlotType(int slot) {
if (viewOnly) {
return InventoryType.SlotType.OUTSIDE;
}
return super.getSlotType(slot);
}
};
}
private int getTopSize(ServerPlayer viewer) {
MenuType<?> menuType = getType();
if (menuType == MenuType.GENERIC_9x1) {
return 9;
} else if (menuType == MenuType.GENERIC_9x2) {
return 18;
} else if (menuType == MenuType.GENERIC_9x3) {
return 27;
} else if (menuType == MenuType.GENERIC_9x4) {
return 36;
} else if (menuType == MenuType.GENERIC_9x5) {
return 45;
} else if (menuType == MenuType.GENERIC_9x6) {
return 54;
}
// This is a bit gross, but allows us a safe fallthrough.
return menuType.create(-1, viewer.getInventory()).slots.size() - BOTTOM_INVENTORY_SIZE;
}
/**
* Reimplementation of {@link AbstractContainerMenu#moveItemStackTo(ItemStack, int, int, boolean)} that ignores fake
* slots and respects {@link Slot#hasItem()}.
*
* @param itemStack the stack to quick-move
* @param rangeLow the start of the range of slots that can be moved to, inclusive
* @param rangeHigh the end of the range of slots that can be moved to, exclusive
* @param topDown whether to start at the top of the range or bottom
* @return whether the stack was modified as a result of being quick-moved
*/
@Override
protected boolean moveItemStackTo(ItemStack itemStack, int rangeLow, int rangeHigh, boolean topDown) {
boolean modified = false;
boolean stackable = itemStack.isStackable();
Slot firstEmpty = null;
for (int index = topDown ? rangeHigh - 1 : rangeLow;
!itemStack.isEmpty() && (topDown ? index >= rangeLow : index < rangeHigh);
index += topDown ? -1 : 1
) {
Slot slot = slots.get(index);
// If the slot cannot be added to, check the next slot.
if (slot.isFake() || !slot.mayPlace(itemStack)) {
continue;
}
if (slot.hasItem()) {
// If the item isn't stackable, check the next slot.
if (!stackable) {
continue;
}
// Otherwise, add as many as we can from our stack to the slot.
modified |= addToExistingStack(itemStack, slot);
} else {
// If this is the first empty slot, keep track of it for later use.
if (firstEmpty == null) {
firstEmpty = slot;
}
// If the item isn't stackable, we've located the slot we're adding it to, so we're done.
if (!stackable) {
break;
}
}
}
// If the item hasn't been fully added yet, add as many as we can to the first open slot.
if (!itemStack.isEmpty() && firstEmpty != null) {
firstEmpty.setByPlayer(itemStack.split(Math.min(itemStack.getCount(), firstEmpty.getMaxStackSize(itemStack))));
firstEmpty.setChanged();
modified = true;
}
return modified;
}
private static boolean addToExistingStack(ItemStack itemStack, Slot slot) {
ItemStack existing = slot.getItem();
// If the items aren't the same, we can't add our item.
if (!ItemStack.isSameItemSameComponents(itemStack, existing)) {
return false;
}
int max = slot.getMaxStackSize(existing);
int existingCount = existing.getCount();
// If the stack is already full, we can't add more.
if (existingCount >= max) {
return false;
}
int total = existingCount + itemStack.getCount();
// If the existing item can accept the entirety of our item, we're done!
if (total <= max) {
itemStack.setCount(0);
existing.setCount(total);
slot.setChanged();
return true;
}
// Otherwise, add as many as we can.
itemStack.shrink(max - existingCount);
existing.setCount(max);
slot.setChanged();
return true;
}
@Override
public boolean stillValid(Player player) {
return true;
}
}
@@ -1,19 +1,22 @@
package com.lishid.openinv.internal.v1_21_R2.container.menu;
package com.lishid.openinv.internal.paper26_1.container.menu;
import com.lishid.openinv.internal.v1_21_R2.container.OpenEnderChest;
import com.lishid.openinv.internal.paper26_1.container.OpenEnderChest;
import net.minecraft.server.level.ServerPlayer;
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.jetbrains.annotations.NotNull;
import org.jspecify.annotations.NullMarked;
public class OpenEnderChestMenu extends OpenChestMenu<OpenEnderChest> {
@NullMarked
public class OpenEnderChestMenu extends OpenSyncMenu<OpenEnderChest> {
public OpenEnderChestMenu(
@NotNull OpenEnderChest enderChest,
@NotNull ServerPlayer viewer,
OpenEnderChest enderChest,
ServerPlayer viewer,
int containerId,
boolean viewOnly) {
boolean viewOnly
) {
super(getChestMenuType(enderChest.getContainerSize()), containerId, enderChest, viewer, viewOnly);
}
@@ -50,4 +53,14 @@ public class OpenEnderChestMenu extends OpenChestMenu<OpenEnderChest> {
return original;
}
@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);
}
}
@@ -0,0 +1,26 @@
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);
}
}
@@ -0,0 +1,166 @@
package com.lishid.openinv.internal.paper26_1.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 net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.inventory.ChestMenu;
import net.minecraft.world.inventory.ContainerListener;
import net.minecraft.world.inventory.ContainerSynchronizer;
import net.minecraft.world.inventory.DataSlot;
import net.minecraft.world.inventory.MenuType;
import net.minecraft.world.inventory.RemoteSlot;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
/**
* An extension of {@link OpenChestMenu} that supports {@link SlotPlaceholder placeholders}.
*/
@NullMarked
public abstract class OpenSyncMenu<T extends Container & ISpecialInventory & InternalOwned<ServerPlayer>>
extends OpenChestMenu<T> {
// Syncher fields required to send placeholder items.
// All fields intentionally shadow AbstractContainerMenu fields so that warnings
// will be generated if they are no longer necessary.
protected @Nullable ContainerSynchronizer synchronizer;
protected final List<ContainerListener> listeners = new ArrayList<>();
private @Nullable RemoteSlot remoteCarried = RemoteSlot.PLACEHOLDER;
protected boolean suppressRemoteUpdates;
protected OpenSyncMenu(
MenuType<ChestMenu> type,
int containerCounter,
T container,
ServerPlayer viewer,
boolean viewOnly
) {
super(type, containerCounter, container, viewer, viewOnly);
}
@Override
public void broadcastChanges() {
for (int index = 0; index < this.slots.size(); ++index) {
Slot slot = this.slots.get(index);
ItemStack itemstack = slot instanceof SlotPlaceholder placeholder ? placeholder.getOrDefault() : slot.getItem();
Supplier<ItemStack> supplier = Suppliers.memoize(itemstack::copy);
this.triggerSlotListeners(index, itemstack, supplier);
this.synchronizeSlotToRemote(index, itemstack, supplier);
}
// See private synchronizeCarriedToRemote
if (!suppressRemoteUpdates && !getRemoteCarried().matches(getCarried())) {
this.broadcastCarriedItem();
}
for (int index = 0; index < this.dataSlots.size(); ++index) {
DataSlot dataSlot = this.dataSlots.get(index);
int current = dataSlot.get();
if (dataSlot.checkAndClearUpdateFlag()) {
// See private updateDataSlotListeners
for (ContainerListener containerListener : this.listeners) {
containerListener.dataChanged(this, index, current);
}
}
// See private synchronizeDataSlotToRemote
if (!suppressRemoteUpdates && remoteDataSlots.getInt(index) != current) {
remoteDataSlots.set(index, current);
if (synchronizer != null) {
synchronizer.sendDataChange(this, index, current);
}
}
}
}
@Override
public void sendAllDataToRemote() {
List<ItemStack> contentsCopy = new ArrayList<>();
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.copy());
this.remoteSlots.get(index).force(itemStack);
}
getRemoteCarried().force(getCarried());
for (int index = 0; index < this.dataSlots.size(); ++index) {
this.remoteDataSlots.set(index, this.dataSlots.get(index).get());
}
if (this.synchronizer != null) {
this.synchronizer.sendInitialData(this, contentsCopy, this.getCarried().copy(), this.remoteDataSlots.toIntArray());
this.synchronizer.sendOffHandSlotChange();
}
}
protected RemoteSlot getRemoteCarried() {
if (remoteCarried != null) {
return remoteCarried;
}
try {
Field remoteCarried = AbstractContainerMenu.class.getDeclaredField("remoteCarried");
remoteCarried.setAccessible(true);
Object slot = remoteCarried.get(this);
if (slot instanceof RemoteSlot remoteSlot) {
this.remoteCarried = remoteSlot;
} else {
this.remoteCarried = RemoteSlot.PLACEHOLDER;
}
} catch (NoSuchFieldException | IllegalAccessException e) {
remoteCarried = RemoteSlot.PLACEHOLDER;
}
return remoteCarried;
}
// Overrides from here on are purely to capture the contents of fields that aren't exposed.
// Note that super calls should likely always be last! Some send data to remotes after updating state.
@Override
public void addSlotListener(ContainerListener containerListener) {
if (!this.listeners.contains(containerListener)) {
this.listeners.add(containerListener);
}
super.addSlotListener(containerListener);
}
@Override
public void setSynchronizer(ContainerSynchronizer containerSynchronizer) {
this.synchronizer = containerSynchronizer;
// Unset carried slot so we re-capture it when sending slots.
// setSynchronizer calls sendAllDataToRemote, so we can't just do the reflection here once after the fact.
this.remoteCarried = null;
super.setSynchronizer(containerSynchronizer);
}
@Override
public void removeSlotListener(ContainerListener containerListener) {
this.listeners.remove(containerListener);
super.removeSlotListener(containerListener);
}
@Override
public void suppressRemoteUpdates() {
this.suppressRemoteUpdates = true;
super.suppressRemoteUpdates();
}
@Override
public void resumeRemoteUpdates() {
this.suppressRemoteUpdates = false;
super.resumeRemoteUpdates();
}
}
@@ -1,4 +1,4 @@
package com.lishid.openinv.internal.v1_21_R3.container.slot;
package com.lishid.openinv.internal.paper26_1.container.slot;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
@@ -1,6 +1,7 @@
package com.lishid.openinv.internal.v1_21_R2.container.slot;
package com.lishid.openinv.internal.paper26_1.container.slot;
import com.lishid.openinv.internal.v1_21_R2.container.Placeholders;
import com.lishid.openinv.internal.paper26_1.container.slot.placeholder.Placeholders;
import com.lishid.openinv.internal.paper26_1.player.OpenPlayer;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
import net.minecraft.world.ContainerHelper;
@@ -30,11 +31,11 @@ public class ContentCrafting implements Content {
return isAvailable(holder);
}
static boolean isAvailable(@NotNull ServerPlayer holder) {
public static boolean isAvailable(@NotNull ServerPlayer holder) {
// Player must be online and not in creative - since the creative client is (semi-)authoritative,
// it ignores changes without extra help, and will delete the item as a result.
// Spectator mode is technically possible but may cause the item to be dropped if the client opens an inventory.
return holder.connection != null && !holder.connection.isDisconnected() && holder.gameMode.isSurvival();
return OpenPlayer.isConnected(holder.connection) && holder.gameMode.isSurvival();
}
@Override
@@ -107,12 +108,12 @@ public class ContentCrafting implements Content {
}
@Override
public boolean mayPickup(Player player) {
public boolean mayPickup(@NotNull Player player) {
return isAvailable();
}
@Override
public boolean mayPlace(ItemStack itemStack) {
public boolean mayPlace(@NotNull ItemStack itemStack) {
return isAvailable();
}
@@ -1,6 +1,6 @@
package com.lishid.openinv.internal.v1_21_R3.container.slot;
package com.lishid.openinv.internal.paper26_1.container.slot;
import com.lishid.openinv.internal.v1_21_R3.container.Placeholders;
import com.lishid.openinv.internal.paper26_1.container.slot.placeholder.Placeholders;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
import net.minecraft.world.inventory.InventoryMenu;
@@ -1,6 +1,7 @@
package com.lishid.openinv.internal.v1_21_R2.container.slot;
package com.lishid.openinv.internal.paper26_1.container.slot;
import com.lishid.openinv.internal.v1_21_R2.container.Placeholders;
import com.lishid.openinv.internal.paper26_1.container.slot.placeholder.Placeholders;
import com.lishid.openinv.internal.paper26_1.player.OpenPlayer;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
import net.minecraft.world.entity.player.Player;
@@ -63,7 +64,7 @@ public class ContentCursor implements Content {
// Player must be online and not in creative - since the creative client is (semi-)authoritative,
// it ignores changes without extra help, and will delete the item as a result.
// Spectator mode is technically possible but may cause the item to be dropped if the client opens an inventory.
return holder.connection != null && !holder.connection.isDisconnected() && holder.gameMode.isSurvival();
return OpenPlayer.isConnected(holder.connection) && holder.gameMode.isSurvival();
}
@Override
@@ -93,12 +94,12 @@ public class ContentCursor implements Content {
}
@Override
public boolean mayPickup(Player player) {
public boolean mayPickup(@NotNull Player player) {
return isAvailable();
}
@Override
public boolean mayPlace(ItemStack itemStack) {
public boolean mayPlace(@NotNull ItemStack itemStack) {
return isAvailable();
}
@@ -1,6 +1,7 @@
package com.lishid.openinv.internal.v1_21_R3.container.slot;
package com.lishid.openinv.internal.paper26_1.container.slot;
import com.lishid.openinv.internal.v1_21_R3.container.Placeholders;
import com.lishid.openinv.internal.paper26_1.container.slot.placeholder.Placeholders;
import com.lishid.openinv.internal.paper26_1.player.OpenPlayer;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
import net.minecraft.world.inventory.Slot;
@@ -63,14 +64,14 @@ public class ContentDrop implements Content {
@Override
public ItemStack getOrDefault() {
return holder.connection != null && !holder.connection.isDisconnected()
return OpenPlayer.isConnected(holder.connection)
? Placeholders.drop
: Placeholders.blockedOffline;
}
@Override
public boolean mayPlace(ItemStack itemStack) {
return holder.connection != null && !holder.connection.isDisconnected();
public boolean mayPlace(@NotNull ItemStack itemStack) {
return OpenPlayer.isConnected(holder.connection);
}
@Override
@@ -1,8 +1,9 @@
package com.lishid.openinv.internal.v1_21_R3.container.slot;
package com.lishid.openinv.internal.paper26_1.container.slot;
import com.lishid.openinv.internal.v1_21_R3.container.Placeholders;
import com.lishid.openinv.internal.paper26_1.container.slot.placeholder.Placeholders;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
import net.minecraft.world.entity.EntityEquipment;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
@@ -12,13 +13,14 @@ import org.jetbrains.annotations.NotNull;
/**
* A slot for equipment that displays placeholders if empty.
*/
public class ContentEquipment extends ContentList {
public class ContentEquipment implements Content {
private EntityEquipment equipment;
private final ItemStack placeholder;
private final EquipmentSlot equipmentSlot;
public ContentEquipment(ServerPlayer holder, int index, EquipmentSlot equipmentSlot) {
super(holder, index, InventoryType.SlotType.ARMOR);
public ContentEquipment(ServerPlayer holder, EquipmentSlot equipmentSlot) {
setHolder(holder);
placeholder = switch (equipmentSlot) {
case HEAD -> Placeholders.emptyHelmet;
case CHEST -> Placeholders.emptyChestplate;
@@ -31,7 +33,31 @@ public class ContentEquipment extends ContentList {
@Override
public void setHolder(@NotNull ServerPlayer holder) {
this.items = holder.getInventory().armor;
this.equipment = holder.getInventory().equipment;
}
@Override
public ItemStack get() {
return equipment.get(equipmentSlot);
}
@Override
public ItemStack remove() {
return equipment.set(equipmentSlot, ItemStack.EMPTY);
}
@Override
public ItemStack removePartial(int amount) {
ItemStack current = get();
if (!current.isEmpty() && amount > 0) {
return current.split(amount);
}
return ItemStack.EMPTY;
}
@Override
public void set(ItemStack itemStack) {
equipment.set(equipmentSlot, itemStack);
}
@Override
@@ -39,6 +65,11 @@ public class ContentEquipment extends ContentList {
return new SlotEquipment(container, slot, x, y);
}
@Override
public InventoryType.SlotType getSlotType() {
return InventoryType.SlotType.ARMOR;
}
public class SlotEquipment extends SlotPlaceholder {
private ServerPlayer viewer;
@@ -65,12 +96,12 @@ public class ContentEquipment extends ContentList {
}
@Override
public boolean mayPlace(ItemStack var0) {
public boolean mayPlace(@NotNull ItemStack itemStack) {
if (viewer == null) {
return true;
}
return equipmentSlot == EquipmentSlot.OFFHAND || viewer.getEquipmentSlotForItem(var0) == equipmentSlot;
return equipmentSlot == EquipmentSlot.OFFHAND || viewer.getEquipmentSlotForItem(itemStack) == equipmentSlot;
}
}
@@ -1,4 +1,4 @@
package com.lishid.openinv.internal.v1_21_R3.container.slot;
package com.lishid.openinv.internal.paper26_1.container.slot;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
@@ -1,8 +1,11 @@
package com.lishid.openinv.internal.v1_21_R3.container.slot;
package com.lishid.openinv.internal.paper26_1.container.slot;
import com.lishid.openinv.internal.paper26_1.player.OpenPlayer;
import net.minecraft.network.protocol.game.ClientboundContainerSetSlotPacket;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
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;
@@ -14,13 +17,13 @@ public class ContentOffHand extends ContentEquipment {
private ServerPlayer holder;
public ContentOffHand(ServerPlayer holder, int localIndex) {
super(holder, localIndex, EquipmentSlot.OFFHAND);
public ContentOffHand(ServerPlayer holder) {
super(holder, EquipmentSlot.OFFHAND);
}
@Override
public void setHolder(@NotNull ServerPlayer holder) {
this.items = holder.getInventory().offhand;
super.setHolder(holder);
this.holder = holder;
}
@@ -34,10 +37,14 @@ public class ContentOffHand extends ContentEquipment {
return new SlotEquipment(container, slot, x, y) {
@Override
public void setChanged() {
if (holder.connection != null
&& !holder.connection.isDisconnected()
&& holder.containerMenu != holder.inventoryMenu) {
holder.resendItemInHands();
if (OpenPlayer.isConnected(holder.connection) && holder.containerMenu != holder.inventoryMenu) {
holder.connection.send(
new ClientboundContainerSetSlotPacket(
holder.inventoryMenu.containerId,
holder.inventoryMenu.incrementStateId(),
InventoryMenu.SHIELD_SLOT,
holder.getOffhandItem()
));
}
}
};
@@ -1,4 +1,4 @@
package com.lishid.openinv.internal.v1_21_R2.container.slot;
package com.lishid.openinv.internal.paper26_1.container.slot;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
@@ -12,7 +12,7 @@ import org.jetbrains.annotations.NotNull;
*/
public class ContentViewOnly implements Content {
@NotNull ServerPlayer holder;
protected @NotNull ServerPlayer holder;
public ContentViewOnly(@NotNull ServerPlayer holder) {
this.holder = holder;
@@ -1,4 +1,4 @@
package com.lishid.openinv.internal.v1_21_R2.container.slot;
package com.lishid.openinv.internal.paper26_1.container.slot;
import net.minecraft.world.Container;
import net.minecraft.world.inventory.Slot;
@@ -1,6 +1,6 @@
package com.lishid.openinv.internal.v1_21_R2.container.slot;
package com.lishid.openinv.internal.paper26_1.container.slot;
import com.lishid.openinv.internal.v1_21_R2.container.Placeholders;
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;
@@ -45,20 +45,20 @@ public class SlotViewOnly extends SlotPlaceholder {
}
@Override
public void onQuickCraft(ItemStack var0, ItemStack var1) {
public void onQuickCraft(@NotNull ItemStack itemStack1, @NotNull ItemStack itemStack2) {
}
@Override
public void onTake(Player var0, ItemStack var1) {
public void onTake(@NotNull Player player, @NotNull ItemStack itemStack) {
}
@Override
public boolean mayPlace(ItemStack var0) {
public boolean mayPlace(@NotNull ItemStack itemStack) {
return false;
}
@Override
public ItemStack getItem() {
public @NotNull ItemStack getItem() {
return ItemStack.EMPTY;
}
@@ -68,15 +68,15 @@ public class SlotViewOnly extends SlotPlaceholder {
}
@Override
public void setByPlayer(ItemStack newStack) {
public void setByPlayer(@NotNull ItemStack newStack) {
}
@Override
public void setByPlayer(ItemStack newStack, ItemStack oldStack) {
public void setByPlayer(@NotNull ItemStack newStack, @NotNull ItemStack oldStack) {
}
@Override
public void set(ItemStack var0) {
public void set(@NotNull ItemStack itemStack) {
}
@Override
@@ -89,17 +89,17 @@ public class SlotViewOnly extends SlotPlaceholder {
}
@Override
public int getMaxStackSize(ItemStack itemStack) {
public int getMaxStackSize(@NotNull ItemStack itemStack) {
return 0;
}
@Override
public ItemStack remove(int amount) {
public @NotNull ItemStack remove(int amount) {
return ItemStack.EMPTY;
}
@Override
public boolean mayPickup(Player var0) {
public boolean mayPickup(@NotNull Player player) {
return false;
}
@@ -109,27 +109,27 @@ public class SlotViewOnly extends SlotPlaceholder {
}
@Override
public Optional<ItemStack> tryRemove(int var0, int var1, Player var2) {
public @NotNull Optional<ItemStack> tryRemove(int var0, int var1, @NotNull Player player) {
return Optional.empty();
}
@Override
public ItemStack safeTake(int var0, int var1, Player var2) {
public @NotNull ItemStack safeTake(int var0, int var1, @NotNull Player player) {
return ItemStack.EMPTY;
}
@Override
public ItemStack safeInsert(ItemStack itemStack) {
public @NotNull ItemStack safeInsert(@NotNull ItemStack itemStack) {
return itemStack;
}
@Override
public ItemStack safeInsert(ItemStack itemStack, int amount) {
public @NotNull ItemStack safeInsert(@NotNull ItemStack itemStack, int amount) {
return itemStack;
}
@Override
public boolean allowModification(Player var0) {
public boolean allowModification(@NotNull Player player) {
return false;
}
@@ -0,0 +1,40 @@
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 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<>());
@Override
protected @NotNull CompoundTag parseTag(@NotNull String itemText) throws Exception {
return TagParser.parseCompoundFully(itemText);
}
@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);
}
}
@@ -1,18 +1,16 @@
package com.lishid.openinv.internal.v1_21_R2.container;
package com.lishid.openinv.internal.paper26_1.container.slot.placeholder;
import com.mojang.serialization.DataResult;
import net.minecraft.core.Registry;
import net.minecraft.core.RegistryAccess;
import net.minecraft.core.component.DataComponents;
import net.minecraft.core.registries.Registries;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.TagParser;
import net.minecraft.nbt.NbtOps;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.util.Unit;
import net.minecraft.world.item.DyeColor;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.component.CustomModelData;
import net.minecraft.world.item.component.DyedItemColor;
import net.minecraft.world.level.GameType;
import net.minecraft.world.level.ItemLike;
@@ -20,86 +18,83 @@ import net.minecraft.world.level.block.entity.BannerPattern;
import net.minecraft.world.level.block.entity.BannerPatternLayers;
import net.minecraft.world.level.block.entity.BannerPatterns;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.craftbukkit.v1_21_R2.CraftRegistry;
import org.bukkit.craftbukkit.CraftRegistry;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.EnumMap;
import java.util.List;
import java.util.Optional;
public final class Placeholders {
public abstract class PlaceholderLoaderBase {
private static final CustomModelData DEFAULT_CUSTOM_MODEL_DATA = new CustomModelData(9999);
public static final @NotNull EnumMap<GameType, ItemStack> BLOCKED_GAME_TYPE = new EnumMap<>(GameType.class);
public static @NotNull ItemStack craftingOutput = defaultCraftingOutput();
public static @NotNull ItemStack cursor = defaultCursor();
public static @NotNull ItemStack drop = defaultDrop();
public static @NotNull ItemStack emptyHelmet = getEmptyArmor(Items.LEATHER_HELMET);
public static @NotNull ItemStack emptyChestplate = getEmptyArmor(Items.LEATHER_CHESTPLATE);
public static @NotNull ItemStack emptyLeggings = getEmptyArmor(Items.LEATHER_LEGGINGS);
public static @NotNull ItemStack emptyBoots = getEmptyArmor(Items.LEATHER_BOOTS);
public static @NotNull ItemStack emptyOffHand = getEmptyShield();
public static @NotNull ItemStack notSlot = defaultNotSlot();
public static @NotNull ItemStack blockedOffline = defaultBlockedOffline();
public void load(@Nullable ConfigurationSection section) throws Exception {
Placeholders.craftingOutput = parse(section, "crafting-output", defaultCraftingOutput());
Placeholders.cursor = parse(section, "cursor", defaultCursor());
Placeholders.drop = parse(section, "drop", defaultDrop());
Placeholders.emptyHelmet = parse(section, "empty-helmet", getEmptyArmor(Items.LEATHER_HELMET));
Placeholders.emptyChestplate = parse(section, "empty-chestplate", getEmptyArmor(Items.LEATHER_CHESTPLATE));
Placeholders.emptyLeggings = parse(section, "empty-leggings", getEmptyArmor(Items.LEATHER_LEGGINGS));
Placeholders.emptyBoots = parse(section, "empty-boots", getEmptyArmor(Items.LEATHER_BOOTS));
Placeholders.emptyOffHand = parse(section, "empty-off-hand", defaultShield());
Placeholders.notSlot = parse(section, "not-a-slot", defaultNotSlot());
Placeholders.blockedOffline = parse(section, "blocked.offline", defaultBlockedOffline());
static {
for (GameType type : GameType.values()) {
// Barrier: "Not available - Creative" etc.
ItemStack typeItem = new ItemStack(Items.BARRIER);
typeItem.set(
DataComponents.ITEM_NAME,
Component.translatable("options.narrator.notavailable").append(" - ").append(type.getShortDisplayName()));
BLOCKED_GAME_TYPE.put(type, typeItem);
Component.translatable("options.narrator.notavailable").append(" - ").append(type.getShortDisplayName())
);
Placeholders.BLOCKED_GAME_TYPE.put(type, typeItem);
}
Placeholders.BLOCKED_GAME_TYPE.put(GameType.CREATIVE, parse(section, "blocked.creative", Placeholders.BLOCKED_GAME_TYPE.get(GameType.CREATIVE)));
Placeholders.BLOCKED_GAME_TYPE.put(GameType.SPECTATOR, parse(section, "blocked.spectator", Placeholders.BLOCKED_GAME_TYPE.get(GameType.SPECTATOR)));
}
public static void load(@NotNull ConfigurationSection section) throws Exception {
craftingOutput = parse(section, "crafting-output", craftingOutput);
cursor = parse(section, "cursor", cursor);
drop = parse(section, "drop", drop);
emptyHelmet = parse(section, "empty-helmet", emptyHelmet);
emptyChestplate = parse(section, "empty-chestplate", emptyChestplate);
emptyLeggings = parse(section, "empty-leggings", emptyLeggings);
emptyBoots = parse(section, "empty-boots", emptyBoots);
emptyOffHand = parse(section, "empty-off-hand", emptyOffHand);
notSlot = parse(section, "not-a-slot", notSlot);
blockedOffline = parse(section, "blocked.offline", blockedOffline);
BLOCKED_GAME_TYPE.put(GameType.CREATIVE, parse(section, "blocked.creative", BLOCKED_GAME_TYPE.get(GameType.CREATIVE)));
BLOCKED_GAME_TYPE.put(GameType.SPECTATOR, parse(section, "blocked.spectator", BLOCKED_GAME_TYPE.get(GameType.SPECTATOR)));
}
private static @NotNull ItemStack parse(
@NotNull ConfigurationSection section,
protected @NotNull ItemStack parse(
@Nullable ConfigurationSection section,
@NotNull String path,
@NotNull ItemStack defaultStack) throws Exception {
@NotNull ItemStack defaultStack
) throws Exception {
if (section == null) {
return defaultStack;
}
String itemText = section.getString(path);
if (itemText == null) {
return defaultStack;
}
CompoundTag compoundTag = TagParser.parseTag(itemText);
Optional<ItemStack> parsed = ItemStack.parse(CraftRegistry.getMinecraftRegistry(), compoundTag);
return parsed.filter(itemStack -> !itemStack.isEmpty()).orElse(defaultStack);
}
public static ItemStack survivalOnly(@NotNull ServerPlayer serverPlayer) {
if (serverPlayer.connection == null || serverPlayer.connection.isDisconnected()) {
return blockedOffline;
CompoundTag compoundTag = parseTag(itemText);
DataResult<ItemStack> parsed = ItemStack.CODEC.parse(CraftRegistry.getMinecraftRegistry().createSerializationContext(NbtOps.INSTANCE), compoundTag);
ItemStack itemStack;
try {
itemStack = parsed.getOrThrow();
} catch (Exception e) {
itemStack = null;
}
return BLOCKED_GAME_TYPE.getOrDefault(serverPlayer.gameMode.getGameModeForPlayer(), ItemStack.EMPTY);
return itemStack == null ? defaultStack : itemStack;
}
private static ItemStack defaultCraftingOutput() {
protected abstract @NotNull CompoundTag parseTag(@NotNull String itemText) throws Exception;
protected abstract void addModelData(@NotNull ItemStack itemStack);
protected abstract void hideTooltip(@NotNull ItemStack itemStack);
protected abstract DyedItemColor getDye(int rgb);
protected @NotNull ItemStack defaultCraftingOutput() {
// Crafting table: "Crafting"
ItemStack itemStack = new ItemStack(Items.CRAFTING_TABLE);
itemStack.set(DataComponents.ITEM_NAME, Component.translatable("container.crafting"));
itemStack.set(DataComponents.CUSTOM_MODEL_DATA, DEFAULT_CUSTOM_MODEL_DATA);
addModelData(itemStack);
return itemStack;
}
private static ItemStack defaultCursor() {
protected @NotNull ItemStack defaultCursor() {
// Cursor-like banner with no tooltip
ItemStack itemStack = new ItemStack(Items.WHITE_BANNER);
RegistryAccess minecraftRegistry = CraftRegistry.getMinecraftRegistry();
@@ -111,32 +106,35 @@ public final class Placeholders {
new BannerPatternLayers(List.of(
new BannerPatternLayers.Layer(bannerPatterns.wrapAsHolder(halfDiagBottomRight), DyeColor.GRAY),
new BannerPatternLayers.Layer(bannerPatterns.wrapAsHolder(downRight), DyeColor.WHITE),
new BannerPatternLayers.Layer(bannerPatterns.wrapAsHolder(border), DyeColor.GRAY))));
itemStack.set(DataComponents.CUSTOM_MODEL_DATA, DEFAULT_CUSTOM_MODEL_DATA);
itemStack.set(DataComponents.HIDE_TOOLTIP, Unit.INSTANCE);
new BannerPatternLayers.Layer(bannerPatterns.wrapAsHolder(border), DyeColor.GRAY)
))
);
addModelData(itemStack);
hideTooltip(itemStack);
return itemStack;
}
private static ItemStack defaultDrop() {
protected @NotNull ItemStack defaultDrop() {
// Dropper: "Drop Selected Item"
ItemStack itemStack = new ItemStack(Items.DROPPER);
// Note: translatable component, not keybind component! We want the text identifying the keybind, not the key.
itemStack.set(DataComponents.ITEM_NAME, Component.translatable("key.drop"));
itemStack.set(DataComponents.CUSTOM_MODEL_DATA, DEFAULT_CUSTOM_MODEL_DATA);
addModelData(itemStack);
return itemStack;
}
private static ItemStack getEmptyArmor(ItemLike item) {
protected @NotNull ItemStack getEmptyArmor(@NotNull ItemLike item) {
// Inventory-background-grey-ish leather armor with no tooltip
ItemStack itemStack = new ItemStack(item);
DyedItemColor color = new DyedItemColor(0xC8C8C8, false);
DyedItemColor color = getDye(0xC8C8C8);
itemStack.set(DataComponents.DYED_COLOR, color);
itemStack.set(DataComponents.HIDE_TOOLTIP, Unit.INSTANCE);
itemStack.set(DataComponents.CUSTOM_MODEL_DATA, DEFAULT_CUSTOM_MODEL_DATA);
hideTooltip(itemStack);
addModelData(itemStack);
return itemStack;
}
private static ItemStack getEmptyShield() {
protected @NotNull ItemStack defaultShield() {
// Shield with "missing texture" pattern, magenta and black squares.
ItemStack itemStack = new ItemStack(Items.SHIELD);
itemStack.set(DataComponents.BASE_COLOR, DyeColor.MAGENTA);
RegistryAccess minecraftRegistry = CraftRegistry.getMinecraftRegistry();
@@ -152,32 +150,31 @@ public final class Placeholders {
new BannerPatternLayers.Layer(bannerPatterns.wrapAsHolder(topLeft), DyeColor.MAGENTA),
new BannerPatternLayers.Layer(bannerPatterns.wrapAsHolder(bottomLeft), DyeColor.MAGENTA),
new BannerPatternLayers.Layer(bannerPatterns.wrapAsHolder(topRight), DyeColor.BLACK),
new BannerPatternLayers.Layer(bannerPatterns.wrapAsHolder(bottomRight), DyeColor.BLACK))));
itemStack.set(DataComponents.HIDE_TOOLTIP, Unit.INSTANCE);
itemStack.set(DataComponents.CUSTOM_MODEL_DATA, DEFAULT_CUSTOM_MODEL_DATA);
new BannerPatternLayers.Layer(bannerPatterns.wrapAsHolder(bottomRight), DyeColor.BLACK)
))
);
hideTooltip(itemStack);
addModelData(itemStack);
return itemStack;
}
private static ItemStack defaultNotSlot() {
protected @NotNull ItemStack defaultNotSlot() {
// White pane with no tooltip
ItemStack itemStack = new ItemStack(Items.WHITE_STAINED_GLASS_PANE);
itemStack.set(DataComponents.HIDE_TOOLTIP, Unit.INSTANCE);
itemStack.set(DataComponents.CUSTOM_MODEL_DATA, DEFAULT_CUSTOM_MODEL_DATA);
hideTooltip(itemStack);
addModelData(itemStack);
return itemStack;
}
private static ItemStack defaultBlockedOffline() {
protected @NotNull ItemStack defaultBlockedOffline() {
// Barrier: "Not available - Offline"
ItemStack itemStack = new ItemStack(Items.BARRIER);
itemStack.set(DataComponents.ITEM_NAME,
Component.translatable("options.narrator.notavailable")
.append(Component.literal(" - "))
.append(Component.translatable("gui.socialInteractions.status_offline")));
.append(Component.translatable("gui.socialInteractions.status_offline"))
);
return itemStack;
}
private Placeholders() {
throw new IllegalStateException("Cannot create instance of utility class.");
}
}
@@ -0,0 +1,37 @@
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.");
}
}
@@ -0,0 +1,232 @@
package com.lishid.openinv.internal.paper26_1.player;
import com.lishid.openinv.event.OpenEvents;
import com.mojang.logging.LogUtils;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtIo;
import net.minecraft.nbt.NumericTag;
import net.minecraft.nbt.Tag;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.network.ServerGamePacketListenerImpl;
import net.minecraft.util.ProblemReporter;
import net.minecraft.world.level.storage.PlayerDataStorage;
import net.minecraft.world.level.storage.TagValueOutput;
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.slf4j.Logger;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Optional;
import java.util.Set;
public class OpenPlayer extends CraftPlayer {
/**
* List of tags to always reset when saving. These are items that do not get written
* if unset or empty, resulting in older values not being clobbered appropriately.
*
* @see net.minecraft.world.entity.Entity#saveWithoutId(ValueOutput, boolean, boolean, boolean)
* @see net.minecraft.server.level.ServerPlayer#addAdditionalSaveData(ValueOutput)
* @see net.minecraft.world.entity.player.Player#addAdditionalSaveData(ValueOutput)
* @see net.minecraft.world.entity.LivingEntity#addAdditionalSaveData(ValueOutput)
*/
@Unmodifiable
protected static final Set<String> RESET_TAGS = Set.of(
// Entity#saveWithoutId(CompoundTag)
"CustomName",
"CustomNameVisible",
"Silent",
"NoGravity",
"Glowing",
"TicksFrozen",
"HasVisualFire",
"Tags",
"data",
"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",
"respawn",
"raid_omen_position",
"ender_pearls",
"ShoulderEntityLeft",
"ShoulderEntityRight",
// Player#addAdditionalSaveData(CompoundTag)
"LastDeathLocation",
// LivingEntity#addAdditionalSaveData(CompoundTag)
"current_explosion_impact_pos",
"active_effects",
"sleeping_pos",
"Brain", // No longer needed as of 26.1
"last_hurt_by_player",
"last_hurt_by_player_memory_time",
"last_hurt_by_mob",
"ticks_since_last_hurt_by_mob",
"equipment",
"locator_bar_icon"
);
private final PlayerManager manager;
protected OpenPlayer(CraftServer server, ServerPlayer entity, PlayerManager manager) {
super(server, entity);
this.manager = manager;
}
@Override
public void loadData() {
manager.loadData(server.getServer(), getHandle());
}
@Override
public void saveData() {
if (OpenEvents.saveCancelled(this)) {
return;
}
ServerPlayer player = getHandle();
Logger logger = LogUtils.getLogger();
// See net.minecraft.world.level.storage.PlayerDataStorage#save(EntityHuman)
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 playerData = getWritableTag(oldData);
ValueOutput valueOutput = TagValueOutput.createWrappingWithContext(scopedCollector, player.registryAccess(), playerData);
player.saveWithoutId(valueOutput);
saveSafe(player, oldData, playerData, worldNbtStorage);
} catch (Exception e) {
LogUtils.getLogger().warn("Failed to save player data for {}: {}", player.getScoreboardName(), e);
}
}
protected Optional<CompoundTag> load(
PlayerDataStorage storage,
ServerPlayer player,
ProblemReporter.ScopedCollector collector
) {
return storage.load(player.nameAndId());
}
protected void saveSafe(
@NotNull ServerPlayer player,
@Nullable CompoundTag oldData,
@NotNull CompoundTag playerData,
@NotNull PlayerDataStorage worldNbtStorage
) throws IOException {
// Revert certain special data values when offline.
revertSpecialValues(playerData, oldData);
Path playerDataDir = worldNbtStorage.getPlayerDir().toPath();
Path tempFile = Files.createTempFile(playerDataDir, player.getStringUUID() + "-", ".dat");
NbtIo.writeCompressed(playerData, tempFile);
Path dataFile = playerDataDir.resolve(player.getStringUUID() + ".dat");
Path backupFile = playerDataDir.resolve(player.getStringUUID() + ".dat_old");
safeReplaceFile(dataFile, tempFile, backupFile);
}
protected void safeReplaceFile(
@NotNull Path dataFile,
@NotNull Path tempFile,
@NotNull Path backupFile
) {
net.minecraft.util.Util.safeReplaceFile(dataFile, tempFile, backupFile);
}
@Contract("null -> new")
protected @NotNull CompoundTag getWritableTag(@Nullable CompoundTag oldData) {
if (oldData == null) {
return new CompoundTag();
}
// Copy old data. This is a deep clone, so operating on it should be safe.
oldData = oldData.copy();
// Remove vanilla/server data that is not written every time.
oldData.keySet().removeIf(
key -> RESET_TAGS.contains(key)
|| key.startsWith("Bukkit")
|| (key.startsWith("Paper") && key.length() > 5)
);
return oldData;
}
protected void revertSpecialValues(@NotNull CompoundTag newData, @Nullable CompoundTag oldData) {
if (oldData == null) {
return;
}
// Revert automatic updates to play timestamps.
copyValue(oldData, newData, "bukkit", "lastPlayed", NumericTag.class);
copyValue(oldData, newData, "Paper", "LastSeen", NumericTag.class);
copyValue(oldData, newData, "Paper", "LastLogin", NumericTag.class);
}
private <T extends Tag> void copyValue(
@NotNull CompoundTag source,
@NotNull CompoundTag target,
@NotNull String container,
@NotNull String key,
@SuppressWarnings("SameParameterValue") @NotNull Class<T> tagType
) {
CompoundTag oldContainer = getTag(source, container, CompoundTag.class);
CompoundTag newContainer = getTag(target, container, CompoundTag.class);
// New container being null means the server implementation doesn't store this data.
if (newContainer == null) {
return;
}
// If old tag exists, copy it to new location, removing otherwise.
setTag(newContainer, key, getTag(oldContainer, key, tagType));
}
private <T extends Tag> @Nullable T getTag(
@Nullable CompoundTag container,
@NotNull String key,
@NotNull Class<T> dataType
) {
if (container == null) {
return null;
}
Tag value = container.get(key);
if (value == null || !dataType.isAssignableFrom(value.getClass())) {
return null;
}
return dataType.cast(value);
}
private <T extends Tag> void setTag(
@NotNull CompoundTag container,
@NotNull String key,
@Nullable T data
) {
if (data == null) {
remove(container, key);
} else {
container.put(key, data);
}
}
protected void remove(@NotNull CompoundTag tag, @NotNull String key) {
tag.remove(key);
}
public static boolean isConnected(@Nullable ServerGamePacketListenerImpl connection) {
return connection != null && !connection.isDisconnected();
}
}
@@ -0,0 +1,309 @@
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 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.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 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 {
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;
}
@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.
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;
}
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 = 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));
}
@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();
}
}
+32
View File
@@ -0,0 +1,32 @@
import com.github.jikoo.openinv.SpigotDependencyExtension
import com.github.jikoo.openinv.SpigotSetup
plugins {
alias(libs.plugins.shadow)
}
repositories {
maven("https://repo.papermc.io/repository/maven-public/")
}
java {
toolchain.languageVersion = JavaLanguageVersion.of(25)
}
tasks.withType<JavaCompile>().configureEach {
options.release = 21
}
apply<SpigotSetup>()
dependencies {
compileOnly(libs.spigotapi)
extensions.getByType(SpigotDependencyExtension::class.java).version = "26.1.2-R0.1-SNAPSHOT"
compileOnly("com.mojang:logging:1.6.11")
compileOnly("com.mojang:brigadier:1.3.10")
compileOnly("com.mojang:datafixerupper:9.0.19")
compileOnly("com.mojang:authlib:7.0.62")
compileOnly(project(":openinvapi"))
compileOnly(project(":openinvcommon"))
}
@@ -1,19 +1,19 @@
package com.lishid.openinv.internal.v1_21_R2;
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.internal.v1_21_R2.container.AnySilentContainer;
import com.lishid.openinv.internal.v1_21_R2.container.OpenEnderChest;
import com.lishid.openinv.internal.v1_21_R2.container.OpenInventory;
import com.lishid.openinv.internal.v1_21_R2.container.Placeholders;
import com.lishid.openinv.internal.v1_21_R2.player.PlayerManager;
import com.lishid.openinv.util.lang.LanguageManager;
import net.minecraft.world.Container;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.craftbukkit.v1_21_R2.inventory.CraftInventory;
import org.bukkit.craftbukkit.inventory.CraftInventory;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import org.jetbrains.annotations.NotNull;
@@ -24,7 +24,7 @@ import java.util.logging.Logger;
public class InternalAccessor implements Accessor {
private final @NotNull Logger logger;
protected final @NotNull Logger logger;
private final @NotNull PlayerManager manager;
private final @NotNull AnySilentContainer anySilentContainer;
@@ -69,12 +69,11 @@ public class InternalAccessor implements Accessor {
@Override
public void reload(@NotNull ConfigurationSection config) {
ConfigurationSection placeholders = config.getConfigurationSection("placeholders");
if (placeholders != null) {
try {
Placeholders.load(placeholders);
} catch (Exception e) {
logger.log(Level.WARNING, "Caught exception loading placeholder overrides!", e);
}
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);
}
}
@@ -0,0 +1,212 @@
package com.github.jikoo.openinv.internal.spigot26_1.container;
import com.lishid.openinv.internal.AnySilentContainerBase;
import com.github.jikoo.openinv.internal.spigot26_1.container.menu.OpenChestMenu;
import com.github.jikoo.openinv.internal.spigot26_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.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.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Field;
import java.util.logging.Logger;
public class AnySilentContainer extends AnySilentContainerBase {
private final @NotNull Logger logger;
private final @NotNull LanguageManager lang;
private @Nullable Field serverPlayerGameModeGameType;
public AnySilentContainer(@NotNull Logger logger, @NotNull LanguageManager lang) {
this.logger = logger;
this.lang = lang;
try {
try {
this.serverPlayerGameModeGameType = ServerPlayerGameMode.class.getDeclaredField("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(
@NotNull final Player bukkitPlayer,
final boolean silentchest,
@NotNull final org.bukkit.block.Block bukkitBlock
) {
// Silent ender chest is API-only
if (silentchest && bukkitBlock.getType() == Material.ENDER_CHEST) {
bukkitPlayer.openInventory(bukkitPlayer.getEnderChest());
bukkitPlayer.incrementStatistic(Statistic.ENDERCHEST_OPENED);
return true;
}
ServerPlayer player = PlayerManager.getHandle(bukkitPlayer);
final net.minecraft.world.level.Level level = player.level();
final BlockPos blockPos = new BlockPos(bukkitBlock.getX(), bukkitBlock.getY(), bukkitBlock.getZ());
final BlockEntity blockEntity = level.getBlockEntity(blockPos);
if (blockEntity == null) {
return false;
}
if (blockEntity instanceof EnderChestBlockEntity enderChestTile) {
// Anychest ender chest. See net.minecraft.world.level.block.EnderChestBlock
PlayerEnderChestContainer enderChest = player.getEnderChestInventory();
enderChest.setActiveChest(enderChestTile);
player.openMenu(
new SimpleMenuProvider(
(containerCounter, playerInventory, ignored) -> {
MenuType<?> containers = 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();
}
@Override
protected @Nullable InventoryHolder getHolder(@NotNull Inventory inventory) {
return inventory.getHolder();
}
}
Loaded 100 of 219 files, more files were not shown because too many files have changed in this diff. Show more