Compare commits

..
170 Commits
Author SHA1 Message Date
Jikoo 6b38cb7201 Bump version to 4.2.1 for release 2022-08-01 13:35:00 -04:00
dependabot[bot] 23e2a7e534 Bump maven-assembly-plugin from 3.3.0 to 3.4.2 (#94)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-08-01 13:32:56 -04:00
Elioby 1093b6d911 Update to 1.19.1 (#89) 2022-07-30 21:04:40 -04:00
Jikoo 89db744113 Bump version to 4.2.1-SNAPSHOT for development 2022-06-07 19:41:52 -04:00
Jikoo fcb9ccb6e6 Bump version to 4.2.0 for release 2022-06-07 19:41:39 -04:00
Jikoo 9c934e440d Update main plugin to Java 17
All supported versions require Java 17, there's no reason the plugin should not.
Swap to Temurin for CI, Adopt is dead.
2022-06-07 19:31:23 -04:00
Jikoo c2d60642c3 Update to Minecraft 1.19
Drop support for 1.17 and outdated 1.18 builds.
2022-06-07 19:31:23 -04:00
Jikoo 0653d12e49 Code health pass for Java 16 2022-06-07 19:31:22 -04:00
Jikoo f34b342cdd Tab? The drink? 2022-06-07 19:31:22 -04:00
Jikoo 70d8999b48 Move translations to a separate package
Makes it easier to find non-language files as necessary
2022-06-07 19:31:22 -04:00
Jikoo a5b21cbbf5 Correct crossworld check 2022-06-07 19:31:22 -04:00
Jikoo 44e514d8c2 Don't close other inventories for opened player during close 2022-06-07 19:31:22 -04:00
Jikoo 6407f1d8e4 Reorganize members 2022-06-07 19:31:22 -04:00
Jikoo 0b761a6fc3 Continue migrating terminology to "container"
Clean up documentation a bit, prettify
2022-06-07 19:31:21 -04:00
Jikoo 6ad6e0c6bc Bump version to 4.2.0-SNAPSHOT 2022-06-07 19:31:21 -04:00
Jikoo fdf920062b Remove cache, add disable-offline-access config node
Fixes various permissions not being respected during login/logout with inventories already open. This will result in a performance hit for repeated open/closes - there will be significantly more disk I/O.

Closes lishid#171
Closes #56
2022-06-07 19:31:21 -04:00
Jikoo 9502b29fe3 Clean up compiler warnings
Compiler doesn't understand that api is provided by plugin during build process
2022-06-07 19:31:21 -04:00
Jikoo c9ba401a6e Don't inject player if unnecessary 2022-06-07 19:31:21 -04:00
Jikoo b6e8e2ba42 Extract some duplicate code from internal implementations 2022-06-07 19:31:20 -04:00
Jikoo ac00261afb Note Spigot requirement 2022-06-07 19:31:20 -04:00
Jikoo f21019eba8 Extract help to location of use 2022-06-07 19:31:20 -04:00
Jikoo 525982ce64 Harden event/internal access slightly 2022-06-07 19:31:20 -04:00
dependabot[bot] 65c3358b22 Bump pascalgn/automerge-action from 0.15.2 to 0.15.3 (#82)
Bumps [pascalgn/automerge-action](https://github.com/pascalgn/automerge-action) from 0.15.2 to 0.15.3.
- [Release notes](https://github.com/pascalgn/automerge-action/releases)
- [Commits](https://github.com/pascalgn/automerge-action/compare/v0.15.2...v0.15.3)

---
updated-dependencies:
- dependency-name: pascalgn/automerge-action
  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>
2022-06-01 17:29:37 -04:00
dependabot[bot] d6152f2f24 Bump dsaltares/fetch-gh-release-asset from 0.0.5 to 1.0.0 (#83)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-01 17:28:42 -04:00
dependabot[bot] cc3d96590f Bump hmarr/auto-approve-action from 2.0.0 to 2.2.1 (#77)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-01 13:19:34 -04:00
Jikoo 60c1d91e25 Simplify Maven check
Apparently Dependabot actually manages language labels when multiple options are enabled. Convenient!
2022-04-29 13:50:32 -04:00
Jikoo 7903942b51 Correct Action indentation
Wish it was more clearly indicated that the action was failing anywhere but the actions tab. No notification even, kind of annoying to have actually made a PR to double-check and then still have it fail.
2022-04-29 13:40:00 -04:00
Adam 22407aa865 Update dependencies, add Dependabot (#75) 2022-04-29 13:32:48 -04:00
Max Lee 173f49680b Log error message when failing to save player data (#72) 2022-04-05 17:10:58 -04:00
Jikoo 3103e8822b Bump version to 4.1.11-SNAPSHOT for development 2022-03-11 08:31:26 -05:00
Jikoo a547ab79fb Bump version to 4.1.10 for release 2022-03-11 08:31:11 -05:00
Jikoo bb75c723a7 Actually build 1.18.2 module 2022-02-28 22:16:26 -05:00
Jikoo 9bb94be7e2 Update to 1.18.2 2022-02-28 22:08:35 -05:00
Jikoo f78a5b4fba Ignore PlayerInventory owner as a viewer
If online, owner is always a viewer. If offline, they are never a viewer. There's no reason to count them - keeps inventory permanently cached while online if opened, even if not in use.
2022-01-27 11:37:53 -05:00
Jikoo 4ca222f031 Remove deprecated API usage 2022-01-27 11:16:06 -05:00
Jikoo 4fdfe15caa Update viewers for closed silent inventories
Closes #60
2022-01-27 11:15:34 -05:00
Jikoo 3aa31391be Add trace to InstantiationException
Closes #59
2022-01-27 10:03:12 -05:00
Jikoo abd529be66 Clean up compilation instructions for Mojang mappings 2022-01-19 10:23:26 -05:00
Jikoo 027d4e5637 Bump Spigot version to 1.18.1 2022-01-19 10:21:54 -05:00
Jikoo f8fb8cd034 Reduce duplicate configuration details
Adapters must now declare a `spigot.version` property.

* Greatly simplifies and speeds up retrieval of Spigot versions
2022-01-19 10:11:16 -05:00
Jikoo 6a31015ee2 Bump version to 4.1.10-SNAPSHOT for development 2021-12-02 16:15:34 -05:00
Jikoo 3e2b601997 Bump version to 4.1.9 for release 2021-12-02 16:15:22 -05:00
Jikoo 160ae47bf5 Correct wrong item list usage 2021-12-02 16:13:37 -05:00
Jikoo 98fbcebfd9 Fix non-translatable component, wrong flag value 2021-12-02 15:44:19 -05:00
Jikoo d236dd27b3 Update to 1.18-pre5 2021-11-23 13:00:42 -05:00
Jikoo dd90b5281f Use Java 17 for CI 2021-11-17 10:41:53 -05:00
Jikoo b4601bb6c1 Fix cached Spigot dependencies never being used
Since plugins have not been downloaded, get always fails when running in offline mode.
2021-10-16 19:16:08 -04:00
Jikoo e3d9cea027 Fix spigot dependency parsing
* Re-add cache
* Work around reflection issues with mvn help:evaluate on Java 9+
2021-10-16 18:03:38 -04:00
Jikoo 556a8bcfce Drop 1.16.5 support to use Mojang mappings
Because there is no option to create a `remapped-mojang` artifact for 1.16.5 and the whole point of the scripts is to save me time, 1.16.5 support is being removed earlier than usual.

Also fixes issues with NMS-based shulker collision check.
Closes #36
2021-10-16 16:53:59 -04:00
Jikoo 8599997e03 Work towards fixing shulkers 2021-10-16 16:11:18 -04:00
Jikoo ea99bd5fd4 Extract duplicate API-only code 2021-07-28 11:28:08 -04:00
蘭蘭露 Flandre_tw 2f36a4d4dc Add Chinese Simplified translation (#35) 2021-06-28 20:05:55 -04:00
蘭蘭露 Flandre_tw e5ebf11d32 Add Chinese Traditional translation (#34) 2021-06-28 20:05:15 -04:00
Jikoo 4336b454b7 Fix potential CME closing viewers without access
Closes #33
2021-06-24 08:21:26 -04:00
Jikoo b198931f30 Bump version to 4.1.9-SNAPSHOT for development 2021-06-11 18:40:48 -04:00
Jikoo 64af4dddb0 Bump version to 4.1.8 for release 2021-06-11 18:40:39 -04:00
Jikoo f613c0a522 Remove redundant load 2021-06-11 18:39:19 -04:00
Jikoo 1cae4c7a8e Correct window id 2021-06-11 18:39:07 -04:00
Jikoo ff7243db6a Fix script issues
Temporarily explicitly declare Spigot dependencies
I don't have the time to deal with whatever the difference between remote and local bash/maven versions is right now.
2021-06-11 12:05:38 -04:00
Jikoo 07a8e3b973 Add 1.17 support 2021-06-11 10:50:50 -04:00
Jikoo 9fccea60f7 Update copyright 2021-06-11 10:50:36 -04:00
Jikoo 9a2b379a64 Escape changelog for YAML
With 1.17 releasing soon, we'll get to see if this was the issue very shortly.
Worst case scenario the yaml is still messed up, but since the changelog is now the last element, unless it's so badly malformed that the entire action can't run the file should still upload even if the changelog does not.
2021-06-08 11:38:26 -04:00
Jikoo 4d800361d8 Bump version to 4.1.8-SNAPSHOT for development 2021-05-29 09:51:39 -04:00
Jikoo 2cfc55813b Bump version to 4.1.7 for release 2021-05-29 09:51:25 -04:00
Jikoo 0f41701429 Update annotations 2021-05-26 08:17:11 -04:00
Jikoo 9e0ca479a5 Fix Bukkit data not being loaded while offline
Closes #16
2021-05-26 08:16:25 -04:00
Jikoo bd207e948a Simplify dependency management
Use dependencyManagement for versioning shared dependencies
Use pluginManagement for versioning and configuring plugins
2021-04-23 13:40:22 -04:00
Jikoo 30425d2baa Don't bother validating names
Fixes problems with players from Geyser not being obtainable when using FloodGate.
2021-04-21 14:57:46 -04:00
Jikoo e3acb5384a Deprecate API for removed notification configuration 2021-04-05 19:12:45 -04:00
Jikoo 6c4818dfd9 Add method to access owner
Makes handling ISpecialInventory ownership much more consistent with a lot less spaghetti.
2021-03-28 11:12:49 -04:00
Jikoo 5b18656485 Fix incorrect player name in title
Closes #13
2021-03-28 11:10:39 -04:00
Jikoo 7ab86f2af9 Fix build 2021-03-22 12:20:58 -04:00
Jikoo 2b563e0e1b Don't convert release notes to HTML
We'll try this in another few months when there're enough changes to be worth it.
2021-03-22 12:07:53 -04:00
Jikoo 2a0e0d33d8 Don't shade annotations 2021-03-22 11:56:39 -04:00
Jikoo 889c2ffdab Bump version to 4.1.7-SNAPSHOT for development 2021-03-22 11:52:14 -04:00
Jikoo c440f618c9 Bump version to 4.1.6 for release 2021-03-22 11:52:04 -04:00
Jikoo ccc6f4b4a6 Fix CF release 2021-03-22 11:50:56 -04:00
Jikoo ae6c3bd292 Move release location 2021-03-22 10:24:48 -04:00
Jikoo 0e3bdb8741 Convert to HTML for CF 2021-03-22 10:21:09 -04:00
Jikoo e09e7c59c7 Fix release action 2021-03-22 09:42:52 -04:00
Jikoo 18c7916d79 Include release script 2021-03-22 09:41:36 -04:00
Jikoo 15ee6ef9a5 Fix revision number 2021-03-20 13:19:33 -04:00
Jikoo 24224e4f9d Add CurseForge release workflow 2021-03-20 13:06:33 -04:00
Jikoo 6563b4f6ce Migrate API-only functions out of PlayerDataManager
With the update to 1.16 there's no need to maintain multiple copies of the same code. Additionally, in 1.16 the action bar now supports JSON text.
2021-03-18 20:31:45 -04:00
Jikoo 1d5a836fd0 Partially automate release changelog 2021-03-18 16:38:41 -04:00
Jikoo 3ba82683ec Fix double chest custom names not displaying
Closes #175
2021-03-17 00:09:10 -04:00
Jikoo 6e5c332272 Extract player implementation 2021-03-16 23:49:17 -04:00
Jikoo 1c9d133ed1 Fix item delete in own inventory
Dragging items across top and bottom inventories with own inventory open resulted in the overlapping content being deleted.
2021-03-16 23:49:10 -04:00
Jikoo dad1e16c18 Fix item dupe in own inventory
Reduce desync for other actions in own inventory

Closes #182
2021-03-16 12:14:47 -04:00
Jikoo 519dd7da33 Fix cache breaking build on Spigot version change 2021-03-16 09:45:29 -04:00
Adam 1bd7932cc5 Drop 1.8/1.15/1.16.3 support (#185)
With the recent uptick in requests for support by people running 1.12 who have recently updated past Java 9, I revisited the supported versions.

First, the "modern" versions: 1.15 is run by 2.9% of servers, 1.16.3 by 5%. Since all of these versions are supported to ease the transition of updating servers and servers have updated, there's no real reason to keep them around. It's a lot easier if you can update a plugin and just have it work on both versions rather than push all the plugin updates to master with the server update, but none of these versions have any reason for long-term support.

Regarding heavily outdated server software: As of the time of writing, 1.8 and 1.12 have market shares of 8.6% and 8.5% of servers respectively.
Regarding 1.8: 1.8 support is already a bit wonky - with the changes made to inventory names, it's not (easily) possible to bridge the gap, and future changes will make that more and more difficult. People use 1.8 because they disliked the 1.9 combat changes, but there are plugins that fully rework combat to how it used to be. I have yet to hear a compelling argument that cannot be resolved with plugins. In the interest of my own sanity (handling and backporting the inventory name change in particular was a real humdinger) I will no longer be backporting changes to 1.8.
Regarding 1.12: I am honestly not sure why people are not updating. I get it, 1.13 and the flattening was a rough transition, but pretty much every developer active at the time bridged the gap. If your server is heavily dependent on some software only available at that time, you can live with other software available at that time. At the time of 1.12's release, Java 9 was not released. Either update your server, downgrade Java, or fork OpenInv and backport Java 9 support yourself.
2021-03-16 09:34:41 -04:00
Adam dad00d2d9c build: Add action to build and automatically release files (#170)
Auto-release is gonna get a nice live test at a later date.
2020-11-26 11:05:45 -05:00
Jikoo 502f661be3 fix: Update 1.15 adapter to 1.15.2 2020-11-26 10:47:51 -05:00
Jikoo 804d30bcc7 tpyo 2020-11-15 14:33:28 -05:00
Jikoo db8b67082b Fix up .gitignore a bit 2020-11-15 13:25:50 -05:00
Jikoo b2156edcc7 Fix mistake in InventoryAccess grabbing ender chest 2020-11-15 12:59:59 -05:00
Jikoo 45de495699 Simplify command executor assignment a little more 2020-11-15 12:26:36 -05:00
Jikoo f45d332727 Clarify download locations for older versions
Hopefully this will help people figure out where they've gone wrong. Maybe I should be stating supported versions in release notes too.
2020-11-15 12:22:25 -05:00
Jikoo 9705cb156b Bump version to 4.1.6-SNAPSHOT for development 2020-11-03 18:32:43 -05:00
Jikoo 1c45ef517f Bump version to 4.1.5 for release 2020-11-03 18:32:11 -05:00
Jikoo 3fee84fc58 Add support for 1.16.4, drop 1.16.1 2020-11-03 18:19:34 -05:00
Jikoo 00113cc4d4 These are my favorite version bumps 2020-09-15 13:47:12 -04:00
Jikoo 788f022e74 Mitigate some permissions confusion
For some reason, certain permissions plugins seem to not respect our declared default false nodes. To alleviate user confusion, these nodes will not be allowed to be used via wildcard internally.
2020-09-15 13:44:50 -04:00
Jikoo da55790fd2 Allow spectators to edit inventories
Requires permission OpenInv.spectate
Closes #155
2020-09-15 13:35:04 -04:00
Jikoo 1a6d513603 Reduce technical debt
* Reduce duplicate code
* Use more specific functional interfaces where available
* Fix some potential NPEs
* Remove some unnecessary/nonfunctional code
* Merge inventory listeners - no longer need to keep separate due to event availability
* Removed TODO items that probably won't ever be implemented. Good ideas, too drastic changes or too much work to maintain.
2020-09-15 12:51:49 -04:00
Jikoo a5b02ab26a Bump version to 4.1.5-SNAPSHOT for development 2020-08-22 12:22:00 -04:00
Jikoo 0c3ce2dfb3 Bump version to 4.1.4 for release 2020-08-22 12:21:22 -04:00
Jikoo a1f4649a09 Reword invalid version message, include releases link 2020-08-22 12:00:08 -04:00
Jikoo 100d0e12cd Update to 1.16.2 2020-08-12 00:06:20 -04:00
Jikoo 3e629798e5 Bump version to 4.1.4-SNAPSHOT for development 2020-07-15 23:23:31 -04:00
Jikoo 7c1f15974c Bump version to 4.1.3 for release 2020-07-15 23:23:03 -04:00
mfnalex d992237766 Add support for 3rd party plugins firing PlayerInteractEvent (#146)
* Add support for InvUnload

* generalize 3rd party plugins' event check
2020-07-15 22:38:38 -04:00
Jikoo c93464e643 Fix double chest loot generation with AnyContainer (#142) 2020-07-08 20:44:15 -04:00
Jikoo 56afefc82b Bump version to 4.1.3-SNAPSHOT for development 2020-06-25 19:40:07 -04:00
Jikoo f0a66570d2 Bump version to 4.1.2 for release 2020-06-25 19:39:38 -04:00
Jikoo 201a3578bf Add 1.16 support, drop 1.14 (#141) 2020-06-25 19:38:38 -04:00
Nathaniel Freeman 1228fc2de2 es_ES translation 2020-06-24 07:12:20 -04:00
NotMyFault 2561e75ae5 Fix typo 2020-06-02 19:01:33 -04:00
N0tMyFaultOG 80661b9465 Add german translation 2020-06-02 17:59:31 -04:00
Jikoo 53701da34f Bump version to 4.1.2-SNAPSHOT for development 2020-05-29 10:32:32 -04:00
Jikoo 9cf4712813 Bump version to 4.1.1 for release 2020-05-29 10:32:03 -04:00
Jikoo abe8fccdb5 Document new nodes 2020-05-29 10:27:50 -04:00
Jikoo 4499bb5453 Don't break behavior on existing setups
Permission to use /openinv and /openender will grant online/offline access so that existing permissions configurations do not have to be redone. Users looking to implement the new permissions will need to deny them.
2020-05-29 10:22:40 -04:00
Phoenix616 5405b7d7b9 Add permission for online/offline opening (Resolves #129) 2020-05-29 10:08:26 -04:00
129fc5e32c631b5007cb2cd8d45d416002b42bab 34199d12a4 Add localization for Brazilian Portuguese 2020-04-30 17:42:10 -04:00
Jikoo 67131ab9f9 Save after adding new keys 2020-04-24 10:47:51 -04:00
Jikoo 723381cb2b Fix IndexOutOfBounds on no args
Closes #128
2020-04-22 12:07:19 -04:00
Jikoo 34e7252d77 Correct lang + replacement mistake with inventory names 2020-04-19 18:04:10 -04:00
Jikoo a1b2df74cc Save existing vehicles if we had to load player (#22) 2020-04-19 17:54:45 -04:00
Jikoo 7210f9153e Bump version to 4.1.1-SNAPSHOT for development 2020-04-18 08:05:09 -04:00
Jikoo dcdfac0d3b Bump version to 4.1.0 for release 2020-04-18 08:04:38 -04:00
Jikoo c7b4554a6c It's been over 3 years :)
The common module was designed to prevent the internal modules depending on the core plugin. With the introduction of localization, this overcomplication became ever more exacerbated.
Probably will play around a bit more to remove freshly introduced static abuse before release.
Closes #61
2020-03-24 21:01:59 -04:00
Jikoo c51acb4e72 Add basic /searchcontainer
May be subject to change as I (very slowly) work towards console support for commands.
Closes #113
2020-03-15 10:43:08 -04:00
Jikoo 23d41cd6c8 Simplify and expand tab completion 2020-03-15 09:50:51 -04:00
Jikoo 8bc389496b Update cache for Java 8 2020-03-15 09:49:11 -04:00
Jikoo 1cc36d08bd Modify players before other plugins access them
Closes #123
2020-03-11 09:17:17 -04:00
Jikoo c8938f451d Update copyright 2020-02-01 08:03:15 -05:00
Jikoo 4f2665fedc Improve any/silent container commands
* Merged duplicate code
* Added on/off parameters - closes #120
* Added tab completion for parameters
2020-02-01 08:00:03 -05:00
unixminecraft 7d93bb06ad Updated /sc check command to correctly display if SilentChest is active or not. 2020-01-14 22:40:01 -05:00
Jikoo 19c491411f Bump version to 4.0.9-SNAPSHOT for development 2019-12-12 14:37:03 -05:00
Jikoo 65c6eb2693 Bump version to 4.0.8 for release 2019-12-12 14:36:38 -05:00
Jikoo ab1f4ace8a Fix AnyContainer breaking modified ender chest sizes 2019-12-12 14:33:42 -05:00
Jikoo e7dc52bee4 Drop support for 1.13, latest 2 versions only. 2019-12-12 14:21:33 -05:00
Jikoo a9c5799677 Merge branch 'master' of https://github.com/lishid/OpenInv 2019-12-12 13:59:38 -05:00
Andre LeBlanc 6d6257720a add support for 1.15 2019-12-12 13:59:12 -05:00
Andre LeBlanc 3e235fa688 add support for 1.15 2019-12-12 12:49:21 -05:00
Jikoo dd02355ac2 Properly remove SilentContainer viewers
Closes #115
2019-12-03 13:50:27 -05:00
Jikoo 4d3f5d67a8 Update copyright
Practically should make it 2020 at this rate, whoops

(cherry picked from commit ff76f2b95ac5ea2a32a42b5dcd82a5e5b762b831)
2019-12-03 13:32:41 -05:00
Jikoo 7ad5faa2ce Clean up maven instructions
(cherry picked from commit b92543e0789518183cfa685302d7938932b09550)
2019-12-03 13:32:37 -05:00
Jikoo c28383ef36 Work around 1.14 tab completion issues 2019-10-01 08:30:28 -04:00
Jikoo 5fbab1ab8f Bump version to 4.0.8-SNAPSHOT for development 2019-09-29 19:43:14 -04:00
Jikoo d97b092978 Bump version to 4.0.7 for release 2019-09-29 19:42:59 -04:00
Jikoo 154bf56c2a Include api-version in plugin.yml
Closes #111, #112
2019-09-29 19:40:06 -04:00
William Blake Galbreath d65a9caeb2 Add support for varying sizes of inventories 2019-08-01 21:04:23 -04:00
Jikoo b3c4253606 Bump version to 4.0.7-SNAPSHOT for development 2019-07-21 13:30:02 -04:00
Jikoo b64000e89b Bump version to 4.0.6 for release 2019-07-21 13:29:45 -04:00
Jikoo 3faf41ef18 More clear error when loot is not generated 2019-07-19 18:16:36 -04:00
Jikoo db27d25a93 More Bukkit API, less breaking with new versions 2019-07-19 18:14:14 -04:00
Jikoo 0f02e6fe66 Right, 1.13 is a thing 2019-07-19 18:12:06 -04:00
Jikoo ec07637e9f Bump version to 4.0.6-SNAPSHOT for development 2019-07-19 16:26:24 -04:00
Jikoo b269f3509c Bump version to 4.0.5 for release 2019-07-19 16:26:03 -04:00
Jikoo f2e4c8a3ad Fix barrel triggering AnyContainer (#108) 2019-07-19 16:24:58 -04:00
Jikoo 35839f1548 Bump version to 4.0.5-SNAPSHOT for development 2019-07-19 07:21:37 -04:00
Jikoo a4a462a1fa Bump version to 4.0.4 for release 2019-07-19 07:21:16 -04:00
Jikoo b9f901af66 Add silent barrels - fixes #106 2019-07-19 07:18:23 -04:00
Jikoo 1333056613 Correct item order - fixes #107 2019-07-19 07:13:11 -04:00
Jikoo fb23df313a Bump version to 4.0.4-SNAPSHOT for development 2019-05-27 20:53:44 -04:00
Jikoo 96dc400c83 Bump version to 4.0.3 for release 2019-05-27 20:53:02 -04:00
Jikoo b801d36f12 Turns out api-version below 1.13 is unsupported.
Compiling for 1.8, no api-version.
2019-05-27 20:52:49 -04:00
Jikoo 4c27599dd9 Bump version to 4.0.3-SNAPSHOT for development 2019-05-27 20:23:16 -04:00
85 changed files with 6612 additions and 5490 deletions

No files matched your search

+10
View File
@@ -0,0 +1,10 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "monthly"
- package-ecosystem: "maven"
directory: "/"
schedule:
interval: "monthly"
+89
View File
@@ -0,0 +1,89 @@
name: OpenInv CI
on:
push:
pull_request_target:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: '17'
cache: 'maven'
# Install Spigot dependencies if necessary.
- name: Install Spigot Dependencies
run: . scripts/install_spigot_dependencies.sh
- name: Build With Maven
run: mvn -e clean package -am -P all
# Upload artifacts
- name: Upload Distributable Jar
id: upload-final
uses: actions/upload-artifact@v3
with:
name: dist
path: ./target/OpenInv.jar
- name: Upload API Jar
id: upload-api
uses: actions/upload-artifact@v3
with:
name: api
path: ./api/target/openinvapi*.jar
merge-dependabot:
name: Auto-merge Dependabot PRs
needs: [ build ]
if: "github.event.name == 'pull_request_target'
&& github.actor == 'dependabot[bot]'
&& contains( github.event.pull_request.labels.*.name, 'java')"
runs-on: ubuntu-latest
steps:
- name: Approve
uses: hmarr/auto-approve-action@v2.2.1
with:
github-token: "${{ secrets.GITHUB_TOKEN }}"
- name: Merge
uses: pascalgn/automerge-action@v0.15.3
env:
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
MERGE_LABELS: "dependencies"
MERGE_METHOD: "squash"
release:
name: Create Github Release
needs: [ build ]
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
steps:
# Fetch all history - used to assemble changelog.
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Set Release Variables
run: bash ./scripts/set_release_env.sh
- name: Download Artifact
uses: actions/download-artifact@v3
with:
name: dist
path: dist
- name: Create Release
id: create-release
uses: softprops/action-gh-release@v0.1.14
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
name: ${{ env.VERSIONED_NAME }}
body: ${{ env.GENERATED_CHANGELOG }}
draft: true
prerelease: false
files: ./dist/OpenInv.jar
+37
View File
@@ -0,0 +1,37 @@
name: Release to CurseForge
on:
release:
types: [ released ]
jobs:
curseforge_release:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Fetch Github Release Asset
uses: dsaltares/fetch-gh-release-asset@1.0.0
with:
token: ${{ secrets.GITHUB_TOKEN }}
version: ${{ github.event.release.id }}
file: OpenInv.jar
- name: Set CurseForge Variables
run: . scripts/set_curseforge_env.sh "${{ github.event.release.body }}"
- name: Create CurseForge Release
uses: itsmeow/curseforge-upload@v3
with:
token: "${{ secrets.CURSEFORGE_TOKEN }}"
project_id: 31432
game_endpoint: minecraft
file_path: ./OpenInv.jar
display_name: "${{ github.event.release.name }}"
game_versions: "${{ env.CURSEFORGE_MINECRAFT_VERSIONS }}"
release_type: release
changelog_type: markdown
changelog: "${{ env.CURSEFORGE_CHANGELOG }}"
+7 -8
View File
@@ -1,11 +1,10 @@
**/.settings
**/.gitignore
**/.project
**/.classpath
**/.idea
**/.iml
**/target
**/bin
**/out
**dependency-reduced-pom.xml
**pom.xml.versionsBackup
**/.idea/
**.iml
**/target/
**/bin/
**/out/
**/dependency-reduced-pom.xml
**/pom.xml.versionsBackup
+45 -62
View File
@@ -15,48 +15,7 @@ OpenInv is a [Bukkit plugin](https://dev.bukkit.org/bukkit-plugins/openinv/) whi
- **AnyContainer**: Open containers, even if blocked by ocelots or blocks.
## Commands
<table width=100%>
<tr>
<th width=175px>Command</th>
<th>Aliases</th>
<th>Description</th>
</tr>
<tr>
<td>/openinv [player]</td>
<td>oi, inv, open</td>
<td>Open a player's inventory. If unspecified, will select last player opened or own if none opened previously.</td>
</tr>
<tr>
<td>/openender [player]</td>
<td>oe</td>
<td>Open a player's ender chest. If unspecified, will select last player opened or own if none opened previously.</td>
</tr>
<tr>
<td>/searchinv &ltitem&gt [minAmount]</td>
<td>si</td>
<td>Lists all online players that have a certain item in their inventory.</td>
</tr>
<tr>
<td>/searchender &ltitem&gt [minAmount]</td>
<td>se</td>
<td>Lists all online players that have a certain item in their ender chest.</td>
</tr>
<tr>
<td>/searchenchant &lt[enchantment] [MinLevel]&gt</td>
<td>searchenchants</td>
<td>Lists all online players with a specific enchantment.</td>
</tr>
<tr>
<td>/anycontainer [check]</td>
<td>ac, anychest</td>
<td>Check or toggle the AnyContainer function, allowing opening blocked containers.</td>
</tr>
<tr>
<td>/silentcontainer [check]</td>
<td>sc, silentchest</td>
<td>Check or toggle the SilentContainer function, allowing opening containers silently.</td>
</tr>
</table>
See [the wiki](https://github.com/Jikoo/OpenInv/wiki/Commands).
## Permissions
<table>
@@ -80,6 +39,14 @@ OpenInv is a [Bukkit plugin](https://dev.bukkit.org/bukkit-plugins/openinv/) whi
<td>OpenInv.editinv</td>
<td>Required to make changes to open inventories.</td>
</tr>
<tr>
<td>OpenInv.openonline</td>
<td>Allows users to open online players' inventories. For compatibility reasons this is granted by the nodes OpenInv.openinv and OpenInv.openender.</td>
</tr>
<tr>
<td>OpenInv.openoffline</td>
<td>Allows users to open offline players' inventories. For compatibility reasons this is granted by the nodes OpenInv.openinv and OpenInv.openender.</td>
</tr>
<tr>
<td>OpenInv.openender</td>
<td>Required to use /openender.</td>
@@ -128,29 +95,45 @@ OpenInv is a [Bukkit plugin](https://dev.bukkit.org/bukkit-plugins/openinv/) whi
<td>OpenInv.silent.default</td>
<td>Cause SilentContainer to be enabled by default.</td>
</tr>
<tr>
<td>OpenInv.spectate</td>
<td>Allows users in spectate gamemode to edit inventories.</td>
</tr>
</table>
## For Developers
To compile, the relevant Craftbukkit/Spigot jars must be installed in your local repository using the install plugin.
Ex: `mvn install:install-file -Dpackaging=jar -Dfile=spigot-1.11-R0.1-SNAPSHOT.jar -DgroupId=org.spigotmc -DartifactId=spigot -Dversion=1.11-R0.1-SNAPSHOT`
To compile for a specific version or set of versions, you'll need to use a profile. Provided profiles are `latest`, `recent` (last 2 major Minecraft versions), and `all`. Select an existing profile using the `-P` argument (ex: `mvn clean package -am -P all`) or make your own. For more information, check out the [official guide](http://maven.apache.org/guides/introduction/introduction-to-profiles.html).
The final file is target/OpenInv.jar
## License
### As a Dependency
The OpenInv API is available via [JitPack](https://jitpack.io/).
```xml
<repositories>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>
```
Copyright (C) 2011-2018 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/>.
```xml
<dependencies>
<dependency>
<groupId>com.github.jikoo.OpenInv</groupId>
<artifactId>openinvapi</artifactId>
<version>${openinv.version}</version>
</dependency>
</dependencies>
```
### 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 for a single version, specify the module you are targeting:
`mvn -pl $moduleName -am clean install`
`$moduleName` is the name of the module, i.e. `internal/v1_18_R1`.
To compile for a set of versions, use a profile. Select a profile using the `-P` argument:
`mvn clean package -am -P all`
The only provided profile is `all`. The final file is `target/OpenInv.jar`
For more information, check out the [official Maven guide](http://maven.apache.org/guides/introduction/introduction-to-profiles.html).
+5 -30
View File
@@ -1,5 +1,5 @@
<!--
~ Copyright (C) 2011-2018 lishid. All rights reserved.
~ Copyright (C) 2011-2022 lishid. All rights reserved.
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
@@ -19,9 +19,9 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.lishid</groupId>
<artifactId>openinvparent</artifactId>
<version>4.0.2</version>
<groupId>com.lishid</groupId>
<version>4.2.1</version>
</parent>
<artifactId>openinvapi</artifactId>
@@ -29,44 +29,19 @@
<dependencies>
<dependency>
<groupId>org.jetbrains</groupId>
<artifactId>annotations</artifactId>
<version>17.0.0</version>
<groupId>org.jetbrains</groupId>
</dependency>
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
<version>1.8.8-R0.1-SNAPSHOT</version>
<scope>provided</scope>
<groupId>org.spigotmc</groupId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.0.0</version>
<configuration>
<minimizeJar>true</minimizeJar>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
+171 -131
View File
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2011-2019 lishid. All rights reserved.
* Copyright (C) 2011-2022 lishid. All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -24,6 +24,7 @@ import com.lishid.openinv.internal.ISpecialPlayerInventory;
import com.lishid.openinv.util.InventoryAccess;
import com.lishid.openinv.util.StringMetric;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
@@ -35,53 +36,120 @@ import org.jetbrains.annotations.Nullable;
/**
* Interface defining behavior for the OpenInv plugin.
*
* @author Jikoo
*/
public interface IOpenInv {
/**
* Check the configuration value for whether or not 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.
* 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();
/**
* Gets the active ISilentContainer implementation.
* 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 the ISilentContainer
* @return false unless configured otherwise
* @since 4.2.0
*/
boolean disableOfflineAccess();
/**
* 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();
/**
* Gets the active IInventoryAccess implementation.
*
* @return the IInventoryAccess
* @throws IllegalStateException if the server version is unsupported
* @deprecated Use static {@link InventoryAccess} methods.
*/
@Deprecated
@Deprecated(forRemoval = true)
default @NotNull IInventoryAccess getInventoryAccess() {
return new InventoryAccess();
}
/**
* Gets the provided player's AnyChest setting.
*
* @param player the OfflinePlayer
* @return true if AnyChest is enabled
* @throws IllegalStateException if the server version is unsupported
* @deprecated Use {@link #getAnyContainerStatus(OfflinePlayer)}. Not all containers are chests.
*/
boolean getPlayerAnyChestStatus(@NotNull OfflinePlayer player);
@Deprecated(forRemoval = true, since = "4.2.0")
default boolean getPlayerAnyChestStatus(@NotNull OfflinePlayer offline) {
return getAnyContainerStatus(offline);
}
/**
* Gets a unique identifier by which the OfflinePlayer can be referenced. Using the value
* returned to look up a Player will generally be much faster for later implementations.
* 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);
/**
* @deprecated Use {@link #setAnyContainerStatus(OfflinePlayer, boolean)}. Not all containers are chests.
*/
@Deprecated(forRemoval = true, since = "4.2.0")
default void setPlayerAnyChestStatus(@NotNull OfflinePlayer offline, boolean status) {
setAnyContainerStatus(offline, 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);
/**
* @deprecated Use {@link #getSilentContainerStatus(OfflinePlayer)}. Not all containers are chests.
*/
@Deprecated(forRemoval = true, since = "4.2.0")
default boolean getPlayerSilentChestStatus(@NotNull OfflinePlayer offline) {
return getSilentContainerStatus(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);
/**
* @deprecated Use {@link #setSilentContainerStatus(OfflinePlayer, boolean)}. Not all containers are chests.
*/
@Deprecated(forRemoval = true, since = "4.2.0")
default void setPlayerSilentChestStatus(@NotNull OfflinePlayer offline, boolean status) {
setSilentContainerStatus(offline, 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 a unique identifier by which the OfflinePlayer can be referenced.
*
* @deprecated Use {@link OfflinePlayer#getUniqueId()} and {@link UUID#toString()}. This was necessary for non-UUID
* versions of Minecraft, but support for them has been dropped for years.
* @param offline the OfflinePlayer
* @return the identifier
* @throws IllegalStateException if the server version is unsupported
@@ -91,61 +159,65 @@ public interface IOpenInv {
}
/**
* Gets a player's SilentChest setting.
* Get an {@link ISpecialEnderChest} for a user.
*
* @param offline the OfflinePlayer
* @return true if SilentChest is enabled
* @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
*/
boolean getPlayerSilentChestStatus(@NotNull OfflinePlayer offline);
/**
* Gets an ISpecialEnderChest for the given Player.
*
* @param player the Player
* @param online true if the Player is currently online
* @return the ISpecialEnderChest
* @throws IllegalStateException if the server version is unsupported
* @throws InstantiationException if the ISpecialEnderChest could not be instantiated
* @throws InstantiationException if there was an issue creating the inventory
*/
@NotNull ISpecialEnderChest getSpecialEnderChest(@NotNull Player player, boolean online) throws InstantiationException;
/**
* Gets an ISpecialPlayerInventory for the given Player.
* Get an {@link ISpecialPlayerInventory} for a user.
*
* @param player the Player
* @param online true if the Player is currently online
* @return the ISpecialPlayerInventory
* @throws IllegalStateException if the server version is unsupported
* @throws InstantiationException if the ISpecialPlayerInventory could not be instantiated
* @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;
/**
* Checks if the server version is supported by OpenInv.
* Open an {@link ISpecialInventory} for a {@link Player}.
*
* @return true if the server version is supported
* @param player the viewer
* @param inventory the inventory to open
* @return the resulting {@link InventoryView}
*/
boolean isSupportedVersion();
@Nullable InventoryView openInventory(@NotNull Player player, @NotNull ISpecialInventory inventory);
/**
* Load a Player from an OfflinePlayer. May return null under some circumstances.
* Check if a {@link Player} is currently loaded by OpenInv.
*
* @param offline the OfflinePlayer to load a Player for
* @return the Player, or null
* @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);
/**
* 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);
/**
* Get an OfflinePlayer by name.
* <p>
* Note: This method is potentially very heavily blocking. It should not ever be called on the
* 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 name of the Player
* @return the OfflinePlayer with the closest matching name or null if no players have ever logged in
* @param name the string to match
* @return the user with the closest matching name
*/
default @Nullable OfflinePlayer matchPlayer(@NotNull String name) {
@@ -153,10 +225,7 @@ public interface IOpenInv {
if (Bukkit.getServer().isPrimaryThread()) {
this.getLogger().warning("Call to OpenInv#matchPlayer made on the main thread!");
this.getLogger().warning("This can cause the server to hang, potentially severely.");
this.getLogger().warning("Trace:");
for (StackTraceElement element : new Throwable().fillInStackTrace().getStackTrace()) {
this.getLogger().warning(element.toString());
}
this.getLogger().log(Level.WARNING, "Current stack trace", new Throwable("Current stack trace"));
}
OfflinePlayer player;
@@ -164,37 +233,38 @@ public interface IOpenInv {
try {
UUID uuid = UUID.fromString(name);
player = Bukkit.getOfflinePlayer(uuid);
// Ensure player is a real player, otherwise return null
// Ensure player is an existing player.
if (player.hasPlayedBefore() || player.isOnline()) {
return player;
}
// Return null otherwise.
return null;
} catch (IllegalArgumentException ignored) {
// Not a UUID
}
// Ensure name is valid if server is in online mode to avoid unnecessary searching
if (Bukkit.getServer().getOnlineMode() && !name.matches("[a-zA-Z0-9_]{3,16}")) {
return null;
}
// Exact online match first.
player = Bukkit.getServer().getPlayerExact(name);
if (player != null) {
return player;
}
// Exact offline match second - ensure offline access works when matchable users are online.
player = Bukkit.getServer().getOfflinePlayer(name);
if (player.hasPlayedBefore()) {
return player;
}
// Inexact online match.
player = Bukkit.getServer().getPlayer(name);
if (player != null) {
return player;
}
// Finally, inexact offline match.
float bestMatch = 0;
for (OfflinePlayer offline : Bukkit.getServer().getOfflinePlayers()) {
if (offline.getName() == null) {
@@ -219,88 +289,58 @@ public interface IOpenInv {
}
/**
* Open an ISpecialInventory for a Player.
*
* @param player the Player
* @param inventory the ISpecialInventory
* @return the InventoryView for the opened ISpecialInventory
* @deprecated OpenInv uses action bar chat for notifications. Whether they show is based on language settings.
*/
@Nullable InventoryView openInventory(@NotNull Player player, @NotNull ISpecialInventory inventory);
@Deprecated(forRemoval = true)
default boolean notifyAnyChest() {
return true;
}
/**
* Check the configuration value for whether or not OpenInv displays a notification to the user
* when a container is activated with AnyChest.
*
* @return true unless configured otherwise
* @deprecated OpenInv uses action bar chat for notifications. Whether they show is based on language settings.
*/
boolean notifyAnyChest();
@Deprecated(forRemoval = true)
default boolean notifySilentChest() {
return true;
}
/**
* Check the configuration value for whether or not OpenInv displays a notification to the user
* when a container is activated with SilentChest.
*
* @return true unless configured otherwise
* @deprecated see {@link #retainPlayer(Player, Plugin)}
*/
boolean notifySilentChest();
@Deprecated(forRemoval = true, since = "4.2.0")
default void releasePlayer(@NotNull Player player, @NotNull Plugin plugin) {}
/**
* Mark a Player as no longer in use by a Plugin to allow OpenInv to remove it from the cache
* when eligible.
* @deprecated OpenInv no longer uses an internal cache beyond maintaining copies of currently open inventories.
* If you wish to use/modify a player, ensure either {@link IOpenInv#isPlayerLoaded(UUID)} is false or the player
* instance is the same memory address as the one in use by OpenInv.
* <pre>
* public &#64;NotNull Player savePlayerData(&#64;NotNull Player player) {
* IOpenInv openInv = ...
* if (!openInv.disableSaving() && openInv.isPlayerLoaded(player.getUniqueId())) {
* Player openInvLoadedPlayer = openInv.loadPlayer(myInUsePlayer);
* if (openInvLoadedPlayer != player) {
* // The copy loaded by OpenInv is not the same as our loaded copy. Push our changes.
* copyPlayerModifications(player, openInvLoadedPlayer);
* }
* // OpenInv will handle saving data when the player is unloaded.
* // Optionally, to be sure our changes will persist, save now.
* // openInvLoadedPlayer.saveData();
* return openInvLoadedPlayer;
* }
*
* @param player the Player
* @param plugin the Plugin no longer holding a reference to the Player
* @throws IllegalStateException if the server version is unsupported
* player.saveData();
* return player;
* }
* </pre>
*/
void releasePlayer(@NotNull Player player, @NotNull Plugin plugin);
@Deprecated(forRemoval = true, since = "4.2.0")
default void retainPlayer(@NotNull Player player, @NotNull Plugin plugin) {}
/**
* Mark a Player as in use by a Plugin to prevent it from being removed from the cache. Used to
* prevent issues with multiple copies of the same Player being loaded such as lishid#49.
* Changes made to loaded copies overwrite changes to the others when saved, leading to
* duplication bugs and more.
* <p>
* When finished with the Player object, be sure to call {@link #releasePlayer(Player, Plugin)}
* to prevent the cache from keeping it stored until the plugin is disabled.
* <p>
* When using a Player object from OpenInv, you must handle the Player coming online, replacing
* your Player reference with the Player from the PlayerJoinEvent. In addition, you must change
* any values in the Player to reflect any unsaved alterations to the existing Player which do
* not affect the inventory or ender chest contents.
* <p>
* OpenInv only saves player data when unloading a Player from the cache, and then only if
* {@link #disableSaving()} returns false. If you are making changes that OpenInv does not cause
* to persist when a Player logs in as noted above, it is suggested that you manually call
* {@link Player#saveData()} when releasing your reference to ensure your changes persist.
* Forcibly close inventories of and unload any cached data for a user.
*
* @param player the Player
* @param plugin the Plugin holding the reference to the Player
* @throws IllegalStateException if the server version is unsupported
*/
void retainPlayer(@NotNull Player player, @NotNull Plugin plugin);
/**
* Sets a player's AnyChest setting.
*
* @param offline the OfflinePlayer
* @param status the status
* @throws IllegalStateException if the server version is unsupported
*/
void setPlayerAnyChestStatus(@NotNull OfflinePlayer offline, boolean status);
/**
* Sets a player's SilentChest setting.
*
* @param offline the OfflinePlayer
* @param status the status
* @throws IllegalStateException if the server version is unsupported
*/
void setPlayerSilentChestStatus(@NotNull OfflinePlayer offline, boolean status);
/**
* Forcibly unload a cached Player's data.
*
* @param offline the OfflinePlayer to unload
* @throws IllegalStateException if the server version is unsupported
* @param offline the {@link OfflinePlayer} to unload
*/
void unload(@NotNull OfflinePlayer offline);
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2011-2018 lishid. All rights reserved.
* Copyright (C) 2011-2022 lishid. All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -16,46 +16,145 @@
package com.lishid.openinv.internal;
import org.bukkit.Material;
import org.bukkit.block.Barrel;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.block.BlockState;
import org.bukkit.block.EnderChest;
import org.bukkit.block.ShulkerBox;
import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.type.Chest;
import org.bukkit.entity.Cat;
import org.bukkit.entity.Player;
import org.bukkit.util.BoundingBox;
import org.jetbrains.annotations.NotNull;
public interface IAnySilentContainer {
/**
* Opens the container at the given coordinates for the Player. If you do not want blocked
* containers to open, be sure to check {@link #isAnyContainerNeeded(Player, Block)}
* first.
* 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 Player opening the container
* @param silent whether the container's noise is to be silenced
* @param block the Block
* @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);
/**
* Closes the Player's currently open container silently, if necessary.
* Perform operations required to close the current container silently.
*
* @param player the Player closing a container
* @param player the {@link Player} closing a container
*/
void deactivateContainer(@NotNull Player player);
/**
* Checks if the container at the given coordinates is blocked.
*
* @param player the Player opening the container
* @param block the Block
* @param player the player opening the container
* @param block the {@link Block} of the container
* @return true if the container is blocked
* @deprecated use {@link #isAnyContainerNeeded(Block)}
*/
boolean isAnyContainerNeeded(@NotNull Player player, @NotNull Block block);
@Deprecated(forRemoval = true, since = "4.1.9")
default boolean isAnyContainerNeeded(@NotNull Player player, @NotNull Block block) {
return isAnyContainerNeeded(block);
}
/**
* Checks if the given block is a container which can be unblocked or silenced.
* Check if the container at the given coordinates is blocked.
*
* @param block the BlockState
* @return true if the Block is a supported container
* @param block the {@link Block} of the container
* @return true if the container is blocked
*/
boolean isAnySilentContainer(@NotNull Block block);
default boolean isAnyContainerNeeded(@NotNull Block block) {
BlockState blockState = block.getState();
// Barrels do not require AnyContainer.
if (blockState instanceof Barrel) {
return false;
}
// Enderchests require a non-occluding block on top to open.
if (blockState instanceof EnderChest) {
return block.getRelative(0, 1, 0).getType().isOccluding();
}
// Shulker boxes require 1/2 a block clear in the direction they open.
if (blockState instanceof ShulkerBox shulker) {
if (isShulkerBlocked(shulker)) {
return true;
}
}
if (!(blockState instanceof org.bukkit.block.Chest)) {
return false;
}
if (isChestBlocked(block)) {
return true;
}
BlockData blockData = block.getBlockData();
if (!(blockData instanceof Chest chest) || ((Chest) blockData).getType() == Chest.Type.SINGLE) {
return false;
}
int ordinal = (chest.getFacing().ordinal() + 4 + (chest.getType() == Chest.Type.RIGHT ? -1 : 1)) % 4;
BlockFace relativeFace = BlockFace.values()[ordinal];
org.bukkit.block.Block relative = block.getRelative(relativeFace);
if (relative.getType() != block.getType()) {
return false;
}
BlockData relativeData = relative.getBlockData();
if (!(relativeData instanceof Chest relativeChest)) {
return false;
}
if (relativeChest.getFacing() != chest.getFacing()
|| relativeChest.getType() != (chest.getType() == Chest.Type.RIGHT ? Chest.Type.LEFT : Chest.Type.RIGHT)) {
return false;
}
return isChestBlocked(relative);
}
/**
* Check if a {@link ShulkerBox} cannot be opened under ordinary circumstances.
*
* @param shulkerBox the shulker box container
* @return whether the container is blocked
*/
boolean isShulkerBlocked(@NotNull ShulkerBox shulkerBox);
/**
* 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), entity -> entity instanceof Cat).size() > 0;
}
/**
* 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
*/
default boolean isAnySilentContainer(@NotNull Block block) {
if (block.getType() == Material.ENDER_CHEST) {
return true;
}
BlockState state = block.getState();
return state instanceof org.bukkit.block.Chest
|| state instanceof org.bukkit.block.ShulkerBox
|| state instanceof org.bukkit.block.Barrel;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2011-2018 lishid. All rights reserved.
* Copyright (C) 2011-2022 lishid. All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -16,49 +16,47 @@
package com.lishid.openinv.internal;
import com.lishid.openinv.util.InventoryAccess;
import org.bukkit.inventory.Inventory;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@Deprecated
/**
* @deprecated Use static {@link InventoryAccess} methods.
*/
@Deprecated(forRemoval = true)
public interface IInventoryAccess {
/**
* Gets an ISpecialEnderChest from an Inventory or null if the Inventory is not backed by an
* ISpecialEnderChest.
*
* @param inventory the Inventory
* @return the ISpecialEnderChest or null
* @deprecated Use static {@link InventoryAccess} methods.
*/
@Deprecated
@Nullable ISpecialEnderChest getSpecialEnderChest(@NotNull Inventory inventory);
@Deprecated(forRemoval = true)
default @Nullable ISpecialEnderChest getSpecialEnderChest(@NotNull Inventory inventory) {
return InventoryAccess.getEnderChest(inventory);
}
/**
* Gets an ISpecialPlayerInventory from an Inventory or null if the Inventory is not backed by
* an ISpecialPlayerInventory.
*
* @param inventory the Inventory
* @return the ISpecialPlayerInventory or null
* @deprecated Use static {@link InventoryAccess} methods.
*/
@Deprecated
@Nullable ISpecialPlayerInventory getSpecialPlayerInventory(@NotNull Inventory inventory);
@Deprecated(forRemoval = true)
default @Nullable ISpecialPlayerInventory getSpecialPlayerInventory(@NotNull Inventory inventory) {
return InventoryAccess.getPlayerInventory(inventory);
}
/**
* Check if an Inventory is an ISpecialEnderChest implementation.
*
* @param inventory the Inventory
* @return true if the Inventory is backed by an ISpecialEnderChest
* @deprecated Use static {@link InventoryAccess} methods.
*/
@Deprecated
boolean isSpecialEnderChest(@NotNull Inventory inventory);
@Deprecated(forRemoval = true)
default boolean isSpecialEnderChest(@NotNull Inventory inventory) {
return InventoryAccess.isEnderChest(inventory);
}
/**
* Check if an Inventory is an ISpecialPlayerInventory implementation.
*
* @param inventory the Inventory
* @return true if the Inventory is backed by an ISpecialPlayerInventory
* @deprecated Use static {@link InventoryAccess} methods.
*/
@Deprecated
boolean isSpecialPlayerInventory(@NotNull Inventory inventory);
@Deprecated(forRemoval = true)
default boolean isSpecialPlayerInventory(@NotNull Inventory inventory) {
return InventoryAccess.isPlayerInventory(inventory);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2011-2018 lishid. All rights reserved.
* Copyright (C) 2011-2022 lishid. All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -16,6 +16,9 @@
package com.lishid.openinv.internal;
/**
* An {@link ISpecialInventory} representing an ender chest.
*/
public interface ISpecialEnderChest extends ISpecialInventory {
}
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2011-2019 lishid. All rights reserved.
* Copyright (C) 2011-2022 lishid. All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -16,36 +16,49 @@
package com.lishid.openinv.internal;
import org.bukkit.entity.HumanEntity;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import org.jetbrains.annotations.NotNull;
/**
* Interface defining behavior for special inventories backed by other inventories' content listings.
*/
public interface ISpecialInventory {
/**
* Gets the Inventory associated with this ISpecialInventory.
* Get the {@link Inventory} associated with this {@code ISpecialInventory}.
*
* @return the Inventory
* @return the Bukkit inventory
*/
@NotNull Inventory getBukkitInventory();
/**
* Sets the Player associated with this ISpecialInventory online.
* Set the owning {@link Player} instance to a newly-joined user.
*
* @param player the Player coming online
* @param player the user coming online
*/
void setPlayerOnline(@NotNull Player player);
/**
* Sets the Player associated with this ISpecialInventory offline.
* Mark the owner of the inventory offline.
*/
void setPlayerOffline();
/**
* Gets whether or not this ISpecialInventory is in use.
* Get whether the inventory is being viewed by any users.
*
* @return true if the ISpecialInventory is in use
* @return true if the inventory is being viewed
*/
boolean isInUse();
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();
}
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2011-2018 lishid. All rights reserved.
* Copyright (C) 2011-2022 lishid. All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -16,6 +16,35 @@
package com.lishid.openinv.internal;
import java.util.List;
import org.bukkit.entity.HumanEntity;
import org.bukkit.inventory.Inventory;
/**
* 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);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2011-2019 lishid. All rights reserved.
* Copyright (C) 2011-2022 lishid. All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -18,128 +18,106 @@ package com.lishid.openinv.util;
import com.lishid.openinv.internal.IInventoryAccess;
import com.lishid.openinv.internal.ISpecialEnderChest;
import com.lishid.openinv.internal.ISpecialInventory;
import com.lishid.openinv.internal.ISpecialPlayerInventory;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import org.bukkit.Bukkit;
import org.bukkit.inventory.Inventory;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class InventoryAccess implements IInventoryAccess {
public final class InventoryAccess implements IInventoryAccess {
private static Class<?> craftInventory = null;
private static Method getInventory = null;
static {
String packageName = Bukkit.getServer().getClass().getPackage().getName();
String version = packageName.substring(packageName.lastIndexOf('.') + 1);
try {
craftInventory = Class.forName("org.bukkit.craftbukkit." + version + ".inventory.CraftInventory");
} catch (ClassNotFoundException ignored) {}
try {
craftInventory = Class.forName(packageName + ".inventory.CraftInventory");
getInventory = craftInventory.getDeclaredMethod("getInventory");
} catch (NoSuchMethodException ignored) {}
} catch (ClassNotFoundException | NoSuchMethodException ignored) {}
}
/**
* @deprecated use {@link #isUsable()}
*/
@Deprecated(forRemoval = true)
public static boolean isUseable() {
return isUsable();
}
public static boolean isUsable() {
return craftInventory != null && getInventory != 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) {
if (craftInventory.isAssignableFrom(inventory.getClass())) {
try {
return getInventory.invoke(inventory) instanceof ISpecialPlayerInventory;
} catch (ReflectiveOperationException ignored) {}
}
return grabFieldOfTypeFromObject(ISpecialPlayerInventory.class, inventory) != null;
return getPlayerInventory(inventory) != null;
}
public static ISpecialPlayerInventory getPlayerInventory(@NotNull Inventory inventory) {
Object inv = null;
if (craftInventory.isAssignableFrom(inventory.getClass())) {
try {
inv = getInventory.invoke(inventory);
} catch (ReflectiveOperationException ignored) {}
}
if (inv == null) {
inv = grabFieldOfTypeFromObject(ISpecialPlayerInventory.class, inventory);
}
if (inv instanceof ISpecialPlayerInventory) {
return (ISpecialPlayerInventory) inv;
}
return 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 getSpecialInventory(ISpecialPlayerInventory.class, inventory);
}
/**
* 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) {
if (craftInventory.isAssignableFrom(inventory.getClass())) {
try {
return getInventory.invoke(inventory) instanceof ISpecialEnderChest;
} catch (ReflectiveOperationException ignored) {}
}
return grabFieldOfTypeFromObject(ISpecialEnderChest.class, inventory) != null;
return getEnderChest(inventory) != null;
}
public static ISpecialEnderChest getEnderChest(@NotNull Inventory inventory) {
Object inv = null;
if (craftInventory.isAssignableFrom(inventory.getClass())) {
/**
* 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 getSpecialInventory(ISpecialEnderChest.class, inventory);
}
private static <T extends ISpecialInventory> @Nullable T getSpecialInventory(@NotNull Class<T> expected, @NotNull Inventory inventory) {
Object inv;
if (isUsable() && craftInventory.isAssignableFrom(inventory.getClass())) {
try {
inv = getInventory.invoke(inventory);
if (expected.isInstance(inv)) {
return expected.cast(inv);
}
} catch (ReflectiveOperationException ignored) {}
}
if (inv == null) {
inv = grabFieldOfTypeFromObject(ISpecialEnderChest.class, inventory);
}
// Use reflection to find the IInventory
inv = ReflectionHelper.grabObjectByType(inventory, expected);
if (inv instanceof ISpecialEnderChest) {
return (ISpecialEnderChest) inv;
if (expected.isInstance(inv)) {
return expected.cast(inv);
}
return null;
}
private static <T> T grabFieldOfTypeFromObject(final Class<T> type, final Object object) {
// Use reflection to find the IInventory
Class<?> clazz = object.getClass();
T result = null;
for (Field f : clazz.getDeclaredFields()) {
f.setAccessible(true);
if (type.isAssignableFrom(f.getDeclaringClass())) {
try {
result = type.cast(f.get(object));
} catch (Exception e) {
e.printStackTrace();
}
}
}
return result;
}
/**
* @deprecated Do not create a new instance to use static methods.
*/
@Deprecated(forRemoval = true, since = "4.2.0")
public InventoryAccess() {}
@Deprecated
@Override
public @Nullable ISpecialEnderChest getSpecialEnderChest(@NotNull Inventory inventory) {
return getEnderChest(inventory);
}
@Deprecated
@Override
public @Nullable ISpecialPlayerInventory getSpecialPlayerInventory(@NotNull Inventory inventory) {
return getPlayerInventory(inventory);
}
@Deprecated
@Override
public boolean isSpecialEnderChest(@NotNull Inventory inventory) {
return isEnderChest(inventory);
}
@Deprecated
@Override
public boolean isSpecialPlayerInventory(@NotNull Inventory inventory) {
return isPlayerInventory(inventory);
}
}
@@ -0,0 +1,75 @@
/*
* Copyright (C) 2011-2021 lishid. All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.lishid.openinv.util;
import java.lang.reflect.Field;
import org.jetbrains.annotations.Nullable;
/**
* A utility for making reflection easier.
*/
public final class ReflectionHelper {
private 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);
if (field != null) {
try {
return fieldType.cast(field.get(holder));
} catch (IllegalAccessException ignored) {
// Ignore issues obtaining field
}
}
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()) {
field.setAccessible(true);
if (fieldType.isAssignableFrom(field.getType())) {
return field;
}
}
if (holderType.getSuperclass() != null) {
return grabFieldByType(fieldType, holderType.getSuperclass());
}
return null;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2011-2019 lishid. All rights reserved.
* Copyright (C) 2011-2021 lishid. All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -12,21 +12,6 @@
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* *
* Copyright (C) 2014 - 2018 Simmetrics Authors
* Copyright (C) 2010 The Guava 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
*
* http://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.
*/
package com.lishid.openinv.util;
+3 -3
View File
@@ -1,5 +1,5 @@
<!--
~ Copyright (C) 2011-2018 lishid. All rights reserved.
~ Copyright (C) 2011-2022 lishid. All rights reserved.
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
@@ -21,11 +21,12 @@
<parent>
<groupId>com.lishid</groupId>
<artifactId>openinvparent</artifactId>
<version>4.0.2</version>
<version>4.2.1</version>
</parent>
<artifactId>openinvassembly</artifactId>
<name>OpenInvAssembly</name>
<packaging>pom</packaging>
<build>
<directory>../target</directory>
@@ -34,7 +35,6 @@
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.0.0</version>
<executions>
<execution>
<id>reactor-uberjar</id>
+16 -6
View File
@@ -1,5 +1,5 @@
<!--
~ Copyright (C) 2011-2018 lishid. All rights reserved.
~ Copyright (C) 2011-2021 lishid. All rights reserved.
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
@@ -14,9 +14,9 @@
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.0.0 http://maven.apache.org/xsd/assembly-2.0.0.xsd">
<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.1.0 http://maven.apache.org/xsd/assembly-2.1.0.xsd">
<id>reactor-uberjar</id>
@@ -34,8 +34,18 @@
<binaries>
<outputDirectory>/</outputDirectory>
<unpack>true</unpack>
<!-- unpackOptions must be present or build breaks. -->
<unpackOptions/>
<unpackOptions>
<excludes>
<!--
- Exclude existing meta - assembly will write its own manifest, and the rest is maven
- details that end users don't need. Ignoring it saves around 7KB after compression
- with a single internal module present.
-->
<exclude>META-INF/**</exclude>
</excludes>
</unpackOptions>
<!-- Exclude dependencies - shade plugin will handle their inclusion as necessary in individual jars. -->
<includeDependencies>false</includeDependencies>
</binaries>
</moduleSet>
@@ -1,195 +0,0 @@
/*
* Copyright (C) 2011-2018 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 com.google.common.collect.Multimap;
import com.google.common.collect.TreeMultimap;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
/**
* A minimal thread-safe time-based cache implementation backed by a HashMap and TreeMultimap.
*
* @author Jikoo
*/
public class Cache<K, V> {
private final Map<K, V> internal;
private final Multimap<Long, K> expiry;
private final long retention;
private final Function<V> inUseCheck, postRemoval;
/**
* Constructs a Cache with the specified retention duration, in use function, and post-removal function.
*
* @param retention duration after which keys are automatically invalidated if not in use
* @param inUseCheck Function used to check if a key is considered in use
* @param postRemoval Function used to perform any operations required when a key is invalidated
*/
public Cache(final long retention, final Function<V> inUseCheck, final Function<V> postRemoval) {
this.internal = new HashMap<K, V>();
this.expiry = TreeMultimap.create(new Comparator<Long>() {
@Override
public int compare(final Long long1, final Long long2) {
return long1.compareTo(long2);
}
},
new Comparator<K>() {
@Override
public int compare(final K k1, final K k2) {
return k1 == k2 || k1 != null && k1.equals(k2) ? 0 : 1;
}
});
this.retention = retention;
this.inUseCheck = inUseCheck;
this.postRemoval = postRemoval;
}
/**
* Set a key and value pair. Keys are unique. Using an existing key will cause the old value to
* be overwritten and the expiration timer to be reset.
*
* @param key key with which the specified value is to be associated
* @param value value to be associated with the specified key
*/
public void put(final K key, final V value) {
// Invalidate key - runs lazy check and ensures value won't be cleaned up early
this.invalidate(key);
synchronized (this.internal) {
this.internal.put(key, value);
this.expiry.put(System.currentTimeMillis() + this.retention, key);
}
}
/**
* Returns the value to which the specified key is mapped, or null if no value is mapped for the key.
*
* @param key the key whose associated value is to be returned
* @return the value to which the specified key is mapped, or null if no value is mapped for the key
*/
public V get(final K key) {
// Run lazy check to clean cache
this.lazyCheck();
synchronized (this.internal) {
return this.internal.get(key);
}
}
/**
* Returns true if the specified key is mapped to a value.
*
* @param key key to check if a mapping exists for
* @return true if a mapping exists for the specified key
*/
public boolean containsKey(final K key) {
// Run lazy check to clean cache
this.lazyCheck();
synchronized (this.internal) {
return this.internal.containsKey(key);
}
}
/**
* Forcibly invalidates a key, even if it is considered to be in use.
*
* @param key key to invalidate
*/
public void invalidate(final K key) {
// Run lazy check to clean cache
this.lazyCheck();
synchronized (this.internal) {
if (!this.internal.containsKey(key)) {
// Value either not present or cleaned by lazy check. Either way, we're good
return;
}
// Remove stored object
this.internal.remove(key);
// Remove expiration entry - prevents more work later, plus prevents issues with values invalidating early
for (Iterator<Map.Entry<Long, K>> iterator = this.expiry.entries().iterator(); iterator.hasNext();) {
if (key.equals(iterator.next().getValue())) {
iterator.remove();
break;
}
}
}
}
/**
* Forcibly invalidates all keys, even if they are considered to be in use.
*/
public void invalidateAll() {
synchronized (this.internal) {
for (V value : this.internal.values()) {
this.postRemoval.run(value);
}
this.expiry.clear();
this.internal.clear();
}
}
/**
* Invalidate all expired keys that are not considered in use. If a key is expired but is
* considered in use by the provided Function, its expiration time is reset.
*/
private void lazyCheck() {
long now = System.currentTimeMillis();
synchronized (this.internal) {
List<K> inUse = new ArrayList<K>();
for (Iterator<Map.Entry<Long, K>> iterator = this.expiry.entries().iterator(); iterator
.hasNext();) {
Map.Entry<Long, K> entry = iterator.next();
if (entry.getKey() > now) {
break;
}
iterator.remove();
if (this.inUseCheck.run(this.internal.get(entry.getValue()))) {
inUse.add(entry.getValue());
continue;
}
V value = this.internal.remove(entry.getValue());
if (value == null) {
continue;
}
this.postRemoval.run(value);
}
long nextExpiry = now + this.retention;
for (K value : inUse) {
this.expiry.put(nextExpiry, value);
}
}
}
}
@@ -1,28 +0,0 @@
/*
* Copyright (C) 2011-2018 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;
/**
* Abstraction for some simple cache calls.
*
* @author Jikoo
*/
public abstract class Function<V> {
public abstract boolean run(V value);
}
-60
View File
@@ -1,60 +0,0 @@
<!--
~ Copyright (C) 2011-2018 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>
<groupId>com.lishid</groupId>
<artifactId>openinvparent</artifactId>
<version>4.0.2</version>
</parent>
<artifactId>openinvinternal</artifactId>
<name>OpenInvInternal</name>
<packaging>pom</packaging>
<profiles>
<profile>
<id>latest</id>
<modules>
<module>v1_14_R1</module>
</modules>
</profile>
<profile>
<id>recent</id>
<!-- The recent profile is for the most recent 2 versions. -->
<modules>
<module>v1_13_R2</module>
<module>v1_14_R1</module>
</modules>
</profile>
<profile>
<id>all</id>
<modules>
<module>v1_8_R3</module>
<module>v1_13_R2</module>
<module>v1_14_R1</module>
</modules>
</profile>
</profiles>
</project>
-74
View File
@@ -1,74 +0,0 @@
<!--
~ Copyright (C) 2011-2018 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>
<groupId>com.lishid</groupId>
<artifactId>openinvinternal</artifactId>
<version>4.0.2</version>
</parent>
<artifactId>openinvadapter1_13_R2</artifactId>
<name>OpenInvAdapter1_13_R2</name>
<dependencies>
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot</artifactId>
<version>1.13.2-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.lishid</groupId>
<artifactId>openinvcommon</artifactId>
<version>4.0.2</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.0.0</version>
<configuration>
<minimizeJar>true</minimizeJar>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
@@ -1,309 +0,0 @@
/*
* Copyright (C) 2011-2018 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.v1_13_R2;
import com.lishid.openinv.internal.IAnySilentContainer;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import net.minecraft.server.v1_13_R2.AxisAlignedBB;
import net.minecraft.server.v1_13_R2.Block;
import net.minecraft.server.v1_13_R2.BlockChest;
import net.minecraft.server.v1_13_R2.BlockChestTrapped;
import net.minecraft.server.v1_13_R2.BlockEnderChest;
import net.minecraft.server.v1_13_R2.BlockPosition;
import net.minecraft.server.v1_13_R2.BlockPropertyChestType;
import net.minecraft.server.v1_13_R2.BlockShulkerBox;
import net.minecraft.server.v1_13_R2.ChatMessage;
import net.minecraft.server.v1_13_R2.Entity;
import net.minecraft.server.v1_13_R2.EntityOcelot;
import net.minecraft.server.v1_13_R2.EntityPlayer;
import net.minecraft.server.v1_13_R2.EnumDirection;
import net.minecraft.server.v1_13_R2.EnumGamemode;
import net.minecraft.server.v1_13_R2.IBlockData;
import net.minecraft.server.v1_13_R2.ITileInventory;
import net.minecraft.server.v1_13_R2.InventoryEnderChest;
import net.minecraft.server.v1_13_R2.InventoryLargeChest;
import net.minecraft.server.v1_13_R2.PlayerInteractManager;
import net.minecraft.server.v1_13_R2.TileEntity;
import net.minecraft.server.v1_13_R2.TileEntityChest;
import net.minecraft.server.v1_13_R2.TileEntityEnderChest;
import net.minecraft.server.v1_13_R2.TileEntityShulkerBox;
import net.minecraft.server.v1_13_R2.VoxelShape;
import net.minecraft.server.v1_13_R2.VoxelShapes;
import net.minecraft.server.v1_13_R2.World;
import org.bukkit.Material;
import org.bukkit.Statistic;
import org.bukkit.block.BlockState;
import org.bukkit.entity.Player;
import org.bukkit.inventory.InventoryView;
import org.jetbrains.annotations.NotNull;
public class AnySilentContainer implements IAnySilentContainer {
private Field playerInteractManagerGamemode;
public AnySilentContainer() {
try {
this.playerInteractManagerGamemode = PlayerInteractManager.class.getDeclaredField("gamemode");
this.playerInteractManagerGamemode.setAccessible(true);
} catch (NoSuchFieldException | SecurityException e) {
System.err.println("[OpenInv] Unable to directly write player gamemode! SilentChest will fail.");
e.printStackTrace();
}
}
@Override
public boolean isAnySilentContainer(@NotNull final org.bukkit.block.Block bukkitBlock) {
if (bukkitBlock.getType() == Material.ENDER_CHEST) {
return true;
}
BlockState state = bukkitBlock.getState();
return state instanceof org.bukkit.block.Chest
|| state instanceof org.bukkit.block.ShulkerBox;
}
@Override
public boolean isAnyContainerNeeded(@NotNull final Player bukkitPlayer, @NotNull final org.bukkit.block.Block bukkitBlock) {
World world = PlayerDataManager.getHandle(bukkitPlayer).world;
BlockPosition blockPosition = new BlockPosition(bukkitBlock.getX(), bukkitBlock.getY(), bukkitBlock.getZ());
IBlockData blockData = world.getType(blockPosition);
Block block = blockData.getBlock();
if (block instanceof BlockShulkerBox) {
return this.isBlockedShulkerBox(world, blockPosition, blockData);
}
if (block instanceof BlockEnderChest) {
// Ender chests are not blocked by ocelots.
return world.getType(blockPosition.up()).isOccluding();
}
// Check if chest is blocked or has an ocelot on top
if (this.isBlockedChest(world, blockPosition)) {
return true;
}
// Check for matching adjacent chests that are blocked or have an ocelot on top
BlockPropertyChestType chestType = blockData.get(BlockChest.b);
if (chestType == BlockPropertyChestType.SINGLE) {
return false;
}
BlockPosition adjacentBlockPosition = blockPosition.shift(BlockChest.k(blockData));
IBlockData adjacentBlockData = world.getType(adjacentBlockPosition);
if (adjacentBlockData.getBlock() == block) {
BlockPropertyChestType adjacentChestType = adjacentBlockData.get(BlockChest.b);
if (adjacentChestType != BlockPropertyChestType.SINGLE && chestType != adjacentChestType
&& adjacentBlockData.get(BlockChest.FACING) == blockData.get(BlockChest.FACING)) {
return this.isBlockedChest(world, adjacentBlockPosition);
}
}
return false;
}
private boolean isBlockedShulkerBox(final World world, final BlockPosition blockPosition,
final IBlockData blockData) {
// For reference, look at net.minecraft.server.BlockShulkerBox
TileEntity tile = world.getTileEntity(blockPosition);
if (!(tile instanceof TileEntityShulkerBox)) {
return false;
}
EnumDirection enumDirection = blockData.get(BlockShulkerBox.a);
if (((TileEntityShulkerBox) tile).r() == TileEntityShulkerBox.AnimationPhase.CLOSED) {
AxisAlignedBB axisAlignedBB;
try {
Method method = VoxelShape.class.getMethod("a");
axisAlignedBB = (AxisAlignedBB) method.invoke(VoxelShapes.b());
} catch (NoSuchMethodException e) {
axisAlignedBB = VoxelShapes.b().getBoundingBox();
} catch (InvocationTargetException | IllegalAccessException e) {
return false;
}
axisAlignedBB = axisAlignedBB
.b(0.5F * enumDirection.getAdjacentX(), 0.5F * enumDirection.getAdjacentY(), 0.5F * enumDirection.getAdjacentZ())
.a(enumDirection.getAdjacentX(), enumDirection.getAdjacentY(), enumDirection.getAdjacentZ());
return !world.getCubes(null, axisAlignedBB.a(blockPosition.shift(enumDirection)));
}
return false;
}
private boolean isBlockedChest(final World world, final BlockPosition blockPosition) {
// For reference, loot at net.minecraft.server.BlockChest
return world.getType(blockPosition.up()).isOccluding() || this.hasOcelotOnTop(world, blockPosition);
}
private boolean hasOcelotOnTop(final World world, final BlockPosition blockPosition) {
for (Entity entity : world.a(EntityOcelot.class,
new AxisAlignedBB(blockPosition.getX(), blockPosition.getY() + 1,
blockPosition.getZ(), blockPosition.getX() + 1, blockPosition.getY() + 2,
blockPosition.getZ() + 1))) {
EntityOcelot entityOcelot = (EntityOcelot) entity;
if (entityOcelot.isSitting()) {
return true;
}
}
return false;
}
@Override
public boolean activateContainer(@NotNull final Player bukkitPlayer, final boolean silent,
@NotNull final org.bukkit.block.Block bukkitBlock) {
// Silent ender chest is API-only
if (silent && bukkitBlock.getType() == Material.ENDER_CHEST) {
bukkitPlayer.openInventory(bukkitPlayer.getEnderChest());
bukkitPlayer.incrementStatistic(Statistic.ENDERCHEST_OPENED);
return true;
}
EntityPlayer player = PlayerDataManager.getHandle(bukkitPlayer);
final World world = player.world;
final BlockPosition blockPosition = new BlockPosition(bukkitBlock.getX(), bukkitBlock.getY(), bukkitBlock.getZ());
final Object tile = world.getTileEntity(blockPosition);
if (tile == null) {
return false;
}
if (tile instanceof TileEntityEnderChest) {
// Anychest ender chest. See net.minecraft.server.BlockEnderChest
InventoryEnderChest enderChest = player.getEnderChest();
enderChest.a((TileEntityEnderChest) tile);
player.openContainer(enderChest);
bukkitPlayer.incrementStatistic(Statistic.ENDERCHEST_OPENED);
return true;
}
if (!(tile instanceof ITileInventory)) {
return false;
}
ITileInventory tileInventory = (ITileInventory) tile;
IBlockData blockData = world.getType(blockPosition);
Block block = blockData.getBlock();
if (block instanceof BlockChest) {
BlockPropertyChestType chestType = blockData.get(BlockChest.b);
if (chestType != BlockPropertyChestType.SINGLE) {
BlockPosition adjacentBlockPosition = blockPosition.shift(BlockChest.k(blockData));
IBlockData adjacentBlockData = world.getType(adjacentBlockPosition);
if (adjacentBlockData.getBlock() == block) {
BlockPropertyChestType adjacentChestType = adjacentBlockData.get(BlockChest.b);
if (adjacentChestType != BlockPropertyChestType.SINGLE && chestType != adjacentChestType
&& adjacentBlockData.get(BlockChest.FACING) == blockData.get(BlockChest.FACING)) {
TileEntity adjacentTile = world.getTileEntity(adjacentBlockPosition);
if (adjacentTile instanceof TileEntityChest) {
ITileInventory rightChest = chestType == BlockPropertyChestType.RIGHT ? tileInventory : (ITileInventory) adjacentTile;
ITileInventory leftChest = chestType == BlockPropertyChestType.RIGHT ? (ITileInventory) adjacentTile : tileInventory;
tileInventory = new InventoryLargeChest(new ChatMessage("container.chestDouble"), rightChest, leftChest);
}
}
}
}
if (block instanceof BlockChestTrapped) {
bukkitPlayer.incrementStatistic(Statistic.TRAPPED_CHEST_TRIGGERED);
} else {
bukkitPlayer.incrementStatistic(Statistic.CHEST_OPENED);
}
}
if (block instanceof BlockShulkerBox) {
bukkitPlayer.incrementStatistic(Statistic.SHULKER_BOX_OPENED);
}
// AnyChest only - SilentChest not active, container unsupported, or unnecessary.
if (!silent || player.playerInteractManager.getGameMode() == EnumGamemode.SPECTATOR) {
player.openContainer(tileInventory);
return true;
}
// SilentChest requires access to setting players' gamemode directly.
if (this.playerInteractManagerGamemode == null) {
return false;
}
EnumGamemode gamemode = player.playerInteractManager.getGameMode();
this.forceGameMode(player, EnumGamemode.SPECTATOR);
player.openContainer(tileInventory);
this.forceGameMode(player, gamemode);
return true;
}
@Override
public void deactivateContainer(@NotNull final Player bukkitPlayer) {
if (this.playerInteractManagerGamemode == null) {
return;
}
InventoryView view = bukkitPlayer.getOpenInventory();
switch (view.getType()) {
case CHEST:
case ENDER_CHEST:
case SHULKER_BOX:
break;
default:
return;
}
EntityPlayer player = PlayerDataManager.getHandle(bukkitPlayer);
EnumGamemode gamemode = player.playerInteractManager.getGameMode();
this.forceGameMode(player, EnumGamemode.SPECTATOR);
player.activeContainer.b(player);
player.activeContainer = player.defaultContainer;
this.forceGameMode(player, gamemode);
}
private void forceGameMode(final EntityPlayer player, final EnumGamemode gameMode) {
if (this.playerInteractManagerGamemode == null) {
// No need to warn repeatedly, error on startup and lack of function should be enough.
return;
}
try {
if (!this.playerInteractManagerGamemode.isAccessible()) {
// Just in case, ensure accessible.
this.playerInteractManagerGamemode.setAccessible(true);
}
this.playerInteractManagerGamemode.set(player.playerInteractManager, gameMode);
} catch (IllegalArgumentException | IllegalAccessException e) {
e.printStackTrace();
}
}
}
@@ -1,85 +0,0 @@
/*
* Copyright (C) 2011-2018 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.v1_13_R2;
import com.lishid.openinv.internal.IPlayerDataManager;
import com.lishid.openinv.internal.ISpecialInventory;
import com.mojang.authlib.GameProfile;
import net.minecraft.server.v1_13_R2.DimensionManager;
import net.minecraft.server.v1_13_R2.EntityPlayer;
import net.minecraft.server.v1_13_R2.MinecraftServer;
import net.minecraft.server.v1_13_R2.PlayerInteractManager;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.Server;
import org.bukkit.craftbukkit.v1_13_R2.CraftServer;
import org.bukkit.craftbukkit.v1_13_R2.entity.CraftPlayer;
import org.bukkit.entity.Player;
import org.bukkit.inventory.InventoryView;
import org.jetbrains.annotations.NotNull;
public class PlayerDataManager implements IPlayerDataManager {
public static EntityPlayer getHandle(final Player player) {
if (player instanceof CraftPlayer) {
return ((CraftPlayer) player).getHandle();
}
Server server = player.getServer();
EntityPlayer nmsPlayer = null;
if (server instanceof CraftServer) {
nmsPlayer = ((CraftServer) server).getHandle().getPlayer(player.getName());
}
if (nmsPlayer == null) {
// Could use reflection to examine fields, but it's honestly not worth the bother.
throw new RuntimeException("Unable to fetch EntityPlayer from provided Player implementation");
}
return nmsPlayer;
}
@Override
public Player loadPlayer(@NotNull final OfflinePlayer offline) {
// Ensure player has data
if (!offline.hasPlayedBefore()) {
return null;
}
// Create a profile and entity to load the player data
GameProfile profile = new GameProfile(offline.getUniqueId(), offline.getName());
MinecraftServer server = ((CraftServer) Bukkit.getServer()).getServer();
EntityPlayer entity = new EntityPlayer(server, server.getWorldServer(DimensionManager.OVERWORLD), profile,
new PlayerInteractManager(server.getWorldServer(DimensionManager.OVERWORLD)));
// Get the bukkit entity
Player target = entity.getBukkitEntity();
if (target != null) {
// Load data
target.loadData();
}
// Return the entity
return target;
}
@Override
public InventoryView openInventory(@NotNull Player player, @NotNull ISpecialInventory inventory) {
return player.openInventory(inventory.getBukkitInventory());
}
}
@@ -1,266 +0,0 @@
/*
* Copyright (C) 2011-2018 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.v1_13_R2;
import com.lishid.openinv.internal.ISpecialEnderChest;
import java.util.List;
import javax.annotation.Nullable;
import net.minecraft.server.v1_13_R2.AutoRecipeOutput;
import net.minecraft.server.v1_13_R2.AutoRecipeStackManager;
import net.minecraft.server.v1_13_R2.ContainerUtil;
import net.minecraft.server.v1_13_R2.EntityHuman;
import net.minecraft.server.v1_13_R2.EntityPlayer;
import net.minecraft.server.v1_13_R2.IChatBaseComponent;
import net.minecraft.server.v1_13_R2.IInventory;
import net.minecraft.server.v1_13_R2.IInventoryListener;
import net.minecraft.server.v1_13_R2.InventoryEnderChest;
import net.minecraft.server.v1_13_R2.ItemStack;
import net.minecraft.server.v1_13_R2.NonNullList;
import org.bukkit.Location;
import org.bukkit.craftbukkit.v1_13_R2.entity.CraftHumanEntity;
import org.bukkit.craftbukkit.v1_13_R2.inventory.CraftInventory;
import org.bukkit.entity.HumanEntity;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryHolder;
import org.jetbrains.annotations.NotNull;
public class SpecialEnderChest implements IInventory, ISpecialEnderChest, AutoRecipeOutput {
private EntityPlayer owner;
private final IChatBaseComponent displayName;
private final CraftInventory inventory;
private NonNullList<ItemStack> items;
private boolean playerOnline;
public SpecialEnderChest(final Player player, final Boolean online) {
this.owner = PlayerDataManager.getHandle(player);
this.displayName = this.owner.getEnderChest().getDisplayName();
this.inventory = new CraftInventory(this);
this.items = this.owner.getEnderChest().items;
this.playerOnline = online;
}
@Override
public @NotNull Inventory getBukkitInventory() {
return this.inventory;
}
@Override
public boolean isInUse() {
return !this.getViewers().isEmpty();
}
@Override
public void setPlayerOffline() {
this.playerOnline = false;
}
@Override
public void setPlayerOnline(@NotNull final Player player) {
if (!this.playerOnline) {
try {
this.owner = PlayerDataManager.getHandle(player);
InventoryEnderChest enderChest = owner.getEnderChest();
for (int i = 0; i < enderChest.getSize(); ++i) {
enderChest.setItem(i, this.items.get(i));
}
this.items = enderChest.items;
} catch (Exception ignored) {}
this.playerOnline = true;
}
}
@Override
public void update() {
this.owner.getEnderChest().update();
}
public List<ItemStack> getContents() {
return this.items;
}
public void onOpen(CraftHumanEntity who) {
this.owner.getEnderChest().onOpen(who);
}
public void onClose(CraftHumanEntity who) {
this.owner.getEnderChest().onClose(who);
}
public List<HumanEntity> getViewers() {
return this.owner.getEnderChest().getViewers();
}
public void setMaxStackSize(int i) {
this.owner.getEnderChest().setMaxStackSize(i);
}
public InventoryHolder getOwner() {
return this.owner.getEnderChest().getOwner();
}
public Location getLocation() {
return null;
}
public void a(IInventoryListener iinventorylistener) {
this.owner.getEnderChest().a(iinventorylistener);
}
public void b(IInventoryListener iinventorylistener) {
this.owner.getEnderChest().b(iinventorylistener);
}
public ItemStack getItem(int i) {
return i >= 0 && i < this.items.size() ? this.items.get(i) : ItemStack.a;
}
public ItemStack splitStack(int i, int j) {
ItemStack itemstack = ContainerUtil.a(this.items, i, j);
if (!itemstack.isEmpty()) {
this.update();
}
return itemstack;
}
public ItemStack a(ItemStack itemstack) {
ItemStack itemstack1 = itemstack.cloneItemStack();
for (int i = 0; i < this.getSize(); ++i) {
ItemStack itemstack2 = this.getItem(i);
if (itemstack2.isEmpty()) {
this.setItem(i, itemstack1);
this.update();
return ItemStack.a;
}
if (ItemStack.c(itemstack2, itemstack1)) {
int j = Math.min(this.getMaxStackSize(), itemstack2.getMaxStackSize());
int k = Math.min(itemstack1.getCount(), j - itemstack2.getCount());
if (k > 0) {
itemstack2.add(k);
itemstack1.subtract(k);
if (itemstack1.isEmpty()) {
this.update();
return ItemStack.a;
}
}
}
}
if (itemstack1.getCount() != itemstack.getCount()) {
this.update();
}
return itemstack1;
}
public ItemStack splitWithoutUpdate(int i) {
ItemStack itemstack = this.items.get(i);
if (itemstack.isEmpty()) {
return ItemStack.a;
} else {
this.items.set(i, ItemStack.a);
return itemstack;
}
}
public void setItem(int i, ItemStack itemstack) {
this.items.set(i, itemstack);
if (!itemstack.isEmpty() && itemstack.getCount() > this.getMaxStackSize()) {
itemstack.setCount(this.getMaxStackSize());
}
this.update();
}
public int getSize() {
return this.owner.getEnderChest().getSize();
}
public boolean P_() {
for (ItemStack itemstack : this.items) {
if (!itemstack.isEmpty()) {
return false;
}
}
return true;
}
public IChatBaseComponent getDisplayName() {
return this.displayName;
}
@Nullable
public IChatBaseComponent getCustomName() {
return this.displayName;
}
public boolean hasCustomName() {
return false;
}
public void a(@Nullable IChatBaseComponent ichatbasecomponent) {
// Ignored - name is always player's name.
}
public int getMaxStackSize() {
return 64;
}
public boolean a(EntityHuman entityhuman) {
return true;
}
public void startOpen(EntityHuman entityhuman) {
}
public void closeContainer(EntityHuman entityhuman) {
}
public boolean b(int i, ItemStack itemstack) {
return true;
}
public int getProperty(int i) {
return 0;
}
public void setProperty(int i, int j) {
}
public int h() {
return 0;
}
public void clear() {
this.items.clear();
}
public void a(AutoRecipeStackManager autorecipestackmanager) {
for (ItemStack itemstack : this.items) {
autorecipestackmanager.b(itemstack);
}
}
}
@@ -1,719 +0,0 @@
/*
* Copyright (C) 2011-2018 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.v1_13_R2;
import com.google.common.collect.ImmutableList;
import com.lishid.openinv.internal.ISpecialPlayerInventory;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.function.Predicate;
import javax.annotation.Nullable;
import net.minecraft.server.v1_13_R2.AutoRecipeStackManager;
import net.minecraft.server.v1_13_R2.ChatMessage;
import net.minecraft.server.v1_13_R2.ContainerUtil;
import net.minecraft.server.v1_13_R2.CrashReport;
import net.minecraft.server.v1_13_R2.CrashReportSystemDetails;
import net.minecraft.server.v1_13_R2.EntityHuman;
import net.minecraft.server.v1_13_R2.EntityPlayer;
import net.minecraft.server.v1_13_R2.IBlockData;
import net.minecraft.server.v1_13_R2.IChatBaseComponent;
import net.minecraft.server.v1_13_R2.Item;
import net.minecraft.server.v1_13_R2.ItemArmor;
import net.minecraft.server.v1_13_R2.ItemStack;
import net.minecraft.server.v1_13_R2.NBTTagCompound;
import net.minecraft.server.v1_13_R2.NBTTagList;
import net.minecraft.server.v1_13_R2.NonNullList;
import net.minecraft.server.v1_13_R2.PacketPlayOutSetSlot;
import net.minecraft.server.v1_13_R2.PlayerInventory;
import net.minecraft.server.v1_13_R2.ReportedException;
import net.minecraft.server.v1_13_R2.World;
import org.bukkit.Location;
import org.bukkit.craftbukkit.v1_13_R2.entity.CraftHumanEntity;
import org.bukkit.craftbukkit.v1_13_R2.inventory.CraftInventory;
import org.bukkit.entity.HumanEntity;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryHolder;
import org.jetbrains.annotations.NotNull;
public class SpecialPlayerInventory extends PlayerInventory implements ISpecialPlayerInventory {
private final CraftInventory inventory = new CraftInventory(this);
private boolean playerOnline;
private NonNullList<ItemStack> items, armor, extraSlots;
private final List<NonNullList<ItemStack>> f;
public SpecialPlayerInventory(final Player bukkitPlayer, final Boolean online) {
super(PlayerDataManager.getHandle(bukkitPlayer));
this.playerOnline = online;
this.items = this.player.inventory.items;
this.armor = this.player.inventory.armor;
this.extraSlots = this.player.inventory.extraSlots;
this.f = ImmutableList.of(this.items, this.armor, this.extraSlots);
}
@Override
public void setPlayerOnline(@NotNull final Player player) {
if (!this.playerOnline) {
EntityPlayer entityPlayer = PlayerDataManager.getHandle(player);
entityPlayer.inventory.transaction.addAll(this.transaction);
this.player = entityPlayer;
this.player.inventory.a(this);
this.items = this.player.inventory.items;
this.armor = this.player.inventory.armor;
this.extraSlots = this.player.inventory.extraSlots;
this.playerOnline = true;
}
}
@Override
public boolean a(final EntityHuman entityhuman) {
return true;
}
@Override
public @NotNull Inventory getBukkitInventory() {
return inventory;
}
@Override
public ItemStack getItem(int i) {
List<ItemStack> list = this.items;
if (i >= list.size()) {
i -= list.size();
list = this.armor;
} else {
i = this.getReversedItemSlotNum(i);
}
if (i >= list.size()) {
i -= list.size();
list = this.extraSlots;
} else if (list == this.armor) {
i = this.getReversedArmorSlotNum(i);
}
if (i >= list.size()) {
return ItemStack.a;
}
return list.get(i);
}
@Override
public IChatBaseComponent getDisplayName() {
return new ChatMessage(this.player.getName());
}
@Override
public boolean hasCustomName() {
return false;
}
private int getReversedArmorSlotNum(final int i) {
if (i == 0) {
return 3;
}
if (i == 1) {
return 2;
}
if (i == 2) {
return 1;
}
if (i == 3) {
return 0;
}
return i;
}
private int getReversedItemSlotNum(final int i) {
if (i >= 27) {
return i - 27;
}
return i + 9;
}
@Override
public int getSize() {
return super.getSize() + 4;
}
@Override
public boolean isInUse() {
return !this.getViewers().isEmpty();
}
@Override
public void setItem(int i, final ItemStack itemstack) {
List<ItemStack> list = this.items;
if (i >= list.size()) {
i -= list.size();
list = this.armor;
} else {
i = this.getReversedItemSlotNum(i);
}
if (i >= list.size()) {
i -= list.size();
list = this.extraSlots;
} else if (list == this.armor) {
i = this.getReversedArmorSlotNum(i);
}
if (i >= list.size()) {
this.player.drop(itemstack, true);
return;
}
list.set(i, itemstack);
}
@Override
public void setPlayerOffline() {
this.playerOnline = false;
}
@Override
public ItemStack splitStack(int i, final int j) {
NonNullList<ItemStack> list = this.items;
if (i >= list.size()) {
i -= list.size();
list = this.armor;
} else {
i = this.getReversedItemSlotNum(i);
}
if (i >= list.size()) {
i -= list.size();
list = this.extraSlots;
} else if (list == this.armor) {
i = this.getReversedArmorSlotNum(i);
}
if (i >= list.size()) {
return ItemStack.a;
}
return list.get(i).isEmpty() ? ItemStack.a : ContainerUtil.a(list, i, j);
}
@Override
public ItemStack splitWithoutUpdate(int i) {
NonNullList<ItemStack> list = this.items;
if (i >= list.size()) {
i -= list.size();
list = this.armor;
} else {
i = this.getReversedItemSlotNum(i);
}
if (i >= list.size()) {
i -= list.size();
list = this.extraSlots;
} else if (list == this.armor) {
i = this.getReversedArmorSlotNum(i);
}
if (i >= list.size()) {
return ItemStack.a;
}
if (!list.get(i).isEmpty()) {
ItemStack itemstack = list.get(i);
list.set(i, ItemStack.a);
return itemstack;
}
return ItemStack.a;
}
public List<ItemStack> getContents() {
List<ItemStack> combined = new ArrayList<>(this.items.size() + this.armor.size() + this.extraSlots.size());
for (List<ItemStack> sub : this.f) {
combined.addAll(sub);
}
return combined;
}
public List<ItemStack> getArmorContents() {
return this.armor;
}
public void onOpen(CraftHumanEntity who) {
this.transaction.add(who);
}
public void onClose(CraftHumanEntity who) {
this.transaction.remove(who);
}
public List<HumanEntity> getViewers() {
return this.transaction;
}
public InventoryHolder getOwner() {
return this.player.getBukkitEntity();
}
public Location getLocation() {
return this.player.getBukkitEntity().getLocation();
}
public ItemStack getItemInHand() {
return e(this.itemInHandIndex) ? this.items.get(this.itemInHandIndex) : ItemStack.a;
}
public static int getHotbarSize() {
return 9;
}
private boolean a(ItemStack itemstack, ItemStack itemstack1) {
return !itemstack.isEmpty() && this.b(itemstack, itemstack1) && itemstack.isStackable() && itemstack.getCount() < itemstack.getMaxStackSize() && itemstack.getCount() < this.getMaxStackSize();
}
private boolean b(ItemStack itemstack, ItemStack itemstack1) {
return itemstack.getItem() == itemstack1.getItem() && ItemStack.equals(itemstack, itemstack1);
}
public int canHold(ItemStack itemstack) {
int remains = itemstack.getCount();
for(int i = 0; i < this.items.size(); ++i) {
ItemStack itemstack1 = this.getItem(i);
if (itemstack1.isEmpty()) {
return itemstack.getCount();
}
if (!this.a(itemstack, itemstack1)) {
remains -= (itemstack1.getMaxStackSize() < this.getMaxStackSize() ? itemstack1.getMaxStackSize() : this.getMaxStackSize()) - itemstack1.getCount();
}
if (remains <= 0) {
return itemstack.getCount();
}
}
return itemstack.getCount() - remains;
}
public int getFirstEmptySlotIndex() {
for(int i = 0; i < this.items.size(); ++i) {
if (this.items.get(i).isEmpty()) {
return i;
}
}
return -1;
}
public void d(int i) {
this.itemInHandIndex = this.l();
ItemStack itemstack = this.items.get(this.itemInHandIndex);
this.items.set(this.itemInHandIndex, this.items.get(i));
this.items.set(i, itemstack);
}
public static boolean e(int i) {
return i >= 0 && i < 9;
}
public int c(ItemStack itemstack) {
for(int i = 0; i < this.items.size(); ++i) {
ItemStack itemstack1 = this.items.get(i);
if (!this.items.get(i).isEmpty() && this.b(itemstack, this.items.get(i)) && !this.items.get(i).f() && !itemstack1.hasEnchantments() && !itemstack1.hasName()) {
return i;
}
}
return -1;
}
public int l() {
int i;
int j;
for(j = 0; j < 9; ++j) {
i = (this.itemInHandIndex + j) % 9;
if (this.items.get(i).isEmpty()) {
return i;
}
}
for(j = 0; j < 9; ++j) {
i = (this.itemInHandIndex + j) % 9;
if (!this.items.get(i).hasEnchantments()) {
return i;
}
}
return this.itemInHandIndex;
}
public int a(Predicate<ItemStack> predicate, int i) {
int j = 0;
int k;
for(k = 0; k < this.getSize(); ++k) {
ItemStack itemstack = this.getItem(k);
if (!itemstack.isEmpty() && predicate.test(itemstack)) {
int l = i <= 0 ? itemstack.getCount() : Math.min(i - j, itemstack.getCount());
j += l;
if (i != 0) {
itemstack.subtract(l);
if (itemstack.isEmpty()) {
this.setItem(k, ItemStack.a);
}
if (i > 0 && j >= i) {
return j;
}
}
}
}
if (!this.getCarried().isEmpty() && predicate.test(this.getCarried())) {
k = i <= 0 ? this.getCarried().getCount() : Math.min(i - j, this.getCarried().getCount());
j += k;
if (i != 0) {
this.getCarried().subtract(k);
if (this.getCarried().isEmpty()) {
this.setCarried(ItemStack.a);
}
if (i > 0 && j >= i) {
return j;
}
}
}
return j;
}
private int i(ItemStack itemstack) {
int i = this.firstPartial(itemstack);
if (i == -1) {
i = this.getFirstEmptySlotIndex();
}
return i == -1 ? itemstack.getCount() : this.d(i, itemstack);
}
private int d(int i, ItemStack itemstack) {
Item item = itemstack.getItem();
int j = itemstack.getCount();
ItemStack itemstack1 = this.getItem(i);
if (itemstack1.isEmpty()) {
itemstack1 = new ItemStack(item, 0);
if (itemstack.hasTag()) {
itemstack1.setTag(itemstack.getTag().clone());
}
this.setItem(i, itemstack1);
}
int k = j;
if (j > itemstack1.getMaxStackSize() - itemstack1.getCount()) {
k = itemstack1.getMaxStackSize() - itemstack1.getCount();
}
if (k > this.getMaxStackSize() - itemstack1.getCount()) {
k = this.getMaxStackSize() - itemstack1.getCount();
}
if (k == 0) {
return j;
} else {
j -= k;
itemstack1.add(k);
itemstack1.d(5);
return j;
}
}
public int firstPartial(ItemStack itemstack) {
if (this.a(this.getItem(this.itemInHandIndex), itemstack)) {
return this.itemInHandIndex;
} else if (this.a(this.getItem(40), itemstack)) {
return 40;
} else {
for(int i = 0; i < this.items.size(); ++i) {
if (this.a(this.items.get(i), itemstack)) {
return i;
}
}
return -1;
}
}
public void p() {
for (NonNullList<ItemStack> itemStacks : this.f) {
for (int i = 0; i < itemStacks.size(); ++i) {
if (!itemStacks.get(i).isEmpty()) {
itemStacks.get(i).a(this.player.world, this.player, i, this.itemInHandIndex == i);
}
}
}
}
public boolean pickup(ItemStack itemstack) {
return this.c(-1, itemstack);
}
public boolean c(int i, ItemStack itemstack) {
if (itemstack.isEmpty()) {
return false;
} else {
try {
if (itemstack.f()) {
if (i == -1) {
i = this.getFirstEmptySlotIndex();
}
if (i >= 0) {
this.items.set(i, itemstack.cloneItemStack());
this.items.get(i).d(5);
itemstack.setCount(0);
return true;
} else if (this.player.abilities.canInstantlyBuild) {
itemstack.setCount(0);
return true;
} else {
return false;
}
} else {
int j;
do {
j = itemstack.getCount();
if (i == -1) {
itemstack.setCount(this.i(itemstack));
} else {
itemstack.setCount(this.d(i, itemstack));
}
} while(!itemstack.isEmpty() && itemstack.getCount() < j);
if (itemstack.getCount() == j && this.player.abilities.canInstantlyBuild) {
itemstack.setCount(0);
return true;
} else {
return itemstack.getCount() < j;
}
}
} catch (Throwable var6) {
CrashReport crashreport = CrashReport.a(var6, "Adding item to inventory");
CrashReportSystemDetails crashreportsystemdetails = crashreport.a("Item being added");
crashreportsystemdetails.a("Item ID", Item.getId(itemstack.getItem()));
crashreportsystemdetails.a("Item data", itemstack.getDamage());
crashreportsystemdetails.a("Item name", () -> itemstack.getName().getString());
throw new ReportedException(crashreport);
}
}
}
public void a(World world, ItemStack itemstack) {
if (!world.isClientSide) {
while(!itemstack.isEmpty()) {
int i = this.firstPartial(itemstack);
if (i == -1) {
i = this.getFirstEmptySlotIndex();
}
if (i == -1) {
this.player.drop(itemstack, false);
break;
}
int j = itemstack.getMaxStackSize() - this.getItem(i).getCount();
if (this.c(i, itemstack.cloneAndSubtract(j))) {
((EntityPlayer)this.player).playerConnection.sendPacket(new PacketPlayOutSetSlot(-2, i, this.getItem(i)));
}
}
}
}
public void f(ItemStack itemstack) {
for (NonNullList<ItemStack> nonnulllist : this.f) {
for (int i = 0; i < nonnulllist.size(); ++i) {
if (nonnulllist.get(i) == itemstack) {
nonnulllist.set(i, ItemStack.a);
break;
}
}
}
}
public float a(IBlockData iblockdata) {
return this.items.get(this.itemInHandIndex).a(iblockdata);
}
public NBTTagList a(NBTTagList nbttaglist) {
NBTTagCompound nbttagcompound;
int i;
for(i = 0; i < this.items.size(); ++i) {
if (!this.items.get(i).isEmpty()) {
nbttagcompound = new NBTTagCompound();
nbttagcompound.setByte("Slot", (byte) i);
this.items.get(i).save(nbttagcompound);
nbttaglist.add(nbttagcompound);
}
}
for(i = 0; i < this.armor.size(); ++i) {
if (!this.armor.get(i).isEmpty()) {
nbttagcompound = new NBTTagCompound();
nbttagcompound.setByte("Slot", (byte) (i + 100));
this.armor.get(i).save(nbttagcompound);
nbttaglist.add(nbttagcompound);
}
}
for(i = 0; i < this.extraSlots.size(); ++i) {
if (!this.extraSlots.get(i).isEmpty()) {
nbttagcompound = new NBTTagCompound();
nbttagcompound.setByte("Slot", (byte) (i + 150));
this.extraSlots.get(i).save(nbttagcompound);
nbttaglist.add(nbttagcompound);
}
}
return nbttaglist;
}
public void b(NBTTagList nbttaglist) {
this.items.clear();
this.armor.clear();
this.extraSlots.clear();
for(int i = 0; i < nbttaglist.size(); ++i) {
NBTTagCompound nbttagcompound = nbttaglist.getCompound(i);
int j = nbttagcompound.getByte("Slot") & 255;
ItemStack itemstack = ItemStack.a(nbttagcompound);
if (!itemstack.isEmpty()) {
if (j < this.items.size()) {
this.items.set(j, itemstack);
} else if (j >= 100 && j < this.armor.size() + 100) {
this.armor.set(j - 100, itemstack);
} else if (j >= 150 && j < this.extraSlots.size() + 150) {
this.extraSlots.set(j - 150, itemstack);
}
}
}
}
public boolean P_() {
Iterator iterator = this.items.iterator();
ItemStack itemstack;
while(iterator.hasNext()) {
itemstack = (ItemStack)iterator.next();
if (!itemstack.isEmpty()) {
return false;
}
}
iterator = this.armor.iterator();
while(iterator.hasNext()) {
itemstack = (ItemStack)iterator.next();
if (!itemstack.isEmpty()) {
return false;
}
}
iterator = this.extraSlots.iterator();
while(iterator.hasNext()) {
itemstack = (ItemStack)iterator.next();
if (!itemstack.isEmpty()) {
return false;
}
}
return true;
}
@Nullable
public IChatBaseComponent getCustomName() {
return null;
}
public boolean b(IBlockData iblockdata) {
return this.getItem(this.itemInHandIndex).b(iblockdata);
}
public void a(float f) {
if (f > 0.0F) {
f /= 4.0F;
if (f < 1.0F) {
f = 1.0F;
}
for (ItemStack itemstack : this.armor) {
if (itemstack.getItem() instanceof ItemArmor) {
itemstack.damage((int) f, this.player);
}
}
}
}
public void dropContents() {
for (NonNullList<ItemStack> itemStacks : this.f) {
for (int i = 0; i < itemStacks.size(); ++i) {
ItemStack itemstack = itemStacks.get(i);
if (!itemstack.isEmpty()) {
itemStacks.set(i, ItemStack.a);
this.player.a(itemstack, true, false);
}
}
}
}
public boolean h(ItemStack itemstack) {
return this.f.stream().flatMap(NonNullList::stream).anyMatch(itemStack1 -> !itemStack1.isEmpty() && itemStack1.doMaterialsMatch(itemstack));
}
public void a(PlayerInventory playerinventory) {
for (int i = 0; i < playerinventory.getSize(); ++i) {
this.setItem(i, playerinventory.getItem(i));
}
this.itemInHandIndex = playerinventory.itemInHandIndex;
}
public void clear() {
this.f.forEach(NonNullList::clear);
}
public void a(AutoRecipeStackManager autorecipestackmanager) {
for (ItemStack itemstack : this.items) {
autorecipestackmanager.a(itemstack);
}
}
}
@@ -1,320 +0,0 @@
/*
* Copyright (C) 2011-2019 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.v1_14_R1;
import com.lishid.openinv.internal.IAnySilentContainer;
import java.lang.reflect.Field;
import javax.annotation.Nullable;
import net.minecraft.server.v1_14_R1.AxisAlignedBB;
import net.minecraft.server.v1_14_R1.Block;
import net.minecraft.server.v1_14_R1.BlockChest;
import net.minecraft.server.v1_14_R1.BlockChestTrapped;
import net.minecraft.server.v1_14_R1.BlockEnderChest;
import net.minecraft.server.v1_14_R1.BlockPosition;
import net.minecraft.server.v1_14_R1.BlockPropertyChestType;
import net.minecraft.server.v1_14_R1.BlockShulkerBox;
import net.minecraft.server.v1_14_R1.ChatMessage;
import net.minecraft.server.v1_14_R1.Container;
import net.minecraft.server.v1_14_R1.ContainerChest;
import net.minecraft.server.v1_14_R1.Entity;
import net.minecraft.server.v1_14_R1.EntityCat;
import net.minecraft.server.v1_14_R1.EntityHuman;
import net.minecraft.server.v1_14_R1.EntityPlayer;
import net.minecraft.server.v1_14_R1.EnumDirection;
import net.minecraft.server.v1_14_R1.EnumGamemode;
import net.minecraft.server.v1_14_R1.IBlockData;
import net.minecraft.server.v1_14_R1.IChatBaseComponent;
import net.minecraft.server.v1_14_R1.ITileInventory;
import net.minecraft.server.v1_14_R1.InventoryEnderChest;
import net.minecraft.server.v1_14_R1.InventoryLargeChest;
import net.minecraft.server.v1_14_R1.PlayerInteractManager;
import net.minecraft.server.v1_14_R1.PlayerInventory;
import net.minecraft.server.v1_14_R1.TileEntity;
import net.minecraft.server.v1_14_R1.TileEntityChest;
import net.minecraft.server.v1_14_R1.TileEntityEnderChest;
import net.minecraft.server.v1_14_R1.TileEntityShulkerBox;
import net.minecraft.server.v1_14_R1.TileInventory;
import net.minecraft.server.v1_14_R1.VoxelShapes;
import net.minecraft.server.v1_14_R1.World;
import org.bukkit.Material;
import org.bukkit.Statistic;
import org.bukkit.block.BlockState;
import org.bukkit.entity.Player;
import org.bukkit.inventory.InventoryView;
import org.jetbrains.annotations.NotNull;
public class AnySilentContainer implements IAnySilentContainer {
private Field playerInteractManagerGamemode;
public AnySilentContainer() {
try {
this.playerInteractManagerGamemode = PlayerInteractManager.class.getDeclaredField("gamemode");
this.playerInteractManagerGamemode.setAccessible(true);
} catch (NoSuchFieldException | SecurityException e) {
System.err.println("[OpenInv] Unable to directly write player gamemode! SilentChest will fail.");
e.printStackTrace();
}
}
@Override
public boolean isAnySilentContainer(@NotNull final org.bukkit.block.Block bukkitBlock) {
if (bukkitBlock.getType() == Material.ENDER_CHEST) {
return true;
}
BlockState state = bukkitBlock.getState();
return state instanceof org.bukkit.block.Chest
|| state instanceof org.bukkit.block.ShulkerBox;
}
@Override
public boolean isAnyContainerNeeded(@NotNull final Player p, @NotNull final org.bukkit.block.Block bukkitBlock) {
EntityPlayer player = PlayerDataManager.getHandle(p);
World world = player.world;
BlockPosition blockPosition = new BlockPosition(bukkitBlock.getX(), bukkitBlock.getY(), bukkitBlock.getZ());
IBlockData blockData = world.getType(blockPosition);
Block block = blockData.getBlock();
if (block instanceof BlockShulkerBox) {
return this.isBlockedShulkerBox(world, blockPosition, blockData);
}
if (block instanceof BlockEnderChest) {
// Ender chests are not blocked by ocelots.
return world.getType(blockPosition.up()).isOccluding(world, blockPosition);
}
// Check if chest is blocked or has an ocelot on top
if (this.isBlockedChest(world, blockPosition)) {
return true;
}
// Check for matching adjacent chests that are blocked or have an ocelot on top
BlockPropertyChestType chestType = blockData.get(BlockChest.b);
if (chestType == BlockPropertyChestType.SINGLE) {
return false;
}
BlockPosition adjacentBlockPosition = blockPosition.shift(BlockChest.j(blockData));
IBlockData adjacentBlockData = world.getType(adjacentBlockPosition);
if (adjacentBlockData.getBlock() == block) {
BlockPropertyChestType adjacentChestType = adjacentBlockData.get(BlockChest.b);
if (adjacentChestType != BlockPropertyChestType.SINGLE && chestType != adjacentChestType
&& adjacentBlockData.get(BlockChest.FACING) == blockData.get(BlockChest.FACING)) {
return this.isBlockedChest(world, adjacentBlockPosition);
}
}
return false;
}
private boolean isBlockedShulkerBox(final World world, final BlockPosition blockPosition,
final IBlockData blockData) {
// For reference, look at net.minecraft.server.BlockShulkerBox
TileEntity tile = world.getTileEntity(blockPosition);
if (!(tile instanceof TileEntityShulkerBox)) {
return false;
}
EnumDirection enumDirection = blockData.get(BlockShulkerBox.a);
if (((TileEntityShulkerBox) tile).s() == TileEntityShulkerBox.AnimationPhase.CLOSED) {
AxisAlignedBB axisAlignedBB = VoxelShapes.b().getBoundingBox()
.b(0.5F * enumDirection.getAdjacentX(), 0.5F * enumDirection.getAdjacentY(), 0.5F * enumDirection.getAdjacentZ())
.a(enumDirection.getAdjacentX(), enumDirection.getAdjacentY(), enumDirection.getAdjacentZ());
return !world.getCubes(null, axisAlignedBB.a(blockPosition.shift(enumDirection)));
}
return false;
}
private boolean isBlockedChest(final World world, final BlockPosition blockPosition) {
// For reference, loot at net.minecraft.server.BlockChest
return world.getType(blockPosition.up()).isOccluding(world, blockPosition) || this.hasOcelotOnTop(world, blockPosition);
}
private boolean hasOcelotOnTop(final World world, final BlockPosition blockPosition) {
for (Entity entity : world.a(EntityCat.class,
new AxisAlignedBB(blockPosition.getX(), blockPosition.getY() + 1,
blockPosition.getZ(), blockPosition.getX() + 1, blockPosition.getY() + 2,
blockPosition.getZ() + 1))) {
EntityCat entityCat = (EntityCat) entity;
if (entityCat.isSitting()) {
return true;
}
}
return false;
}
@Override
public boolean activateContainer(@NotNull final Player bukkitPlayer, final boolean silentchest,
@NotNull final org.bukkit.block.Block bukkitBlock) {
// Silent ender chest is API-only
if (silentchest && bukkitBlock.getType() == Material.ENDER_CHEST) {
bukkitPlayer.openInventory(bukkitPlayer.getEnderChest());
bukkitPlayer.incrementStatistic(Statistic.ENDERCHEST_OPENED);
return true;
}
EntityPlayer player = PlayerDataManager.getHandle(bukkitPlayer);
final World world = player.world;
final BlockPosition blockPosition = new BlockPosition(bukkitBlock.getX(), bukkitBlock.getY(), bukkitBlock.getZ());
final Object tile = world.getTileEntity(blockPosition);
if (tile == null) {
return false;
}
if (tile instanceof TileEntityEnderChest) {
// Anychest ender chest. See net.minecraft.server.BlockEnderChest
InventoryEnderChest enderChest = player.getEnderChest();
enderChest.a((TileEntityEnderChest) tile);
player.openContainer(new TileInventory((containerCounter, playerInventory, ignored) ->
ContainerChest.a(containerCounter, playerInventory, enderChest), BlockEnderChest.d));
bukkitPlayer.incrementStatistic(Statistic.ENDERCHEST_OPENED);
return true;
}
if (!(tile instanceof ITileInventory)) {
return false;
}
ITileInventory tileInventory = (ITileInventory) tile;
IBlockData blockData = world.getType(blockPosition);
Block block = blockData.getBlock();
if (block instanceof BlockChest) {
BlockPropertyChestType chestType = blockData.get(BlockChest.b);
if (chestType != BlockPropertyChestType.SINGLE) {
BlockPosition adjacentBlockPosition = blockPosition.shift(BlockChest.j(blockData));
IBlockData adjacentBlockData = world.getType(adjacentBlockPosition);
if (adjacentBlockData.getBlock() == block) {
BlockPropertyChestType adjacentChestType = adjacentBlockData.get(BlockChest.b);
if (adjacentChestType != BlockPropertyChestType.SINGLE && chestType != adjacentChestType
&& adjacentBlockData.get(BlockChest.FACING) == blockData.get(BlockChest.FACING)) {
TileEntity adjacentTile = world.getTileEntity(adjacentBlockPosition);
if (adjacentTile instanceof TileEntityChest && tileInventory instanceof TileEntityChest) {
TileEntityChest rightChest = chestType == BlockPropertyChestType.RIGHT ? ((TileEntityChest) tileInventory) : (TileEntityChest) adjacentTile;
TileEntityChest leftChest = chestType == BlockPropertyChestType.RIGHT ? (TileEntityChest) adjacentTile : ((TileEntityChest) tileInventory);
tileInventory = new ITileInventory() {
@Nullable
public Container createMenu(int containerCounter, PlayerInventory playerInventory, EntityHuman entityHuman) {
if (leftChest.e(entityHuman) && rightChest.e(entityHuman)) {
leftChest.d(playerInventory.player);
rightChest.d(playerInventory.player);
return ContainerChest.b(containerCounter, playerInventory, new InventoryLargeChest(rightChest, leftChest));
} else {
return null;
}
}
public IChatBaseComponent getScoreboardDisplayName() {
return new ChatMessage("container.chestDouble");
}
};
}
}
}
}
if (block instanceof BlockChestTrapped) {
bukkitPlayer.incrementStatistic(Statistic.TRAPPED_CHEST_TRIGGERED);
} else {
bukkitPlayer.incrementStatistic(Statistic.CHEST_OPENED);
}
}
if (block instanceof BlockShulkerBox) {
bukkitPlayer.incrementStatistic(Statistic.SHULKER_BOX_OPENED);
}
// AnyChest only - SilentChest not active, container unsupported, or unnecessary.
if (!silentchest || player.playerInteractManager.getGameMode() == EnumGamemode.SPECTATOR) {
player.openContainer(tileInventory);
return true;
}
// SilentChest requires access to setting players' gamemode directly.
if (this.playerInteractManagerGamemode == null) {
return false;
}
EnumGamemode gamemode = player.playerInteractManager.getGameMode();
this.forceGameMode(player, EnumGamemode.SPECTATOR);
player.openContainer(tileInventory);
this.forceGameMode(player, gamemode);
return true;
}
@Override
public void deactivateContainer(@NotNull final Player bukkitPlayer) {
if (this.playerInteractManagerGamemode == null) {
return;
}
InventoryView view = bukkitPlayer.getOpenInventory();
switch (view.getType()) {
case CHEST:
case ENDER_CHEST:
case SHULKER_BOX:
break;
default:
return;
}
EntityPlayer player = PlayerDataManager.getHandle(bukkitPlayer);
EnumGamemode gamemode = player.playerInteractManager.getGameMode();
this.forceGameMode(player, EnumGamemode.SPECTATOR);
player.activeContainer.b(player);
player.activeContainer = player.defaultContainer;
this.forceGameMode(player, gamemode);
}
private void forceGameMode(final EntityPlayer player, final EnumGamemode gameMode) {
if (this.playerInteractManagerGamemode == null) {
// No need to warn repeatedly, error on startup and lack of function should be enough.
return;
}
try {
if (!this.playerInteractManagerGamemode.isAccessible()) {
// Just in case, ensure accessible.
this.playerInteractManagerGamemode.setAccessible(true);
}
this.playerInteractManagerGamemode.set(player.playerInteractManager, gameMode);
} catch (IllegalArgumentException | IllegalAccessException e) {
e.printStackTrace();
}
}
}
@@ -1,155 +0,0 @@
/*
* Copyright (C) 2011-2019 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.v1_14_R1;
import com.lishid.openinv.internal.IPlayerDataManager;
import com.lishid.openinv.internal.ISpecialInventory;
import com.mojang.authlib.GameProfile;
import net.minecraft.server.v1_14_R1.ChatComponentText;
import net.minecraft.server.v1_14_R1.Container;
import net.minecraft.server.v1_14_R1.Containers;
import net.minecraft.server.v1_14_R1.DimensionManager;
import net.minecraft.server.v1_14_R1.EntityHuman;
import net.minecraft.server.v1_14_R1.EntityPlayer;
import net.minecraft.server.v1_14_R1.MinecraftServer;
import net.minecraft.server.v1_14_R1.PacketPlayOutOpenWindow;
import net.minecraft.server.v1_14_R1.PlayerInteractManager;
import net.minecraft.server.v1_14_R1.PlayerInventory;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.Server;
import org.bukkit.craftbukkit.v1_14_R1.CraftServer;
import org.bukkit.craftbukkit.v1_14_R1.entity.CraftPlayer;
import org.bukkit.craftbukkit.v1_14_R1.event.CraftEventFactory;
import org.bukkit.craftbukkit.v1_14_R1.inventory.CraftContainer;
import org.bukkit.entity.HumanEntity;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.InventoryType;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryView;
import org.jetbrains.annotations.NotNull;
public class PlayerDataManager implements IPlayerDataManager {
public static EntityPlayer getHandle(final Player player) {
if (player instanceof CraftPlayer) {
return ((CraftPlayer) player).getHandle();
}
Server server = player.getServer();
EntityPlayer nmsPlayer = null;
if (server instanceof CraftServer) {
nmsPlayer = ((CraftServer) server).getHandle().getPlayer(player.getName());
}
if (nmsPlayer == null) {
// Could use reflection to examine fields, but it's honestly not worth the bother.
throw new RuntimeException("Unable to fetch EntityPlayer from provided Player implementation");
}
return nmsPlayer;
}
@Override
public Player loadPlayer(@NotNull final OfflinePlayer offline) {
// Ensure player has data
if (!offline.hasPlayedBefore()) {
return null;
}
// Create a profile and entity to load the player data
GameProfile profile = new GameProfile(offline.getUniqueId(), offline.getName());
MinecraftServer server = ((CraftServer) Bukkit.getServer()).getServer();
EntityPlayer entity = new EntityPlayer(server, server.getWorldServer(DimensionManager.OVERWORLD), profile,
new PlayerInteractManager(server.getWorldServer(DimensionManager.OVERWORLD)));
// Get the bukkit entity
Player target = entity.getBukkitEntity();
if (target != null) {
// Load data
target.loadData();
}
// Return the entity
return target;
}
@Override
public InventoryView openInventory(@NotNull Player player, @NotNull ISpecialInventory inventory) {
EntityPlayer nmsPlayer = getHandle(player);
if (nmsPlayer == null || nmsPlayer.playerConnection == null) {
return null;
}
String title;
if (inventory instanceof SpecialEnderChest) {
HumanEntity owner = (HumanEntity) ((SpecialEnderChest) inventory).getBukkitOwner();
title = (owner.getName() != null ? owner.getName() : owner.getUniqueId().toString()) + "'s Ender Chest";
} else if (inventory instanceof SpecialPlayerInventory) {
EntityHuman owner = ((PlayerInventory) inventory).player;
title = (owner.getName() != null ? owner.getName() : owner.getUniqueID().toString()) + "'s Inventory";
} else {
return player.openInventory(inventory.getBukkitInventory());
}
Container container = new CraftContainer(new InventoryView() {
@Override
public @NotNull Inventory getTopInventory() {
return inventory.getBukkitInventory();
}
@Override
public @NotNull Inventory getBottomInventory() {
return player.getInventory();
}
@Override
public @NotNull HumanEntity getPlayer() {
return player;
}
@Override
public @NotNull InventoryType getType() {
return inventory.getBukkitInventory().getType();
}
@Override
public @NotNull String getTitle() {
return title;
}
}, nmsPlayer, nmsPlayer.nextContainerCounter()) {
@Override
public Containers<?> getType() {
return inventory instanceof SpecialEnderChest ? Containers.GENERIC_9X3 : Containers.GENERIC_9X5;
}
};
container.setTitle(new ChatComponentText(title));
container = CraftEventFactory.callInventoryOpenEvent(nmsPlayer, container);
if (container == null) {
return null;
}
nmsPlayer.playerConnection.sendPacket(new PacketPlayOutOpenWindow(container.windowId, container.getType(),
new ChatComponentText(container.getBukkitView().getTitle())));
nmsPlayer.activeContainer = container;
container.addSlotListener(nmsPlayer);
return container.getBukkitView();
}
}
@@ -1,255 +0,0 @@
/*
* Copyright (C) 2011-2019 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.v1_14_R1;
import com.lishid.openinv.internal.ISpecialEnderChest;
import java.util.List;
import net.minecraft.server.v1_14_R1.AutoRecipeStackManager;
import net.minecraft.server.v1_14_R1.ContainerUtil;
import net.minecraft.server.v1_14_R1.EntityHuman;
import net.minecraft.server.v1_14_R1.EntityPlayer;
import net.minecraft.server.v1_14_R1.IInventoryListener;
import net.minecraft.server.v1_14_R1.InventoryEnderChest;
import net.minecraft.server.v1_14_R1.ItemStack;
import net.minecraft.server.v1_14_R1.NonNullList;
import org.bukkit.Location;
import org.bukkit.craftbukkit.v1_14_R1.entity.CraftHumanEntity;
import org.bukkit.craftbukkit.v1_14_R1.inventory.CraftInventory;
import org.bukkit.entity.HumanEntity;
import org.bukkit.entity.Player;
import org.bukkit.inventory.InventoryHolder;
import org.jetbrains.annotations.NotNull;
public class SpecialEnderChest extends InventoryEnderChest implements ISpecialEnderChest {
private final CraftInventory inventory;
private EntityPlayer owner;
private NonNullList<ItemStack> items;
private boolean playerOnline;
public SpecialEnderChest(final Player player, final Boolean online) {
super(PlayerDataManager.getHandle(player));
this.inventory = new CraftInventory(this);
this.owner = PlayerDataManager.getHandle(player);
this.playerOnline = online;
this.items = this.owner.getEnderChest().items;
}
@Override
public @NotNull CraftInventory getBukkitInventory() {
return inventory;
}
@Override
public boolean isInUse() {
return !this.getViewers().isEmpty();
}
@Override
public void setPlayerOffline() {
this.playerOnline = false;
}
@Override
public void setPlayerOnline(@NotNull final Player player) {
if (!this.playerOnline) {
try {
this.owner = PlayerDataManager.getHandle(player);
InventoryEnderChest enderChest = owner.getEnderChest();
for (int i = 0; i < enderChest.getSize(); ++i) {
enderChest.setItem(i, this.items.get(i));
}
this.items = enderChest.items;
} catch (Exception ignored) {}
this.playerOnline = true;
}
}
@Override
public void update() {
this.owner.getEnderChest().update();
}
@Override
public List<ItemStack> getContents() {
return this.items;
}
@Override
public void onOpen(CraftHumanEntity who) {
this.owner.getEnderChest().onOpen(who);
}
@Override
public void onClose(CraftHumanEntity who) {
this.owner.getEnderChest().onClose(who);
}
@Override
public List<HumanEntity> getViewers() {
return this.owner.getEnderChest().getViewers();
}
@Override
public void setMaxStackSize(int i) {
this.owner.getEnderChest().setMaxStackSize(i);
}
@Override
public InventoryHolder getOwner() {
return this.owner.getEnderChest().getOwner();
}
@Override
public Location getLocation() {
return null;
}
@Override
public void a(IInventoryListener iinventorylistener) {
this.owner.getEnderChest().a(iinventorylistener);
}
@Override
public void b(IInventoryListener iinventorylistener) {
this.owner.getEnderChest().b(iinventorylistener);
}
@Override
public ItemStack getItem(int i) {
return i >= 0 && i < this.items.size() ? this.items.get(i) : ItemStack.a;
}
@Override
public ItemStack splitStack(int i, int j) {
ItemStack itemstack = ContainerUtil.a(this.items, i, j);
if (!itemstack.isEmpty()) {
this.update();
}
return itemstack;
}
@Override
public ItemStack a(ItemStack itemstack) {
ItemStack itemstack1 = itemstack.cloneItemStack();
for (int i = 0; i < this.getSize(); ++i) {
ItemStack itemstack2 = this.getItem(i);
if (itemstack2.isEmpty()) {
this.setItem(i, itemstack1);
this.update();
return ItemStack.a;
}
if (ItemStack.c(itemstack2, itemstack1)) {
int j = Math.min(this.getMaxStackSize(), itemstack2.getMaxStackSize());
int k = Math.min(itemstack1.getCount(), j - itemstack2.getCount());
if (k > 0) {
itemstack2.add(k);
itemstack1.subtract(k);
if (itemstack1.isEmpty()) {
this.update();
return ItemStack.a;
}
}
}
}
if (itemstack1.getCount() != itemstack.getCount()) {
this.update();
}
return itemstack1;
}
@Override
public ItemStack splitWithoutUpdate(int i) {
ItemStack itemstack = this.items.get(i);
if (itemstack.isEmpty()) {
return ItemStack.a;
} else {
this.items.set(i, ItemStack.a);
return itemstack;
}
}
@Override
public void setItem(int i, ItemStack itemstack) {
this.items.set(i, itemstack);
if (!itemstack.isEmpty() && itemstack.getCount() > this.getMaxStackSize()) {
itemstack.setCount(this.getMaxStackSize());
}
this.update();
}
@Override
public int getSize() {
return this.owner.getEnderChest().getSize();
}
@Override
public boolean isNotEmpty() {
for (ItemStack itemstack : this.items) {
if (!itemstack.isEmpty()) {
return false;
}
}
return true;
}
@Override
public int getMaxStackSize() {
return 64;
}
@Override
public boolean a(EntityHuman entityhuman) {
return true;
}
@Override
public void startOpen(EntityHuman entityhuman) {
}
@Override
public void closeContainer(EntityHuman entityhuman) {
}
@Override
public boolean b(int i, ItemStack itemstack) {
return true;
}
@Override
public void clear() {
this.items.clear();
}
@Override
public void a(AutoRecipeStackManager autorecipestackmanager) {
for (ItemStack itemstack : this.items) {
autorecipestackmanager.b(itemstack);
}
}
}
@@ -1,742 +0,0 @@
/*
* Copyright (C) 2011-2019 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.v1_14_R1;
import com.google.common.collect.ImmutableList;
import com.lishid.openinv.internal.ISpecialPlayerInventory;
import java.util.Iterator;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import net.minecraft.server.v1_14_R1.AutoRecipeStackManager;
import net.minecraft.server.v1_14_R1.ChatMessage;
import net.minecraft.server.v1_14_R1.ContainerUtil;
import net.minecraft.server.v1_14_R1.CrashReport;
import net.minecraft.server.v1_14_R1.CrashReportSystemDetails;
import net.minecraft.server.v1_14_R1.EntityHuman;
import net.minecraft.server.v1_14_R1.EntityPlayer;
import net.minecraft.server.v1_14_R1.EnumItemSlot;
import net.minecraft.server.v1_14_R1.IBlockData;
import net.minecraft.server.v1_14_R1.IChatBaseComponent;
import net.minecraft.server.v1_14_R1.Item;
import net.minecraft.server.v1_14_R1.ItemArmor;
import net.minecraft.server.v1_14_R1.ItemStack;
import net.minecraft.server.v1_14_R1.NBTTagCompound;
import net.minecraft.server.v1_14_R1.NBTTagList;
import net.minecraft.server.v1_14_R1.NonNullList;
import net.minecraft.server.v1_14_R1.PacketPlayOutSetSlot;
import net.minecraft.server.v1_14_R1.PlayerInventory;
import net.minecraft.server.v1_14_R1.ReportedException;
import net.minecraft.server.v1_14_R1.World;
import org.bukkit.Location;
import org.bukkit.craftbukkit.v1_14_R1.entity.CraftHumanEntity;
import org.bukkit.craftbukkit.v1_14_R1.inventory.CraftInventory;
import org.bukkit.entity.HumanEntity;
import org.bukkit.entity.Player;
import org.bukkit.inventory.InventoryHolder;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class SpecialPlayerInventory extends PlayerInventory implements ISpecialPlayerInventory {
private final CraftInventory inventory;
private boolean playerOnline;
private EntityHuman player;
private NonNullList<ItemStack> items, armor, extraSlots;
private final List<NonNullList<ItemStack>> f;
public SpecialPlayerInventory(final Player bukkitPlayer, final Boolean online) {
super(PlayerDataManager.getHandle(bukkitPlayer));
this.inventory = new CraftInventory(this);
this.playerOnline = online;
this.player = super.player;
this.items = this.player.inventory.items;
this.armor = this.player.inventory.armor;
this.extraSlots = this.player.inventory.extraSlots;
this.f = ImmutableList.of(this.items, this.armor, this.extraSlots);
}
@Override
public void setPlayerOnline(@NotNull final Player player) {
if (!this.playerOnline) {
EntityPlayer entityPlayer = PlayerDataManager.getHandle(player);
entityPlayer.inventory.transaction.addAll(this.transaction);
this.player = entityPlayer;
this.player.inventory.a(this);
this.items = this.player.inventory.items;
this.armor = this.player.inventory.armor;
this.extraSlots = this.player.inventory.extraSlots;
this.playerOnline = true;
}
}
@Override
public boolean a(final EntityHuman entityhuman) {
return true;
}
@Override
public @NotNull CraftInventory getBukkitInventory() {
return this.inventory;
}
@Override
public ItemStack getItem(int i) {
List<ItemStack> list = this.items;
if (i >= list.size()) {
i -= list.size();
list = this.armor;
} else {
i = this.getReversedItemSlotNum(i);
}
if (i >= list.size()) {
i -= list.size();
list = this.extraSlots;
} else if (list == this.armor) {
i = this.getReversedArmorSlotNum(i);
}
if (i >= list.size()) {
return ItemStack.a;
}
return list.get(i);
}
@Override
public IChatBaseComponent getDisplayName() {
return new ChatMessage(this.player.getName());
}
@Override
public boolean hasCustomName() {
return false;
}
private int getReversedArmorSlotNum(final int i) {
if (i == 0) {
return 3;
}
if (i == 1) {
return 2;
}
if (i == 2) {
return 1;
}
if (i == 3) {
return 0;
}
return i;
}
private int getReversedItemSlotNum(final int i) {
if (i >= 27) {
return i - 27;
}
return i + 9;
}
@Override
public int getSize() {
return 45;
}
@Override
public boolean isInUse() {
return !this.getViewers().isEmpty();
}
@Override
public void setItem(int i, final ItemStack itemstack) {
List<ItemStack> list = this.items;
if (i >= list.size()) {
i -= list.size();
list = this.armor;
} else {
i = this.getReversedItemSlotNum(i);
}
if (i >= list.size()) {
i -= list.size();
list = this.extraSlots;
} else if (list == this.armor) {
i = this.getReversedArmorSlotNum(i);
}
if (i >= list.size()) {
this.player.drop(itemstack, true);
return;
}
list.set(i, itemstack);
}
@Override
public void setPlayerOffline() {
this.playerOnline = false;
}
@Override
public ItemStack splitStack(int i, final int j) {
List<ItemStack> list = this.items;
if (i >= list.size()) {
i -= list.size();
list = this.armor;
} else {
i = this.getReversedItemSlotNum(i);
}
if (i >= list.size()) {
i -= list.size();
list = this.extraSlots;
} else if (list == this.armor) {
i = this.getReversedArmorSlotNum(i);
}
if (i >= list.size()) {
return ItemStack.a;
}
return list.get(i).isEmpty() ? ItemStack.a : ContainerUtil.a(list, i, j);
}
@Override
public ItemStack splitWithoutUpdate(int i) {
List<ItemStack> list = this.items;
if (i >= list.size()) {
i -= list.size();
list = this.armor;
} else {
i = this.getReversedItemSlotNum(i);
}
if (i >= list.size()) {
i -= list.size();
list = this.extraSlots;
} else if (list == this.armor) {
i = this.getReversedArmorSlotNum(i);
}
if (i >= list.size()) {
return ItemStack.a;
}
if (!list.get(i).isEmpty()) {
ItemStack itemstack = list.get(i);
list.set(i, ItemStack.a);
return itemstack;
}
return ItemStack.a;
}
@Override
public List<ItemStack> getContents() {
return this.f.stream().flatMap(List::stream).collect(Collectors.toList());
}
@Override
public List<ItemStack> getArmorContents() {
return this.armor;
}
@Override
public void onOpen(CraftHumanEntity who) {
this.transaction.add(who);
}
@Override
public void onClose(CraftHumanEntity who) {
this.transaction.remove(who);
}
@Override
public List<HumanEntity> getViewers() {
return this.transaction;
}
@Override
public InventoryHolder getOwner() {
return this.player.getBukkitEntity();
}
@Override
public Location getLocation() {
return this.player.getBukkitEntity().getLocation();
}
@Override
public ItemStack getItemInHand() {
return d(this.itemInHandIndex) ? this.items.get(this.itemInHandIndex) : ItemStack.a;
}
private boolean a(ItemStack itemstack, ItemStack itemstack1) {
return !itemstack.isEmpty() && this.b(itemstack, itemstack1) && itemstack.isStackable() && itemstack.getCount() < itemstack.getMaxStackSize() && itemstack.getCount() < this.getMaxStackSize();
}
private boolean b(ItemStack itemstack, ItemStack itemstack1) {
return itemstack.getItem() == itemstack1.getItem() && ItemStack.equals(itemstack, itemstack1);
}
@Override
public int canHold(ItemStack itemstack) {
int remains = itemstack.getCount();
for (int i = 0; i < this.items.size(); ++i) {
ItemStack itemstack1 = this.getItem(i);
if (itemstack1.isEmpty()) {
return itemstack.getCount();
}
if (!this.a(itemstack, itemstack1)) {
remains -= (itemstack1.getMaxStackSize() < this.getMaxStackSize() ? itemstack1.getMaxStackSize() : this.getMaxStackSize()) - itemstack1.getCount();
}
if (remains <= 0) {
return itemstack.getCount();
}
}
return itemstack.getCount() - remains;
}
@Override
public int getFirstEmptySlotIndex() {
for (int i = 0; i < this.items.size(); ++i) {
if (this.items.get(i).isEmpty()) {
return i;
}
}
return -1;
}
@Override
public void c(int i) {
this.itemInHandIndex = this.i();
ItemStack itemstack = this.items.get(this.itemInHandIndex);
this.items.set(this.itemInHandIndex, this.items.get(i));
this.items.set(i, itemstack);
}
@Override
public int c(ItemStack itemstack) {
for (int i = 0; i < this.items.size(); ++i) {
ItemStack itemstack1 = this.items.get(i);
if (!this.items.get(i).isEmpty() && this.b(itemstack, this.items.get(i)) && !this.items.get(i).f() && !itemstack1.hasEnchantments() && !itemstack1.hasName()) {
return i;
}
}
return -1;
}
@Override
public int i() {
int i;
int j;
for (j = 0; j < 9; ++j) {
i = (this.itemInHandIndex + j) % 9;
if (this.items.get(i).isEmpty()) {
return i;
}
}
for (j = 0; j < 9; ++j) {
i = (this.itemInHandIndex + j) % 9;
if (!this.items.get(i).hasEnchantments()) {
return i;
}
}
return this.itemInHandIndex;
}
@Override
public int a(Predicate<ItemStack> predicate, int i) {
int j = 0;
int k;
for (k = 0; k < this.getSize(); ++k) {
ItemStack itemstack = this.getItem(k);
if (!itemstack.isEmpty() && predicate.test(itemstack)) {
int l = i <= 0 ? itemstack.getCount() : Math.min(i - j, itemstack.getCount());
j += l;
if (i != 0) {
itemstack.subtract(l);
if (itemstack.isEmpty()) {
this.setItem(k, ItemStack.a);
}
if (i > 0 && j >= i) {
return j;
}
}
}
}
if (!this.getCarried().isEmpty() && predicate.test(this.getCarried())) {
k = i <= 0 ? this.getCarried().getCount() : Math.min(i - j, this.getCarried().getCount());
j += k;
if (i != 0) {
this.getCarried().subtract(k);
if (this.getCarried().isEmpty()) {
this.setCarried(ItemStack.a);
}
if (i > 0 && j >= i) {
return j;
}
}
}
return j;
}
private int i(ItemStack itemstack) {
int i = this.firstPartial(itemstack);
if (i == -1) {
i = this.getFirstEmptySlotIndex();
}
return i == -1 ? itemstack.getCount() : this.d(i, itemstack);
}
private int d(int i, ItemStack itemstack) {
Item item = itemstack.getItem();
int j = itemstack.getCount();
ItemStack itemstack1 = this.getItem(i);
if (itemstack1.isEmpty()) {
itemstack1 = new ItemStack(item, 0);
if (itemstack.hasTag()) {
itemstack1.setTag(itemstack.getTag().clone());
}
this.setItem(i, itemstack1);
}
int k = j;
if (j > itemstack1.getMaxStackSize() - itemstack1.getCount()) {
k = itemstack1.getMaxStackSize() - itemstack1.getCount();
}
if (k > this.getMaxStackSize() - itemstack1.getCount()) {
k = this.getMaxStackSize() - itemstack1.getCount();
}
if (k == 0) {
return j;
} else {
j -= k;
itemstack1.add(k);
itemstack1.d(5);
return j;
}
}
@Override
public int firstPartial(ItemStack itemstack) {
if (this.a(this.getItem(this.itemInHandIndex), itemstack)) {
return this.itemInHandIndex;
} else if (this.a(this.getItem(40), itemstack)) {
return 40;
} else {
for (int i = 0; i < this.items.size(); ++i) {
if (this.a(this.items.get(i), itemstack)) {
return i;
}
}
return -1;
}
}
@Override
public void j() {
for (List<ItemStack> itemStacks : this.f) {
for (int i = 0; i < itemStacks.size(); ++i) {
if (!itemStacks.get(i).isEmpty()) {
itemStacks.get(i).a(this.player.world, this.player, i, this.itemInHandIndex == i);
}
}
}
}
@Override
public boolean pickup(ItemStack itemstack) {
return this.c(-1, itemstack);
}
@Override
public boolean c(int i, ItemStack itemstack) {
if (itemstack.isEmpty()) {
return false;
} else {
try {
if (itemstack.f()) {
if (i == -1) {
i = this.getFirstEmptySlotIndex();
}
if (i >= 0) {
this.items.set(i, itemstack.cloneItemStack());
this.items.get(i).d(5);
itemstack.setCount(0);
return true;
} else if (this.player.abilities.canInstantlyBuild) {
itemstack.setCount(0);
return true;
} else {
return false;
}
} else {
int j;
do {
j = itemstack.getCount();
if (i == -1) {
itemstack.setCount(this.i(itemstack));
} else {
itemstack.setCount(this.d(i, itemstack));
}
} while(!itemstack.isEmpty() && itemstack.getCount() < j);
if (itemstack.getCount() == j && this.player.abilities.canInstantlyBuild) {
itemstack.setCount(0);
return true;
} else {
return itemstack.getCount() < j;
}
}
} catch (Throwable var6) {
CrashReport crashreport = CrashReport.a(var6, "Adding item to inventory");
CrashReportSystemDetails crashreportsystemdetails = crashreport.a("Item being added");
crashreportsystemdetails.a("Item ID", Item.getId(itemstack.getItem()));
crashreportsystemdetails.a("Item data", itemstack.getDamage());
crashreportsystemdetails.a("Item name", () -> itemstack.getName().getString());
throw new ReportedException(crashreport);
}
}
}
@Override
public void a(World world, ItemStack itemstack) {
if (!world.isClientSide) {
while(!itemstack.isEmpty()) {
int i = this.firstPartial(itemstack);
if (i == -1) {
i = this.getFirstEmptySlotIndex();
}
if (i == -1) {
this.player.drop(itemstack, false);
break;
}
int j = itemstack.getMaxStackSize() - this.getItem(i).getCount();
if (this.c(i, itemstack.cloneAndSubtract(j))) {
((EntityPlayer)this.player).playerConnection.sendPacket(new PacketPlayOutSetSlot(-2, i, this.getItem(i)));
}
}
}
}
@Override
public void f(ItemStack itemstack) {
for (List<ItemStack> list : this.f) {
for (int i = 0; i < list.size(); ++i) {
if (list.get(i) == itemstack) {
list.set(i, ItemStack.a);
break;
}
}
}
}
@Override
public float a(IBlockData iblockdata) {
return this.items.get(this.itemInHandIndex).a(iblockdata);
}
@Override
public NBTTagList a(NBTTagList nbttaglist) {
NBTTagCompound nbttagcompound;
int i;
for (i = 0; i < this.items.size(); ++i) {
if (!this.items.get(i).isEmpty()) {
nbttagcompound = new NBTTagCompound();
nbttagcompound.setByte("Slot", (byte) i);
this.items.get(i).save(nbttagcompound);
nbttaglist.add(nbttagcompound);
}
}
for (i = 0; i < this.armor.size(); ++i) {
if (!this.armor.get(i).isEmpty()) {
nbttagcompound = new NBTTagCompound();
nbttagcompound.setByte("Slot", (byte) (i + 100));
this.armor.get(i).save(nbttagcompound);
nbttaglist.add(nbttagcompound);
}
}
for (i = 0; i < this.extraSlots.size(); ++i) {
if (!this.extraSlots.get(i).isEmpty()) {
nbttagcompound = new NBTTagCompound();
nbttagcompound.setByte("Slot", (byte) (i + 150));
this.extraSlots.get(i).save(nbttagcompound);
nbttaglist.add(nbttagcompound);
}
}
return nbttaglist;
}
@Override
public void b(NBTTagList nbttaglist) {
this.items.clear();
this.armor.clear();
this.extraSlots.clear();
for(int i = 0; i < nbttaglist.size(); ++i) {
NBTTagCompound nbttagcompound = nbttaglist.getCompound(i);
int j = nbttagcompound.getByte("Slot") & 255;
ItemStack itemstack = ItemStack.a(nbttagcompound);
if (!itemstack.isEmpty()) {
if (j < this.items.size()) {
this.items.set(j, itemstack);
} else if (j >= 100 && j < this.armor.size() + 100) {
this.armor.set(j - 100, itemstack);
} else if (j >= 150 && j < this.extraSlots.size() + 150) {
this.extraSlots.set(j - 150, itemstack);
}
}
}
}
@Override
public boolean isNotEmpty() {
Iterator iterator = this.items.iterator();
ItemStack itemstack;
while (iterator.hasNext()) {
itemstack = (ItemStack)iterator.next();
if (!itemstack.isEmpty()) {
return false;
}
}
iterator = this.armor.iterator();
while (iterator.hasNext()) {
itemstack = (ItemStack)iterator.next();
if (!itemstack.isEmpty()) {
return false;
}
}
iterator = this.extraSlots.iterator();
while (iterator.hasNext()) {
itemstack = (ItemStack)iterator.next();
if (!itemstack.isEmpty()) {
return false;
}
}
return true;
}
@Nullable
@Override
public IChatBaseComponent getCustomName() {
return null;
}
@Override
public boolean b(IBlockData iblockdata) {
return this.getItem(this.itemInHandIndex).b(iblockdata);
}
@Override
public void a(float f) {
if (f > 0.0F) {
f /= 4.0F;
if (f < 1.0F) {
f = 1.0F;
}
for (int i = 0; i < this.armor.size(); ++i) {
ItemStack itemstack = this.armor.get(0);
int index = i;
if (itemstack.getItem() instanceof ItemArmor) {
itemstack.damage((int) f, this.player, (entityhuman) -> entityhuman.c(EnumItemSlot.a(EnumItemSlot.Function.ARMOR, index)));
}
}
}
}
@Override
public void dropContents() {
for (List<ItemStack> itemStacks : this.f) {
for (int i = 0; i < itemStacks.size(); ++i) {
ItemStack itemstack = itemStacks.get(i);
if (!itemstack.isEmpty()) {
itemStacks.set(i, ItemStack.a);
this.player.a(itemstack, true, false);
}
}
}
}
@Override
public boolean h(ItemStack itemstack) {
return this.f.stream().flatMap(List::stream).anyMatch(itemStack1 -> !itemStack1.isEmpty() && itemStack1.doMaterialsMatch(itemstack));
}
@Override
public void a(PlayerInventory playerinventory) {
for (int i = 0; i < playerinventory.getSize(); ++i) {
this.setItem(i, playerinventory.getItem(i));
}
this.itemInHandIndex = playerinventory.itemInHandIndex;
}
@Override
public void clear() {
this.f.forEach(List::clear);
}
@Override
public void a(AutoRecipeStackManager autorecipestackmanager) {
for (ItemStack itemstack : this.items) {
autorecipestackmanager.a(itemstack);
}
}
}
+39 -28
View File
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright (C) 2011-2018 lishid. All rights reserved.
~ Copyright (C) 2011-2022 lishid. All rights reserved.
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
@@ -19,54 +20,64 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.lishid</groupId>
<artifactId>openinvparent</artifactId>
<version>4.0.2</version>
<groupId>com.lishid</groupId>
<relativePath>../../pom.xml</relativePath>
<version>4.2.1</version>
</parent>
<artifactId>openinvcommon</artifactId>
<name>OpenInvCommon</name>
<artifactId>openinvadapter1_18_R2</artifactId>
<name>OpenInvAdapter1_18_R2</name>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<spigot.version>1.18.2-R0.1-SNAPSHOT</spigot.version>
</properties>
<dependencies>
<dependency>
<groupId>com.lishid</groupId>
<artifactId>openinvapi</artifactId>
<version>4.0.2</version>
</dependency>
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
<version>1.8.8-R0.1-SNAPSHOT</version>
<version>${spigot.version}</version>
</dependency>
<dependency>
<artifactId>spigot</artifactId>
<groupId>org.spigotmc</groupId>
<scope>provided</scope>
<version>${spigot.version}</version>
<classifier>remapped-mojang</classifier>
</dependency>
<dependency>
<artifactId>openinvapi</artifactId>
<groupId>com.lishid</groupId>
<scope>provided</scope>
</dependency>
<dependency>
<artifactId>openinvplugincore</artifactId>
<groupId>com.lishid</groupId>
</dependency>
<dependency>
<artifactId>annotations</artifactId>
<groupId>org.jetbrains</groupId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.0.0</version>
<!-- Cannot use minimizeJar: MSHADE-366 -->
<configuration>
<minimizeJar>true</minimizeJar>
<minimizeJar>false</minimizeJar>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>net.md-5</groupId>
<artifactId>specialsource-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
@@ -0,0 +1,281 @@
/*
* 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.internal.v1_18_R2;
import com.lishid.openinv.OpenInv;
import com.lishid.openinv.internal.IAnySilentContainer;
import com.lishid.openinv.util.ReflectionHelper;
import java.lang.reflect.Field;
import java.util.Optional;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.minecraft.core.BlockPos;
import net.minecraft.network.chat.TranslatableComponent;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.level.ServerPlayerGameMode;
import net.minecraft.world.CompoundContainer;
import net.minecraft.world.MenuProvider;
import net.minecraft.world.SimpleMenuProvider;
import net.minecraft.world.entity.monster.Shulker;
import net.minecraft.world.inventory.ChestMenu;
import net.minecraft.world.inventory.MenuType;
import net.minecraft.world.inventory.PlayerEnderChestContainer;
import net.minecraft.world.level.GameType;
import net.minecraft.world.level.block.BarrelBlock;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.ChestBlock;
import net.minecraft.world.level.block.DoubleBlockCombiner;
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.ChestBlockEntity;
import net.minecraft.world.level.block.entity.EnderChestBlockEntity;
import net.minecraft.world.level.block.entity.RandomizableContainerBlockEntity;
import net.minecraft.world.level.block.entity.ShulkerBoxBlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.AABB;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.Statistic;
import org.bukkit.block.ShulkerBox;
import org.bukkit.craftbukkit.v1_18_R2.CraftWorld;
import org.bukkit.entity.Player;
import org.bukkit.inventory.InventoryView;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class AnySilentContainer implements IAnySilentContainer {
private @Nullable Field serverPlayerGameModeGameType;
public AnySilentContainer() {
try {
try {
// IDE warns about field not existing, but SpecialSource does not remap strings used in reflection.
// The warning is not suppressed as a reminder that it must manually be checked on updates.
this.serverPlayerGameModeGameType = ServerPlayerGameMode.class.getDeclaredField("b");
this.serverPlayerGameModeGameType.setAccessible(true);
} catch (NoSuchFieldException e) {
Logger logger = OpenInv.getPlugin(OpenInv.class).getLogger();
logger.warning("ServerPlayerGameMode#gameModeForPlayer's obfuscated name has changed!");
logger.warning("Please report this at https://github.com/Jikoo/OpenInv/issues");
logger.warning("Attempting to fall through using reflection. Please verify that SilentContainer does not fail.");
// N.B. gameModeForPlayer is (for now) declared before previousGameModeForPlayer so silent shouldn't break.
this.serverPlayerGameModeGameType = ReflectionHelper.grabFieldByType(ServerPlayerGameMode.class, GameType.class);
}
} catch (SecurityException e) {
Logger logger = OpenInv.getPlugin(OpenInv.class).getLogger();
logger.warning("Unable to directly write player game mode! SilentContainer will fail.");
logger.log(Level.WARNING, "Error obtaining GameType field", e);
}
}
@Override
public boolean isShulkerBlocked(@NotNull ShulkerBox shulkerBox) {
org.bukkit.World bukkitWorld = shulkerBox.getWorld();
if (!(bukkitWorld instanceof CraftWorld)) {
bukkitWorld = Bukkit.getWorld(bukkitWorld.getUID());
}
if (!(bukkitWorld instanceof CraftWorld craftWorld)) {
Exception exception = new IllegalStateException("AnySilentContainer access attempted on an unknown world!");
OpenInv.getPlugin(OpenInv.class).getLogger().log(Level.WARNING, exception.getMessage(), exception);
return false;
}
final ServerLevel world = craftWorld.getHandle();
final BlockPos blockPosition = new BlockPos(shulkerBox.getX(), shulkerBox.getY(), shulkerBox.getZ());
final BlockEntity tile = world.getBlockEntity(blockPosition);
if (!(tile instanceof ShulkerBoxBlockEntity shulkerBoxBlockEntity)
|| shulkerBoxBlockEntity.getAnimationStatus() != ShulkerBoxBlockEntity.AnimationStatus.CLOSED) {
return false;
}
BlockState blockState = world.getBlockState(blockPosition);
// See net.minecraft.world.level.block.ShulkerBoxBlock#canOpen
AABB boundingBox = Shulker.getProgressDeltaAabb(blockState.getValue(ShulkerBoxBlock.FACING), 0.0F, 0.5F)
.move(blockPosition)
.deflate(1.0E-6D);
return !world.noCollision(boundingBox);
}
@Override
public boolean activateContainer(
@NotNull final Player bukkitPlayer,
final boolean silentchest,
@NotNull final org.bukkit.block.Block bukkitBlock) {
// Silent ender chest is API-only
if (silentchest && bukkitBlock.getType() == Material.ENDER_CHEST) {
bukkitPlayer.openInventory(bukkitPlayer.getEnderChest());
bukkitPlayer.incrementStatistic(Statistic.ENDERCHEST_OPENED);
return true;
}
ServerPlayer player = PlayerDataManager.getHandle(bukkitPlayer);
final ServerLevel level = player.getLevel();
final BlockPos blockPos = new BlockPos(bukkitBlock.getX(), bukkitBlock.getY(), bukkitBlock.getZ());
final BlockEntity blockEntity = level.getBlockEntity(blockPos);
if (blockEntity == null) {
return false;
}
if (blockEntity instanceof EnderChestBlockEntity enderChestTile) {
// Anychest ender chest. See net.minecraft.world.level.block.EnderChestBlock
PlayerEnderChestContainer enderChest = player.getEnderChestInventory();
enderChest.setActiveChest(enderChestTile);
player.openMenu(new SimpleMenuProvider((containerCounter, playerInventory, ignored) -> {
MenuType<?> containers = PlayerDataManager.getContainers(enderChest.getContainerSize());
int rows = enderChest.getContainerSize() / 9;
return new ChestMenu(containers, containerCounter, playerInventory, enderChest, rows);
}, new TranslatableComponent("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
Optional<MenuProvider> menuOptional = chestBlock.combine(blockState, level, blockPos, true).apply(
// Combiner is a copy of private ChestBlock.MENU_PROVIDER_COMBINER
new DoubleBlockCombiner.Combiner<ChestBlockEntity, Optional<MenuProvider>>() {
@Override
public Optional<MenuProvider> acceptDouble(ChestBlockEntity localChest1, ChestBlockEntity localChest2) {
CompoundContainer doubleChest = new CompoundContainer(localChest1, localChest2);
return Optional.of(new ChestBlock.DoubleInventory(localChest1, localChest2, doubleChest));
}
@Override
public Optional<MenuProvider> acceptSingle(ChestBlockEntity localChest) {
return Optional.of(localChest);
}
@Override
public Optional<MenuProvider> acceptNone() {
return Optional.empty();
}
});
if (menuOptional.isEmpty()) {
OpenInv.getPlugin(OpenInv.class).sendSystemMessage(bukkitPlayer, "messages.error.lootNotGenerated");
return false;
}
menuProvider = menuOptional.get();
if (block instanceof TrappedChestBlock) {
bukkitPlayer.incrementStatistic(Statistic.TRAPPED_CHEST_TRIGGERED);
} else {
bukkitPlayer.incrementStatistic(Statistic.CHEST_OPENED);
}
}
if (block instanceof ShulkerBoxBlock) {
bukkitPlayer.incrementStatistic(Statistic.SHULKER_BOX_OPENED);
}
if (block instanceof BarrelBlock) {
bukkitPlayer.incrementStatistic(Statistic.OPEN_BARREL);
}
// AnyChest only - SilentChest not active, container unsupported, or unnecessary.
if (!silentchest || player.gameMode.getGameModeForPlayer() == GameType.SPECTATOR) {
player.openMenu(menuProvider);
return true;
}
// SilentChest requires access to setting players' game mode directly.
if (this.serverPlayerGameModeGameType == null) {
return false;
}
if (blockEntity instanceof RandomizableContainerBlockEntity lootable) {
if (lootable.lootTable != null) {
OpenInv.getPlugin(OpenInv.class).sendSystemMessage(bukkitPlayer, "messages.error.lootNotGenerated");
return false;
}
}
GameType gameType = player.gameMode.getGameModeForPlayer();
this.forceGameType(player, GameType.SPECTATOR);
player.openMenu(menuProvider);
this.forceGameType(player, gameType);
return true;
}
@Override
public void deactivateContainer(@NotNull final Player bukkitPlayer) {
if (this.serverPlayerGameModeGameType == null) {
return;
}
InventoryView view = bukkitPlayer.getOpenInventory();
switch (view.getType()) {
case CHEST:
case ENDER_CHEST:
case SHULKER_BOX:
case BARREL:
break;
default:
return;
}
ServerPlayer player = PlayerDataManager.getHandle(bukkitPlayer);
// Force game mode change without informing plugins or players.
// Regular game mode set calls GameModeChangeEvent and is cancellable.
GameType gameType = player.gameMode.getGameModeForPlayer();
this.forceGameType(player, GameType.SPECTATOR);
// ServerPlayer#closeContainer cannot be called without entering an
// infinite loop because this method is called during inventory close.
// From ServerPlayer#closeContainer -> CraftEventFactory#handleInventoryCloseEvent
player.containerMenu.transferTo(player.inventoryMenu, player.getBukkitEntity());
// From ServerPlayer#closeContainer
player.doCloseContainer();
// Regular inventory close will handle the rest - packet sending, etc.
// Revert forced game mode.
this.forceGameType(player, gameType);
}
private void forceGameType(final ServerPlayer player, final GameType gameMode) {
if (this.serverPlayerGameModeGameType == null) {
// No need to warn repeatedly, error on startup and lack of function should be enough.
return;
}
try {
this.serverPlayerGameModeGameType.setAccessible(true);
this.serverPlayerGameModeGameType.set(player.gameMode, gameMode);
} catch (IllegalArgumentException | IllegalAccessException e) {
e.printStackTrace();
}
}
}
@@ -0,0 +1,74 @@
/*
* 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.internal.v1_18_R2;
import java.io.File;
import net.minecraft.Util;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtIo;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.level.storage.PlayerDataStorage;
import org.apache.logging.log4j.LogManager;
import org.bukkit.craftbukkit.v1_18_R2.CraftServer;
import org.bukkit.craftbukkit.v1_18_R2.entity.CraftPlayer;
public class OpenPlayer extends CraftPlayer {
public OpenPlayer(CraftServer server, ServerPlayer entity) {
super(server, entity);
}
@Override
public void loadData() {
// See CraftPlayer#loadData
CompoundTag loaded = this.server.getHandle().playerIo.load(this.getHandle());
if (loaded != null) {
readExtraData(loaded);
}
}
@Override
public void saveData() {
ServerPlayer player = this.getHandle();
// See net.minecraft.world.level.storage.PlayerDataStorage#save(EntityHuman)
try {
PlayerDataStorage worldNBTStorage = player.server.getPlayerList().playerIo;
CompoundTag playerData = player.saveWithoutId(new CompoundTag());
setExtraData(playerData);
if (!isOnline()) {
// Special case: save old vehicle data
CompoundTag oldData = worldNBTStorage.load(player);
if (oldData != null && oldData.contains("RootVehicle", 10)) {
// See net.minecraft.server.PlayerList#a(NetworkManager, EntityPlayer) and net.minecraft.server.EntityPlayer#b(NBTTagCompound)
playerData.put("RootVehicle", oldData.getCompound("RootVehicle"));
}
}
File file = File.createTempFile(player.getStringUUID() + "-", ".dat", worldNBTStorage.getPlayerDir());
NbtIo.writeCompressed(playerData, file);
File file1 = new File(worldNBTStorage.getPlayerDir(), player.getStringUUID() + ".dat");
File file2 = new File(worldNBTStorage.getPlayerDir(), player.getStringUUID() + ".dat_old");
Util.safeReplaceFile(file1, file, file2);
} catch (Exception e) {
LogManager.getLogger().warn("Failed to save player data for {}: {}", player.getName().getString(), e.getMessage());
}
}
}
@@ -0,0 +1,233 @@
/*
* 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.internal.v1_18_R2;
import com.lishid.openinv.OpenInv;
import com.lishid.openinv.internal.IPlayerDataManager;
import com.lishid.openinv.internal.ISpecialInventory;
import com.lishid.openinv.internal.OpenInventoryView;
import com.mojang.authlib.GameProfile;
import java.lang.reflect.Field;
import java.util.logging.Logger;
import net.minecraft.network.chat.TextComponent;
import net.minecraft.network.protocol.game.ClientboundOpenScreenPacket;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.inventory.MenuType;
import net.minecraft.world.level.Level;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.Server;
import org.bukkit.craftbukkit.v1_18_R2.CraftServer;
import org.bukkit.craftbukkit.v1_18_R2.entity.CraftPlayer;
import org.bukkit.craftbukkit.v1_18_R2.event.CraftEventFactory;
import org.bukkit.craftbukkit.v1_18_R2.inventory.CraftContainer;
import org.bukkit.entity.Player;
import org.bukkit.inventory.InventoryView;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class PlayerDataManager implements IPlayerDataManager {
private @Nullable Field bukkitEntity;
public PlayerDataManager() {
try {
bukkitEntity = Entity.class.getDeclaredField("bukkitEntity");
} catch (NoSuchFieldException e) {
Logger logger = OpenInv.getPlugin(OpenInv.class).getLogger();
logger.warning("Unable to obtain field to inject custom save process - players' mounts may be deleted when loaded.");
logger.log(java.util.logging.Level.WARNING, e.getMessage(), e);
bukkitEntity = null;
}
}
public static @NotNull ServerPlayer getHandle(final Player player) {
if (player instanceof CraftPlayer) {
return ((CraftPlayer) player).getHandle();
}
Server server = player.getServer();
ServerPlayer nmsPlayer = null;
if (server instanceof CraftServer) {
nmsPlayer = ((CraftServer) server).getHandle().getPlayer(player.getUniqueId());
}
if (nmsPlayer == null) {
// Could use reflection to examine fields, but it's honestly not worth the bother.
throw new RuntimeException("Unable to fetch EntityPlayer from provided Player implementation");
}
return nmsPlayer;
}
@Override
public @Nullable Player loadPlayer(@NotNull final OfflinePlayer offline) {
// Ensure player has data
if (!offline.hasPlayedBefore()) {
return null;
}
// Create a profile and entity to load the player data
// See net.minecraft.server.PlayerList#attemptLogin
GameProfile profile = new GameProfile(offline.getUniqueId(),
offline.getName() != null ? offline.getName() : offline.getUniqueId().toString());
MinecraftServer server = ((CraftServer) Bukkit.getServer()).getServer();
ServerLevel worldServer = server.getLevel(Level.OVERWORLD);
if (worldServer == null) {
return null;
}
ServerPlayer entity = new ServerPlayer(server, worldServer, profile);
try {
injectPlayer(entity);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
// Get the bukkit entity
Player target = entity.getBukkitEntity();
if (target != null) {
// Load data
target.loadData();
}
// Return the entity
return target;
}
void injectPlayer(ServerPlayer player) throws IllegalAccessException {
if (bukkitEntity == null) {
return;
}
bukkitEntity.setAccessible(true);
bukkitEntity.set(player, new OpenPlayer(player.server.server, player));
}
@NotNull
@Override
public Player inject(@NotNull Player player) {
try {
ServerPlayer nmsPlayer = getHandle(player);
if (nmsPlayer.getBukkitEntity() instanceof OpenPlayer openPlayer) {
return openPlayer;
}
injectPlayer(nmsPlayer);
return nmsPlayer.getBukkitEntity();
} catch (IllegalAccessException e) {
e.printStackTrace();
return player;
}
}
@Nullable
@Override
public InventoryView openInventory(@NotNull Player player, @NotNull ISpecialInventory inventory) {
ServerPlayer nmsPlayer = getHandle(player);
if (nmsPlayer.connection == null) {
return null;
}
InventoryView view = getView(player, inventory);
if (view == null) {
return player.openInventory(inventory.getBukkitInventory());
}
AbstractContainerMenu container = new CraftContainer(view, nmsPlayer, nmsPlayer.nextContainerCounter()) {
@Override
public MenuType<?> getType() {
return getContainers(inventory.getBukkitInventory().getSize());
}
};
container.setTitle(new TextComponent(view.getTitle()));
container = CraftEventFactory.callInventoryOpenEvent(nmsPlayer, container);
if (container == null) {
return null;
}
nmsPlayer.connection.send(new ClientboundOpenScreenPacket(container.containerId, container.getType(),
new TextComponent(container.getBukkitView().getTitle())));
nmsPlayer.containerMenu = container;
nmsPlayer.initMenu(container);
return container.getBukkitView();
}
private @Nullable InventoryView getView(Player player, ISpecialInventory inventory) {
if (inventory instanceof SpecialEnderChest) {
return new OpenInventoryView(player, inventory, "container.enderchest", "'s Ender Chest");
} else if (inventory instanceof SpecialPlayerInventory) {
return new OpenInventoryView(player, inventory, "container.player", "'s Inventory");
} else {
return null;
}
}
static @NotNull MenuType<?> getContainers(int inventorySize) {
return switch (inventorySize) {
case 9 -> MenuType.GENERIC_9x1;
case 18 -> MenuType.GENERIC_9x2;
case 36 -> MenuType.GENERIC_9x4; // PLAYER
case 41, 45 -> MenuType.GENERIC_9x5;
case 54 -> MenuType.GENERIC_9x6;
default -> MenuType.GENERIC_9x3; // Default 27-slot inventory
};
}
@Override
public int convertToPlayerSlot(InventoryView view, int rawSlot) {
int topSize = view.getTopInventory().getSize();
if (topSize <= rawSlot) {
// Slot is not inside special inventory, use Bukkit logic.
return view.convertSlot(rawSlot);
}
// Main inventory, slots 0-26 -> 9-35
if (rawSlot < 27) {
return rawSlot + 9;
}
// Hotbar, slots 27-35 -> 0-8
if (rawSlot < 36) {
return rawSlot - 27;
}
// Armor, slots 36-39 -> 39-36
if (rawSlot < 40) {
return 36 + (39 - rawSlot);
}
// Off hand
if (rawSlot == 40) {
return 40;
}
// Drop slots, "out of inventory"
return -1;
}
}
@@ -0,0 +1,339 @@
/*
* 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.internal.v1_18_R2;
import com.lishid.openinv.internal.ISpecialEnderChest;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import net.minecraft.core.NonNullList;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.ContainerHelper;
import net.minecraft.world.ContainerListener;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.entity.player.StackedContents;
import net.minecraft.world.inventory.PlayerEnderChestContainer;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.entity.EnderChestBlockEntity;
import org.bukkit.Location;
import org.bukkit.craftbukkit.v1_18_R2.entity.CraftHumanEntity;
import org.bukkit.craftbukkit.v1_18_R2.inventory.CraftInventory;
import org.bukkit.entity.HumanEntity;
import org.bukkit.inventory.InventoryHolder;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class SpecialEnderChest extends PlayerEnderChestContainer implements ISpecialEnderChest {
private final CraftInventory inventory;
private ServerPlayer owner;
private NonNullList<ItemStack> items;
private boolean playerOnline;
public SpecialEnderChest(final org.bukkit.entity.Player player, final Boolean online) {
super(PlayerDataManager.getHandle(player));
this.inventory = new CraftInventory(this);
this.owner = PlayerDataManager.getHandle(player);
this.playerOnline = online;
this.items = this.owner.getEnderChestInventory().items;
}
@Override
public @NotNull CraftInventory getBukkitInventory() {
return inventory;
}
@Override
public void setPlayerOffline() {
this.playerOnline = false;
}
@Override
public void setPlayerOnline(@NotNull final org.bukkit.entity.Player player) {
if (!this.playerOnline) {
try {
this.owner = PlayerDataManager.getHandle(player);
PlayerEnderChestContainer enderChest = owner.getEnderChestInventory();
for (int i = 0; i < enderChest.getContainerSize(); ++i) {
enderChest.setItem(i, this.items.get(i));
}
this.items = enderChest.items;
enderChest.transaction.addAll(this.transaction);
} catch (Exception ignored) {}
this.playerOnline = true;
}
}
@Override
public @NotNull org.bukkit.entity.Player getPlayer() {
return owner.getBukkitEntity();
}
@Override
public void setChanged() {
this.owner.getEnderChestInventory().setChanged();
}
@Override
public List<ItemStack> getContents() {
return this.items;
}
@Override
public void onOpen(CraftHumanEntity who) {
this.owner.getEnderChestInventory().onOpen(who);
}
@Override
public void onClose(CraftHumanEntity who) {
this.owner.getEnderChestInventory().onClose(who);
}
@Override
public List<HumanEntity> getViewers() {
return this.owner.getEnderChestInventory().getViewers();
}
@Override
public boolean stillValid(Player player) {
return true;
}
@Override
public void setActiveChest(EnderChestBlockEntity enderChest) {
this.owner.getEnderChestInventory().setActiveChest(enderChest);
}
@Override
public boolean isActiveChest(EnderChestBlockEntity enderChest) {
return this.owner.getEnderChestInventory().isActiveChest(enderChest);
}
@Override
public int getMaxStackSize() {
return this.owner.getEnderChestInventory().getMaxStackSize();
}
@Override
public void setMaxStackSize(int i) {
this.owner.getEnderChestInventory().setMaxStackSize(i);
}
@Override
public InventoryHolder getOwner() {
return this.owner.getEnderChestInventory().getOwner();
}
@Override
public @Nullable Location getLocation() {
return null;
}
@Override
public void addListener(ContainerListener listener) {
this.owner.getEnderChestInventory().addListener(listener);
}
@Override
public void removeListener(ContainerListener listener) {
this.owner.getEnderChestInventory().removeListener(listener);
}
@Override
public ItemStack getItem(int i) {
return i >= 0 && i < this.items.size() ? this.items.get(i) : ItemStack.EMPTY;
}
@Override
public ItemStack removeItem(int i, int j) {
ItemStack itemstack = ContainerHelper.removeItem(this.items, i, j);
if (!itemstack.isEmpty()) {
this.setChanged();
}
return itemstack;
}
@Override
public ItemStack addItem(ItemStack itemstack) {
ItemStack localItem = itemstack.copy();
this.moveItemToOccupiedSlotsWithSameType(localItem);
if (localItem.isEmpty()) {
return ItemStack.EMPTY;
} else {
this.moveItemToEmptySlots(localItem);
return localItem.isEmpty() ? ItemStack.EMPTY : localItem;
}
}
@Override
public boolean canAddItem(ItemStack itemstack) {
for (ItemStack itemstack1 : this.items) {
if (itemstack1.isEmpty() || ItemStack.isSameItemSameTags(itemstack1, itemstack) && itemstack1.getCount() < itemstack1.getMaxStackSize()) {
return true;
}
}
return false;
}
private void moveItemToEmptySlots(ItemStack itemstack) {
for(int i = 0; i < this.getContainerSize(); ++i) {
ItemStack localItem = this.getItem(i);
if (localItem.isEmpty()) {
this.setItem(i, itemstack.copy());
itemstack.setCount(0);
return;
}
}
}
private void moveItemToOccupiedSlotsWithSameType(ItemStack itemstack) {
for(int i = 0; i < this.getContainerSize(); ++i) {
ItemStack localItem = this.getItem(i);
if (ItemStack.isSameItemSameTags(localItem, itemstack)) {
this.moveItemsBetweenStacks(itemstack, localItem);
if (itemstack.isEmpty()) {
return;
}
}
}
}
private void moveItemsBetweenStacks(ItemStack itemstack, ItemStack itemstack1) {
int i = Math.min(this.getMaxStackSize(), itemstack1.getMaxStackSize());
int j = Math.min(itemstack.getCount(), i - itemstack1.getCount());
if (j > 0) {
itemstack1.grow(j);
itemstack.shrink(j);
this.setChanged();
}
}
@Override
public ItemStack removeItemNoUpdate(int i) {
ItemStack itemstack = this.items.get(i);
if (itemstack.isEmpty()) {
return ItemStack.EMPTY;
} else {
this.items.set(i, ItemStack.EMPTY);
return itemstack;
}
}
@Override
public void setItem(int i, ItemStack itemstack) {
this.items.set(i, itemstack);
if (!itemstack.isEmpty() && itemstack.getCount() > this.getMaxStackSize()) {
itemstack.setCount(this.getMaxStackSize());
}
this.setChanged();
}
@Override
public int getContainerSize() {
return this.owner.getEnderChestInventory().getContainerSize();
}
@Override
public boolean isEmpty() {
return this.items.stream().allMatch(ItemStack::isEmpty);
}
@Override
public void startOpen(Player player) {
}
@Override
public void stopOpen(Player player) {
}
@Override
public boolean canPlaceItem(int i, ItemStack itemstack) {
return true;
}
@Override
public void clearContent() {
this.items.clear();
this.setChanged();
}
@Override
public void fillStackedContents(StackedContents stackedContents) {
for (ItemStack itemstack : this.items) {
stackedContents.accountStack(itemstack);
}
}
@Override
public List<ItemStack> removeAllItems() {
List<ItemStack> list = this.items.stream().filter(Predicate.not(ItemStack::isEmpty)).collect(Collectors.toList());
this.clearContent();
return list;
}
@Override
public ItemStack removeItemType(Item item, int i) {
ItemStack itemstack = new ItemStack(item, 0);
for(int j = this.getContainerSize() - 1; j >= 0; --j) {
ItemStack localItem = this.getItem(j);
if (localItem.getItem().equals(item)) {
int k = i - itemstack.getCount();
ItemStack splitItem = localItem.split(k);
itemstack.grow(splitItem.getCount());
if (itemstack.getCount() == i) {
break;
}
}
}
if (!itemstack.isEmpty()) {
this.setChanged();
}
return itemstack;
}
@Override
public String toString() {
return this.items.stream().filter((itemStack) -> !itemStack.isEmpty()).collect(Collectors.toList()).toString();
}
@Override
public void fromTag(ListTag listTag) {
for (int i = 0; i < this.getContainerSize(); ++i) {
this.setItem(i, ItemStack.EMPTY);
}
for (int i = 0; i < listTag.size(); ++i) {
CompoundTag compoundTag = listTag.getCompound(i);
int j = compoundTag.getByte("Slot") & 255;
if (j < this.getContainerSize()) {
this.setItem(j, ItemStack.of(compoundTag));
}
}
}
}
@@ -0,0 +1,788 @@
/*
* 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.internal.v1_18_R2;
import com.google.common.collect.ImmutableList;
import com.lishid.openinv.internal.ISpecialPlayerInventory;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import net.minecraft.CrashReport;
import net.minecraft.CrashReportCategory;
import net.minecraft.ReportedException;
import net.minecraft.core.NonNullList;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.game.ClientboundContainerSetSlotPacket;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.tags.TagKey;
import net.minecraft.world.Container;
import net.minecraft.world.ContainerHelper;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.entity.player.StackedContents;
import net.minecraft.world.item.ArmorItem;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.state.BlockState;
import org.bukkit.Location;
import org.bukkit.craftbukkit.v1_18_R2.entity.CraftHumanEntity;
import org.bukkit.craftbukkit.v1_18_R2.inventory.CraftInventory;
import org.bukkit.entity.HumanEntity;
import org.bukkit.inventory.InventoryHolder;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class SpecialPlayerInventory extends Inventory implements ISpecialPlayerInventory {
private final CraftInventory inventory;
private boolean playerOnline;
private Player player;
private NonNullList<ItemStack> items;
private NonNullList<ItemStack> armor;
private NonNullList<ItemStack> offhand;
private List<NonNullList<ItemStack>> compartments;
public SpecialPlayerInventory(@NotNull org.bukkit.entity.Player bukkitPlayer, @NotNull Boolean online) {
super(PlayerDataManager.getHandle(bukkitPlayer));
this.inventory = new CraftInventory(this);
this.playerOnline = online;
this.player = super.player;
this.selected = player.getInventory().selected;
this.items = this.player.getInventory().items;
this.armor = this.player.getInventory().armor;
this.offhand = this.player.getInventory().offhand;
this.compartments = ImmutableList.of(this.items, this.armor, this.offhand);
}
@Override
public void setPlayerOnline(@NotNull org.bukkit.entity.Player player) {
if (!this.playerOnline) {
Player entityPlayer = PlayerDataManager.getHandle(player);
entityPlayer.getInventory().transaction.addAll(this.transaction);
this.player = entityPlayer;
for (int i = 0; i < getContainerSize(); ++i) {
this.player.getInventory().setItem(i, getRawItem(i));
}
this.player.getInventory().selected = this.selected;
this.items = this.player.getInventory().items;
this.armor = this.player.getInventory().armor;
this.offhand = this.player.getInventory().offhand;
this.compartments = ImmutableList.of(this.items, this.armor, this.offhand);
this.playerOnline = true;
}
}
@Override
public @NotNull CraftInventory getBukkitInventory() {
return this.inventory;
}
@Override
public void setPlayerOffline() {
this.playerOnline = false;
}
@Override
public @NotNull HumanEntity getPlayer() {
return this.player.getBukkitEntity();
}
private @NotNull ItemStack getRawItem(int i) {
if (i < 0) {
return ItemStack.EMPTY;
}
NonNullList<ItemStack> list;
for (Iterator<NonNullList<ItemStack>> iterator = this.compartments.iterator(); iterator.hasNext(); i -= list.size()) {
list = iterator.next();
if (i < list.size()) {
return list.get(i);
}
}
return ItemStack.EMPTY;
}
private void setRawItem(int i, @NotNull ItemStack itemStack) {
if (i < 0) {
return;
}
NonNullList<ItemStack> list;
for (Iterator<NonNullList<ItemStack>> iterator = this.compartments.iterator(); iterator.hasNext(); i -= list.size()) {
list = iterator.next();
if (i < list.size()) {
list.set(i, itemStack);
}
}
}
private static record IndexedCompartment(@Nullable NonNullList<ItemStack> compartment, int index) {}
private @NotNull SpecialPlayerInventory.IndexedCompartment getIndexedContent(int index) {
if (index < items.size()) {
return new IndexedCompartment(items, getReversedItemSlotNum(index));
}
index -= items.size();
if (index < armor.size()) {
return new IndexedCompartment(armor, getReversedArmorSlotNum(index));
}
index -= armor.size();
if (index < offhand.size()) {
return new IndexedCompartment(offhand, index);
}
index -= offhand.size();
return new IndexedCompartment(null, index);
}
private int getReversedArmorSlotNum(final int i) {
if (i == 0) {
return 3;
}
if (i == 1) {
return 2;
}
if (i == 2) {
return 1;
}
if (i == 3) {
return 0;
}
return i;
}
private int getReversedItemSlotNum(final int i) {
if (i >= 27) {
return i - 27;
}
return i + 9;
}
private boolean contains(Predicate<ItemStack> predicate) {
return this.compartments.stream().flatMap(NonNullList::stream).anyMatch(predicate);
}
@Override
public List<ItemStack> getArmorContents() {
return this.armor;
}
@Override
public void onOpen(CraftHumanEntity who) {
this.player.getInventory().onOpen(who);
}
@Override
public void onClose(CraftHumanEntity who) {
this.player.getInventory().onClose(who);
}
@Override
public List<HumanEntity> getViewers() {
return this.player.getInventory().getViewers();
}
@Override
public InventoryHolder getOwner() {
return this.player.getBukkitEntity();
}
@Override
public int getMaxStackSize() {
return this.player.getInventory().getMaxStackSize();
}
@Override
public void setMaxStackSize(int size) {
this.player.getInventory().setMaxStackSize(size);
}
@Override
public Location getLocation() {
return this.player.getBukkitEntity().getLocation();
}
@Override
public boolean hasCustomName() {
return false;
}
@Override
public List<ItemStack> getContents() {
return this.compartments.stream().flatMap(Collection::stream).collect(Collectors.toList());
}
@Override
public ItemStack getSelected() {
return isHotbarSlot(this.selected) ? this.items.get(this.selected) : ItemStack.EMPTY;
}
private boolean hasRemainingSpaceForItem(ItemStack itemstack, ItemStack itemstack1) {
return !itemstack.isEmpty() && ItemStack.isSameItemSameTags(itemstack, itemstack1) && itemstack.isStackable() && itemstack.getCount() < itemstack.getMaxStackSize() && itemstack.getCount() < this.getMaxStackSize();
}
@Override
public int canHold(ItemStack itemstack) {
int remains = itemstack.getCount();
for (int i = 0; i < this.items.size(); ++i) {
ItemStack itemstack1 = this.getRawItem(i);
if (itemstack1.isEmpty()) {
return itemstack.getCount();
}
if (this.hasRemainingSpaceForItem(itemstack1, itemstack)) {
remains -= (itemstack1.getMaxStackSize() < this.getMaxStackSize() ? itemstack1.getMaxStackSize() : this.getMaxStackSize()) - itemstack1.getCount();
}
if (remains <= 0) {
return itemstack.getCount();
}
}
ItemStack offhandItemStack = this.getRawItem(this.items.size() + this.armor.size());
if (this.hasRemainingSpaceForItem(offhandItemStack, itemstack)) {
remains -= (offhandItemStack.getMaxStackSize() < this.getMaxStackSize() ? offhandItemStack.getMaxStackSize() : this.getMaxStackSize()) - offhandItemStack.getCount();
}
return remains <= 0 ? itemstack.getCount() : itemstack.getCount() - remains;
}
@Override
public int getFreeSlot() {
for(int i = 0; i < this.items.size(); ++i) {
if (this.items.get(i).isEmpty()) {
return i;
}
}
return -1;
}
@Override
public void setPickedItem(ItemStack itemstack) {
int i = this.findSlotMatchingItem(itemstack);
if (isHotbarSlot(i)) {
this.selected = i;
} else if (i == -1) {
this.selected = this.getSuitableHotbarSlot();
if (!this.items.get(this.selected).isEmpty()) {
int j = this.getFreeSlot();
if (j != -1) {
this.items.set(j, this.items.get(this.selected));
}
}
this.items.set(this.selected, itemstack);
} else {
this.pickSlot(i);
}
}
@Override
public void pickSlot(int i) {
this.selected = this.getSuitableHotbarSlot();
ItemStack itemstack = this.items.get(this.selected);
this.items.set(this.selected, this.items.get(i));
this.items.set(i, itemstack);
}
@Override
public int findSlotMatchingItem(ItemStack itemstack) {
for(int i = 0; i < this.items.size(); ++i) {
if (!this.items.get(i).isEmpty() && ItemStack.isSameItemSameTags(itemstack, this.items.get(i))) {
return i;
}
}
return -1;
}
@Override
public int findSlotMatchingUnusedItem(ItemStack itemStack) {
for(int i = 0; i < this.items.size(); ++i) {
ItemStack localItem = this.items.get(i);
if (!this.items.get(i).isEmpty() && ItemStack.isSameItemSameTags(itemStack, this.items.get(i)) && !this.items.get(i).isDamaged() && !localItem.isEnchanted() && !localItem.hasCustomHoverName()) {
return i;
}
}
return -1;
}
@Override
public int getSuitableHotbarSlot() {
int i;
int j;
for(j = 0; j < 9; ++j) {
i = (this.selected + j) % 9;
if (this.items.get(i).isEmpty()) {
return i;
}
}
for(j = 0; j < 9; ++j) {
i = (this.selected + j) % 9;
if (!this.items.get(i).isEnchanted()) {
return i;
}
}
return this.selected;
}
@Override
public void swapPaint(double d0) {
if (d0 > 0.0D) {
d0 = 1.0D;
}
if (d0 < 0.0D) {
d0 = -1.0D;
}
this.selected = (int) (this.selected - d0);
while (this.selected < 0) {
this.selected += 9;
}
while(this.selected >= 9) {
this.selected -= 9;
}
}
@Override
public int clearOrCountMatchingItems(Predicate<ItemStack> predicate, int i, Container container) {
byte b0 = 0;
boolean flag = i == 0;
int j = b0 + ContainerHelper.clearOrCountMatchingItems(this, predicate, i - b0, flag);
j += ContainerHelper.clearOrCountMatchingItems(container, predicate, i - j, flag);
ItemStack itemstack = this.player.containerMenu.getCarried();
j += ContainerHelper.clearOrCountMatchingItems(itemstack, predicate, i - j, flag);
if (itemstack.isEmpty()) {
this.player.containerMenu.setCarried(ItemStack.EMPTY);
}
return j;
}
private int addResource(ItemStack itemstack) {
int i = this.getSlotWithRemainingSpace(itemstack);
if (i == -1) {
i = this.getFreeSlot();
}
return i == -1 ? itemstack.getCount() : this.addResource(i, itemstack);
}
private int addResource(int i, ItemStack itemstack) {
Item item = itemstack.getItem();
int j = itemstack.getCount();
ItemStack localItemStack = this.getRawItem(i);
if (localItemStack.isEmpty()) {
localItemStack = new ItemStack(item, 0);
if (itemstack.hasTag()) {
// hasTag ensures tag not null
//noinspection ConstantConditions
localItemStack.setTag(itemstack.getTag().copy());
}
this.setRawItem(i, localItemStack);
}
int k = Math.min(j, localItemStack.getMaxStackSize() - localItemStack.getCount());
if (k > this.getMaxStackSize() - localItemStack.getCount()) {
k = this.getMaxStackSize() - localItemStack.getCount();
}
if (k != 0) {
j -= k;
localItemStack.grow(k);
localItemStack.setPopTime(5);
}
return j;
}
@Override
public int getSlotWithRemainingSpace(ItemStack itemstack) {
if (this.hasRemainingSpaceForItem(this.getRawItem(this.selected), itemstack)) {
return this.selected;
} else if (this.hasRemainingSpaceForItem(this.getRawItem(40), itemstack)) {
return 40;
} else {
for(int i = 0; i < this.items.size(); ++i) {
if (this.hasRemainingSpaceForItem(this.items.get(i), itemstack)) {
return i;
}
}
return -1;
}
}
@Override
public void tick() {
for (NonNullList<ItemStack> compartment : this.compartments) {
for (int i = 0; i < compartment.size(); ++i) {
if (!compartment.get(i).isEmpty()) {
compartment.get(i).inventoryTick(this.player.level, this.player, i, this.selected == i);
}
}
}
}
@Override
public boolean add(ItemStack itemStack) {
return this.add(-1, itemStack);
}
@Override
public boolean add(int i, ItemStack itemStack) {
if (itemStack.isEmpty()) {
return false;
} else {
try {
if (itemStack.isDamaged()) {
if (i == -1) {
i = this.getFreeSlot();
}
if (i >= 0) {
this.items.set(i, itemStack.copy());
this.items.get(i).setPopTime(5);
itemStack.setCount(0);
return true;
} else if (this.player.getAbilities().instabuild) {
itemStack.setCount(0);
return true;
} else {
return false;
}
} else {
int j;
do {
j = itemStack.getCount();
if (i == -1) {
itemStack.setCount(this.addResource(itemStack));
} else {
itemStack.setCount(this.addResource(i, itemStack));
}
} while(!itemStack.isEmpty() && itemStack.getCount() < j);
if (itemStack.getCount() == j && this.player.getAbilities().instabuild) {
itemStack.setCount(0);
return true;
} else {
return itemStack.getCount() < j;
}
}
} catch (Throwable var6) {
CrashReport crashReport = CrashReport.forThrowable(var6, "Adding item to inventory");
CrashReportCategory crashReportCategory = crashReport.addCategory("Item being added");
crashReportCategory.setDetail("Item ID", Item.getId(itemStack.getItem()));
crashReportCategory.setDetail("Item data", itemStack.getDamageValue());
crashReportCategory.setDetail("Item name", () -> itemStack.getHoverName().getString());
throw new ReportedException(crashReport);
}
}
}
@Override
public void placeItemBackInInventory(ItemStack itemStack) {
this.placeItemBackInInventory(itemStack, true);
}
@Override
public void placeItemBackInInventory(ItemStack itemStack, boolean flag) {
while(true) {
if (!itemStack.isEmpty()) {
int i = this.getSlotWithRemainingSpace(itemStack);
if (i == -1) {
i = this.getFreeSlot();
}
if (i != -1) {
int j = itemStack.getMaxStackSize() - this.getRawItem(i).getCount();
if (this.add(i, itemStack.split(j)) && flag && this.player instanceof ServerPlayer) {
((ServerPlayer)this.player).connection.send(new ClientboundContainerSetSlotPacket(-2, 0, i, this.getRawItem(i)));
}
continue;
}
this.player.drop(itemStack, false);
}
return;
}
}
@Override
public ItemStack removeItem(int rawIndex, final int j) {
IndexedCompartment indexedCompartment = getIndexedContent(rawIndex);
if (indexedCompartment.compartment() == null
|| indexedCompartment.compartment().get(indexedCompartment.index()).isEmpty()) {
return ItemStack.EMPTY;
}
return ContainerHelper.removeItem(indexedCompartment.compartment(), indexedCompartment.index(), j);
}
@Override
public void removeItem(ItemStack itemStack) {
for (NonNullList<ItemStack> compartment : this.compartments) {
for (int i = 0; i < compartment.size(); ++i) {
if (compartment.get(i) == itemStack) {
compartment.set(i, ItemStack.EMPTY);
break;
}
}
}
}
@Override
public ItemStack removeItemNoUpdate(int rawIndex) {
IndexedCompartment indexedCompartment = getIndexedContent(rawIndex);
if (indexedCompartment.compartment() == null) {
return ItemStack.EMPTY;
}
ItemStack removed = indexedCompartment.compartment().set(indexedCompartment.index(), ItemStack.EMPTY);
if (removed.isEmpty()) {
return ItemStack.EMPTY;
}
return removed;
}
@Override
public void setItem(int rawIndex, final ItemStack itemStack) {
IndexedCompartment indexedCompartment = getIndexedContent(rawIndex);
if (indexedCompartment.compartment() == null) {
this.player.drop(itemStack, true);
return;
}
indexedCompartment.compartment().set(indexedCompartment.index(), itemStack);
}
@Override
public float getDestroySpeed(BlockState blockState) {
return this.items.get(this.selected).getDestroySpeed(blockState);
}
@Override
public ListTag save(ListTag listTag) {
for (int i = 0; i < this.items.size(); ++i) {
if (!this.items.get(i).isEmpty()) {
CompoundTag compoundTag = new CompoundTag();
compoundTag.putByte("Slot", (byte)i);
this.items.get(i).save(compoundTag);
listTag.add(compoundTag);
}
}
for (int i = 0; i < this.armor.size(); ++i) {
if (!this.armor.get(i).isEmpty()) {
CompoundTag compoundTag = new CompoundTag();
compoundTag.putByte("Slot", (byte)(i + 100));
this.armor.get(i).save(compoundTag);
listTag.add(compoundTag);
}
}
for (int i = 0; i < this.offhand.size(); ++i) {
if (!this.offhand.get(i).isEmpty()) {
CompoundTag compoundTag = new CompoundTag();
compoundTag.putByte("Slot", (byte)(i + 150));
this.offhand.get(i).save(compoundTag);
listTag.add(compoundTag);
}
}
return listTag;
}
@Override
public void load(ListTag listTag) {
this.items.clear();
this.armor.clear();
this.offhand.clear();
for(int i = 0; i < listTag.size(); ++i) {
CompoundTag compoundTag = listTag.getCompound(i);
int j = compoundTag.getByte("Slot") & 255;
ItemStack itemstack = ItemStack.of(compoundTag);
if (!itemstack.isEmpty()) {
if (j < this.items.size()) {
this.items.set(j, itemstack);
} else if (j >= 100 && j < this.armor.size() + 100) {
this.armor.set(j - 100, itemstack);
} else if (j >= 150 && j < this.offhand.size() + 150) {
this.offhand.set(j - 150, itemstack);
}
}
}
}
@Override
public int getContainerSize() {
return 45;
}
@Override
public boolean isEmpty() {
return !contains(itemStack -> !itemStack.isEmpty());
}
@Override
public ItemStack getItem(int rawIndex) {
IndexedCompartment indexedCompartment = getIndexedContent(rawIndex);
if (indexedCompartment.compartment() == null) {
return ItemStack.EMPTY;
}
return indexedCompartment.compartment().get(indexedCompartment.index());
}
@Override
public Component getName() {
return this.player.getName();
}
@Override
public ItemStack getArmor(int index) {
return this.armor.get(index);
}
@Override
public void hurtArmor(DamageSource damagesource, float damage, int[] armorIndices) {
if (damage > 0.0F) {
damage /= 4.0F;
if (damage < 1.0F) {
damage = 1.0F;
}
for (int index : armorIndices) {
ItemStack itemstack = this.armor.get(index);
if ((!damagesource.isFire() || !itemstack.getItem().isFireResistant()) && itemstack.getItem() instanceof ArmorItem) {
itemstack.hurtAndBreak((int) damage, this.player, localPlayer -> localPlayer.broadcastBreakEvent(EquipmentSlot.byTypeAndIndex(EquipmentSlot.Type.ARMOR, index)));
}
}
}
}
@Override
public void dropAll() {
for (NonNullList<ItemStack> compartment : this.compartments) {
for (int i = 0; i < compartment.size(); ++i) {
ItemStack itemstack = compartment.get(i);
if (!itemstack.isEmpty()) {
this.player.drop(itemstack, true, false);
compartment.set(i, ItemStack.EMPTY);
}
}
}
}
@Override
public void setChanged() {
super.setChanged();
}
@Override
public int getTimesChanged() {
return super.getTimesChanged();
}
@Override
public boolean stillValid(Player player) {
return true;
}
@Override
public boolean contains(ItemStack itemstack) {
return contains(itemStack -> itemStack.isEmpty() && itemStack.sameItem(itemstack));
}
@Override
public boolean contains(TagKey<Item> tagKey) {
return contains(itemStack -> !itemStack.isEmpty() && itemStack.is(tagKey));
}
@Override
public void replaceWith(Inventory inventory) {
Function<Integer, ItemStack> getter;
if (inventory instanceof SpecialPlayerInventory specialPlayerInventory) {
getter = specialPlayerInventory::getRawItem;
} else {
getter = inventory::getItem;
}
for(int i = 0; i < this.getContainerSize(); ++i) {
this.setRawItem(i, getter.apply(i));
}
this.selected = inventory.selected;
}
@Override
public void clearContent() {
for (NonNullList<ItemStack> compartment : this.compartments) {
compartment.clear();
}
}
@Override
public void fillStackedContents(StackedContents stackedContents) {
for (ItemStack itemstack : this.items) {
stackedContents.accountSimpleStack(itemstack);
}
}
@Override
public ItemStack removeFromSelected(boolean dropWholeStack) {
ItemStack itemstack = this.getSelected();
return itemstack.isEmpty() ? ItemStack.EMPTY : this.removeItem(this.selected, dropWholeStack ? itemstack.getCount() : 1);
}
}
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright (C) 2011-2019 lishid. All rights reserved.
~ Copyright (C) 2011-2022 lishid. All rights reserved.
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
@@ -20,56 +20,66 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>openinvparent</artifactId>
<groupId>com.lishid</groupId>
<artifactId>openinvinternal</artifactId>
<version>4.0.2</version>
<relativePath>../../pom.xml</relativePath>
<version>4.2.1</version>
</parent>
<artifactId>openinvadapter1_14_R1</artifactId>
<name>OpenInvAdapter1_14_R1</name>
<artifactId>openinvadapter1_19_R1</artifactId>
<name>OpenInvAdapter1_19_R1</name>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<spigot.version>1.19.1-R0.1-SNAPSHOT</spigot.version>
</properties>
<dependencies>
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
<version>${spigot.version}</version>
</dependency>
<dependency>
<artifactId>spigot</artifactId>
<version>1.14.2-R0.1-SNAPSHOT</version>
<groupId>org.spigotmc</groupId>
<scope>provided</scope>
<version>${spigot.version}</version>
<classifier>remapped-mojang</classifier>
</dependency>
<dependency>
<artifactId>openinvapi</artifactId>
<groupId>com.lishid</groupId>
<scope>provided</scope>
</dependency>
<dependency>
<artifactId>openinvplugincore</artifactId>
<groupId>com.lishid</groupId>
<artifactId>openinvcommon</artifactId>
<version>4.0.2</version>
</dependency>
<dependency>
<artifactId>annotations</artifactId>
<groupId>org.jetbrains</groupId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.0.0</version>
<!-- Cannot use minimizeJar: MSHADE-366 -->
<configuration>
<minimizeJar>true</minimizeJar>
<minimizeJar>false</minimizeJar>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>net.md-5</groupId>
<artifactId>specialsource-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
</project>
@@ -0,0 +1,281 @@
/*
* 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.internal.v1_19_R1;
import com.lishid.openinv.OpenInv;
import com.lishid.openinv.internal.IAnySilentContainer;
import com.lishid.openinv.util.ReflectionHelper;
import java.lang.reflect.Field;
import java.util.Optional;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.minecraft.core.BlockPos;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.level.ServerPlayerGameMode;
import net.minecraft.world.CompoundContainer;
import net.minecraft.world.MenuProvider;
import net.minecraft.world.SimpleMenuProvider;
import net.minecraft.world.entity.monster.Shulker;
import net.minecraft.world.inventory.ChestMenu;
import net.minecraft.world.inventory.MenuType;
import net.minecraft.world.inventory.PlayerEnderChestContainer;
import net.minecraft.world.level.GameType;
import net.minecraft.world.level.block.BarrelBlock;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.ChestBlock;
import net.minecraft.world.level.block.DoubleBlockCombiner;
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.ChestBlockEntity;
import net.minecraft.world.level.block.entity.EnderChestBlockEntity;
import net.minecraft.world.level.block.entity.RandomizableContainerBlockEntity;
import net.minecraft.world.level.block.entity.ShulkerBoxBlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.AABB;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.Statistic;
import org.bukkit.block.ShulkerBox;
import org.bukkit.craftbukkit.v1_19_R1.CraftWorld;
import org.bukkit.entity.Player;
import org.bukkit.inventory.InventoryView;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class AnySilentContainer implements IAnySilentContainer {
private @Nullable Field serverPlayerGameModeGameType;
public AnySilentContainer() {
try {
try {
// IDE warns about field not existing, but SpecialSource does not remap strings used in reflection.
// The warning is not suppressed as a reminder that it must manually be checked on updates.
this.serverPlayerGameModeGameType = ServerPlayerGameMode.class.getDeclaredField("b");
this.serverPlayerGameModeGameType.setAccessible(true);
} catch (NoSuchFieldException e) {
Logger logger = OpenInv.getPlugin(OpenInv.class).getLogger();
logger.warning("ServerPlayerGameMode#gameModeForPlayer's obfuscated name has changed!");
logger.warning("Please report this at https://github.com/Jikoo/OpenInv/issues");
logger.warning("Attempting to fall through using reflection. Please verify that SilentContainer does not fail.");
// N.B. gameModeForPlayer is (for now) declared before previousGameModeForPlayer so silent shouldn't break.
this.serverPlayerGameModeGameType = ReflectionHelper.grabFieldByType(ServerPlayerGameMode.class, GameType.class);
}
} catch (SecurityException e) {
Logger logger = OpenInv.getPlugin(OpenInv.class).getLogger();
logger.warning("Unable to directly write player game mode! SilentContainer will fail.");
logger.log(Level.WARNING, "Error obtaining GameType field", e);
}
}
@Override
public boolean isShulkerBlocked(@NotNull ShulkerBox shulkerBox) {
org.bukkit.World bukkitWorld = shulkerBox.getWorld();
if (!(bukkitWorld instanceof CraftWorld)) {
bukkitWorld = Bukkit.getWorld(bukkitWorld.getUID());
}
if (!(bukkitWorld instanceof CraftWorld craftWorld)) {
Exception exception = new IllegalStateException("AnySilentContainer access attempted on an unknown world!");
OpenInv.getPlugin(OpenInv.class).getLogger().log(Level.WARNING, exception.getMessage(), exception);
return false;
}
final ServerLevel world = craftWorld.getHandle();
final BlockPos blockPosition = new BlockPos(shulkerBox.getX(), shulkerBox.getY(), shulkerBox.getZ());
final BlockEntity tile = world.getBlockEntity(blockPosition);
if (!(tile instanceof ShulkerBoxBlockEntity shulkerBoxBlockEntity)
|| shulkerBoxBlockEntity.getAnimationStatus() != ShulkerBoxBlockEntity.AnimationStatus.CLOSED) {
return false;
}
BlockState blockState = world.getBlockState(blockPosition);
// See net.minecraft.world.level.block.ShulkerBoxBlock#canOpen
AABB boundingBox = Shulker.getProgressDeltaAabb(blockState.getValue(ShulkerBoxBlock.FACING), 0.0F, 0.5F)
.move(blockPosition)
.deflate(1.0E-6D);
return !world.noCollision(boundingBox);
}
@Override
public boolean activateContainer(
@NotNull final Player bukkitPlayer,
final boolean silentchest,
@NotNull final org.bukkit.block.Block bukkitBlock) {
// Silent ender chest is API-only
if (silentchest && bukkitBlock.getType() == Material.ENDER_CHEST) {
bukkitPlayer.openInventory(bukkitPlayer.getEnderChest());
bukkitPlayer.incrementStatistic(Statistic.ENDERCHEST_OPENED);
return true;
}
ServerPlayer player = PlayerDataManager.getHandle(bukkitPlayer);
final ServerLevel level = player.getLevel();
final BlockPos blockPos = new BlockPos(bukkitBlock.getX(), bukkitBlock.getY(), bukkitBlock.getZ());
final BlockEntity blockEntity = level.getBlockEntity(blockPos);
if (blockEntity == null) {
return false;
}
if (blockEntity instanceof EnderChestBlockEntity enderChestTile) {
// Anychest ender chest. See net.minecraft.world.level.block.EnderChestBlock
PlayerEnderChestContainer enderChest = player.getEnderChestInventory();
enderChest.setActiveChest(enderChestTile);
player.openMenu(new SimpleMenuProvider((containerCounter, playerInventory, ignored) -> {
MenuType<?> containers = PlayerDataManager.getContainers(enderChest.getContainerSize());
int rows = enderChest.getContainerSize() / 9;
return new ChestMenu(containers, containerCounter, playerInventory, enderChest, rows);
}, Component.translatable(("container.enderchest"))));
bukkitPlayer.incrementStatistic(Statistic.ENDERCHEST_OPENED);
return true;
}
if (!(blockEntity instanceof MenuProvider menuProvider)) {
return false;
}
BlockState blockState = level.getBlockState(blockPos);
Block block = blockState.getBlock();
if (block instanceof ChestBlock chestBlock) {
// boolean flag: do not check if chest is blocked
Optional<MenuProvider> menuOptional = chestBlock.combine(blockState, level, blockPos, true).apply(
// Combiner is a copy of private ChestBlock.MENU_PROVIDER_COMBINER
new DoubleBlockCombiner.Combiner<ChestBlockEntity, Optional<MenuProvider>>() {
@Override
public Optional<MenuProvider> acceptDouble(ChestBlockEntity localChest1, ChestBlockEntity localChest2) {
CompoundContainer doubleChest = new CompoundContainer(localChest1, localChest2);
return Optional.of(new ChestBlock.DoubleInventory(localChest1, localChest2, doubleChest));
}
@Override
public Optional<MenuProvider> acceptSingle(ChestBlockEntity localChest) {
return Optional.of(localChest);
}
@Override
public Optional<MenuProvider> acceptNone() {
return Optional.empty();
}
});
if (menuOptional.isEmpty()) {
OpenInv.getPlugin(OpenInv.class).sendSystemMessage(bukkitPlayer, "messages.error.lootNotGenerated");
return false;
}
menuProvider = menuOptional.get();
if (block instanceof TrappedChestBlock) {
bukkitPlayer.incrementStatistic(Statistic.TRAPPED_CHEST_TRIGGERED);
} else {
bukkitPlayer.incrementStatistic(Statistic.CHEST_OPENED);
}
}
if (block instanceof ShulkerBoxBlock) {
bukkitPlayer.incrementStatistic(Statistic.SHULKER_BOX_OPENED);
}
if (block instanceof BarrelBlock) {
bukkitPlayer.incrementStatistic(Statistic.OPEN_BARREL);
}
// AnyChest only - SilentChest not active, container unsupported, or unnecessary.
if (!silentchest || player.gameMode.getGameModeForPlayer() == GameType.SPECTATOR) {
player.openMenu(menuProvider);
return true;
}
// SilentChest requires access to setting players' game mode directly.
if (this.serverPlayerGameModeGameType == null) {
return false;
}
if (blockEntity instanceof RandomizableContainerBlockEntity lootable) {
if (lootable.lootTable != null) {
OpenInv.getPlugin(OpenInv.class).sendSystemMessage(bukkitPlayer, "messages.error.lootNotGenerated");
return false;
}
}
GameType gameType = player.gameMode.getGameModeForPlayer();
this.forceGameType(player, GameType.SPECTATOR);
player.openMenu(menuProvider);
this.forceGameType(player, gameType);
return true;
}
@Override
public void deactivateContainer(@NotNull final Player bukkitPlayer) {
if (this.serverPlayerGameModeGameType == null) {
return;
}
InventoryView view = bukkitPlayer.getOpenInventory();
switch (view.getType()) {
case CHEST:
case ENDER_CHEST:
case SHULKER_BOX:
case BARREL:
break;
default:
return;
}
ServerPlayer player = PlayerDataManager.getHandle(bukkitPlayer);
// Force game mode change without informing plugins or players.
// Regular game mode set calls GameModeChangeEvent and is cancellable.
GameType gameType = player.gameMode.getGameModeForPlayer();
this.forceGameType(player, GameType.SPECTATOR);
// ServerPlayer#closeContainer cannot be called without entering an
// infinite loop because this method is called during inventory close.
// From ServerPlayer#closeContainer -> CraftEventFactory#handleInventoryCloseEvent
player.containerMenu.transferTo(player.inventoryMenu, player.getBukkitEntity());
// From ServerPlayer#closeContainer
player.doCloseContainer();
// Regular inventory close will handle the rest - packet sending, etc.
// Revert forced game mode.
this.forceGameType(player, gameType);
}
private void forceGameType(final ServerPlayer player, final GameType gameMode) {
if (this.serverPlayerGameModeGameType == null) {
// No need to warn repeatedly, error on startup and lack of function should be enough.
return;
}
try {
this.serverPlayerGameModeGameType.setAccessible(true);
this.serverPlayerGameModeGameType.set(player.gameMode, gameMode);
} catch (IllegalArgumentException | IllegalAccessException e) {
e.printStackTrace();
}
}
}
@@ -0,0 +1,74 @@
/*
* 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.internal.v1_19_R1;
import java.io.File;
import net.minecraft.Util;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtIo;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.level.storage.PlayerDataStorage;
import org.apache.logging.log4j.LogManager;
import org.bukkit.craftbukkit.v1_19_R1.CraftServer;
import org.bukkit.craftbukkit.v1_19_R1.entity.CraftPlayer;
public class OpenPlayer extends CraftPlayer {
public OpenPlayer(CraftServer server, ServerPlayer entity) {
super(server, entity);
}
@Override
public void loadData() {
// See CraftPlayer#loadData
CompoundTag loaded = this.server.getHandle().playerIo.load(this.getHandle());
if (loaded != null) {
readExtraData(loaded);
}
}
@Override
public void saveData() {
ServerPlayer player = this.getHandle();
// See net.minecraft.world.level.storage.PlayerDataStorage#save(EntityHuman)
try {
PlayerDataStorage worldNBTStorage = player.server.getPlayerList().playerIo;
CompoundTag playerData = player.saveWithoutId(new CompoundTag());
setExtraData(playerData);
if (!isOnline()) {
// Special case: save old vehicle data
CompoundTag oldData = worldNBTStorage.load(player);
if (oldData != null && oldData.contains("RootVehicle", 10)) {
// See net.minecraft.server.PlayerList#a(NetworkManager, EntityPlayer) and net.minecraft.server.EntityPlayer#b(NBTTagCompound)
playerData.put("RootVehicle", oldData.getCompound("RootVehicle"));
}
}
File file = File.createTempFile(player.getStringUUID() + "-", ".dat", worldNBTStorage.getPlayerDir());
NbtIo.writeCompressed(playerData, file);
File file1 = new File(worldNBTStorage.getPlayerDir(), player.getStringUUID() + ".dat");
File file2 = new File(worldNBTStorage.getPlayerDir(), player.getStringUUID() + ".dat_old");
Util.safeReplaceFile(file1, file, file2);
} catch (Exception e) {
LogManager.getLogger().warn("Failed to save player data for {}: {}", player.getName().getString(), e.getMessage());
}
}
}
@@ -0,0 +1,234 @@
/*
* 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.internal.v1_19_R1;
import com.lishid.openinv.OpenInv;
import com.lishid.openinv.internal.IPlayerDataManager;
import com.lishid.openinv.internal.ISpecialInventory;
import com.lishid.openinv.internal.OpenInventoryView;
import com.mojang.authlib.GameProfile;
import java.lang.reflect.Field;
import java.util.logging.Logger;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.game.ClientboundOpenScreenPacket;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.inventory.MenuType;
import net.minecraft.world.level.Level;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.Server;
import org.bukkit.craftbukkit.v1_19_R1.CraftServer;
import org.bukkit.craftbukkit.v1_19_R1.entity.CraftPlayer;
import org.bukkit.craftbukkit.v1_19_R1.event.CraftEventFactory;
import org.bukkit.craftbukkit.v1_19_R1.inventory.CraftContainer;
import org.bukkit.entity.Player;
import org.bukkit.inventory.InventoryView;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class PlayerDataManager implements IPlayerDataManager {
private @Nullable Field bukkitEntity;
public PlayerDataManager() {
try {
bukkitEntity = Entity.class.getDeclaredField("bukkitEntity");
} catch (NoSuchFieldException e) {
Logger logger = OpenInv.getPlugin(OpenInv.class).getLogger();
logger.warning("Unable to obtain field to inject custom save process - players' mounts may be deleted when loaded.");
logger.log(java.util.logging.Level.WARNING, e.getMessage(), e);
bukkitEntity = null;
}
}
public static @NotNull ServerPlayer getHandle(final Player player) {
if (player instanceof CraftPlayer) {
return ((CraftPlayer) player).getHandle();
}
Server server = player.getServer();
ServerPlayer nmsPlayer = null;
if (server instanceof CraftServer) {
nmsPlayer = ((CraftServer) server).getHandle().getPlayer(player.getUniqueId());
}
if (nmsPlayer == null) {
// Could use reflection to examine fields, but it's honestly not worth the bother.
throw new RuntimeException("Unable to fetch EntityPlayer from provided Player implementation");
}
return nmsPlayer;
}
@Override
public @Nullable Player loadPlayer(@NotNull final OfflinePlayer offline) {
// Ensure player has data
if (!offline.hasPlayedBefore()) {
return null;
}
// Create a profile and entity to load the player data
// See net.minecraft.server.players.PlayerList#canPlayerLogin
// and net.minecraft.server.network.ServerLoginPacketListenerImpl#handleHello
GameProfile profile = new GameProfile(offline.getUniqueId(),
offline.getName() != null ? offline.getName() : offline.getUniqueId().toString());
MinecraftServer server = ((CraftServer) Bukkit.getServer()).getServer();
ServerLevel worldServer = server.getLevel(Level.OVERWORLD);
if (worldServer == null) {
return null;
}
ServerPlayer entity = new ServerPlayer(server, worldServer, profile, null);
try {
injectPlayer(entity);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
// Get the bukkit entity
Player target = entity.getBukkitEntity();
if (target != null) {
// Load data
target.loadData();
}
// Return the entity
return target;
}
void injectPlayer(ServerPlayer player) throws IllegalAccessException {
if (bukkitEntity == null) {
return;
}
bukkitEntity.setAccessible(true);
bukkitEntity.set(player, new OpenPlayer(player.server.server, player));
}
@NotNull
@Override
public Player inject(@NotNull Player player) {
try {
ServerPlayer nmsPlayer = getHandle(player);
if (nmsPlayer.getBukkitEntity() instanceof OpenPlayer openPlayer) {
return openPlayer;
}
injectPlayer(nmsPlayer);
return nmsPlayer.getBukkitEntity();
} catch (IllegalAccessException e) {
e.printStackTrace();
return player;
}
}
@Nullable
@Override
public InventoryView openInventory(@NotNull Player player, @NotNull ISpecialInventory inventory) {
ServerPlayer nmsPlayer = getHandle(player);
if (nmsPlayer.connection == null) {
return null;
}
InventoryView view = getView(player, inventory);
if (view == null) {
return player.openInventory(inventory.getBukkitInventory());
}
AbstractContainerMenu container = new CraftContainer(view, nmsPlayer, nmsPlayer.nextContainerCounter()) {
@Override
public MenuType<?> getType() {
return getContainers(inventory.getBukkitInventory().getSize());
}
};
container.setTitle(Component.literal(view.getTitle()));
container = CraftEventFactory.callInventoryOpenEvent(nmsPlayer, container);
if (container == null) {
return null;
}
nmsPlayer.connection.send(new ClientboundOpenScreenPacket(container.containerId, container.getType(),
Component.literal(container.getBukkitView().getTitle())));
nmsPlayer.containerMenu = container;
nmsPlayer.initMenu(container);
return container.getBukkitView();
}
private @Nullable InventoryView getView(Player player, ISpecialInventory inventory) {
if (inventory instanceof SpecialEnderChest) {
return new OpenInventoryView(player, inventory, "container.enderchest", "'s Ender Chest");
} else if (inventory instanceof SpecialPlayerInventory) {
return new OpenInventoryView(player, inventory, "container.player", "'s Inventory");
} else {
return null;
}
}
static @NotNull MenuType<?> getContainers(int inventorySize) {
return switch (inventorySize) {
case 9 -> MenuType.GENERIC_9x1;
case 18 -> MenuType.GENERIC_9x2;
case 36 -> MenuType.GENERIC_9x4; // PLAYER
case 41, 45 -> MenuType.GENERIC_9x5;
case 54 -> MenuType.GENERIC_9x6;
default -> MenuType.GENERIC_9x3; // Default 27-slot inventory
};
}
@Override
public int convertToPlayerSlot(InventoryView view, int rawSlot) {
int topSize = view.getTopInventory().getSize();
if (topSize <= rawSlot) {
// Slot is not inside special inventory, use Bukkit logic.
return view.convertSlot(rawSlot);
}
// Main inventory, slots 0-26 -> 9-35
if (rawSlot < 27) {
return rawSlot + 9;
}
// Hotbar, slots 27-35 -> 0-8
if (rawSlot < 36) {
return rawSlot - 27;
}
// Armor, slots 36-39 -> 39-36
if (rawSlot < 40) {
return 36 + (39 - rawSlot);
}
// Off hand
if (rawSlot == 40) {
return 40;
}
// Drop slots, "out of inventory"
return -1;
}
}
@@ -0,0 +1,339 @@
/*
* 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.internal.v1_19_R1;
import com.lishid.openinv.internal.ISpecialEnderChest;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import net.minecraft.core.NonNullList;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.ContainerHelper;
import net.minecraft.world.ContainerListener;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.entity.player.StackedContents;
import net.minecraft.world.inventory.PlayerEnderChestContainer;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.entity.EnderChestBlockEntity;
import org.bukkit.Location;
import org.bukkit.craftbukkit.v1_19_R1.entity.CraftHumanEntity;
import org.bukkit.craftbukkit.v1_19_R1.inventory.CraftInventory;
import org.bukkit.entity.HumanEntity;
import org.bukkit.inventory.InventoryHolder;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class SpecialEnderChest extends PlayerEnderChestContainer implements ISpecialEnderChest {
private final CraftInventory inventory;
private ServerPlayer owner;
private NonNullList<ItemStack> items;
private boolean playerOnline;
public SpecialEnderChest(final org.bukkit.entity.Player player, final Boolean online) {
super(PlayerDataManager.getHandle(player));
this.inventory = new CraftInventory(this);
this.owner = PlayerDataManager.getHandle(player);
this.playerOnline = online;
this.items = this.owner.getEnderChestInventory().items;
}
@Override
public @NotNull CraftInventory getBukkitInventory() {
return inventory;
}
@Override
public void setPlayerOffline() {
this.playerOnline = false;
}
@Override
public void setPlayerOnline(@NotNull final org.bukkit.entity.Player player) {
if (!this.playerOnline) {
try {
this.owner = PlayerDataManager.getHandle(player);
PlayerEnderChestContainer enderChest = owner.getEnderChestInventory();
for (int i = 0; i < enderChest.getContainerSize(); ++i) {
enderChest.setItem(i, this.items.get(i));
}
this.items = enderChest.items;
enderChest.transaction.addAll(this.transaction);
} catch (Exception ignored) {}
this.playerOnline = true;
}
}
@Override
public @NotNull org.bukkit.entity.Player getPlayer() {
return owner.getBukkitEntity();
}
@Override
public void setChanged() {
this.owner.getEnderChestInventory().setChanged();
}
@Override
public List<ItemStack> getContents() {
return this.items;
}
@Override
public void onOpen(CraftHumanEntity who) {
this.owner.getEnderChestInventory().onOpen(who);
}
@Override
public void onClose(CraftHumanEntity who) {
this.owner.getEnderChestInventory().onClose(who);
}
@Override
public List<HumanEntity> getViewers() {
return this.owner.getEnderChestInventory().getViewers();
}
@Override
public boolean stillValid(Player player) {
return true;
}
@Override
public void setActiveChest(EnderChestBlockEntity enderChest) {
this.owner.getEnderChestInventory().setActiveChest(enderChest);
}
@Override
public boolean isActiveChest(EnderChestBlockEntity enderChest) {
return this.owner.getEnderChestInventory().isActiveChest(enderChest);
}
@Override
public int getMaxStackSize() {
return this.owner.getEnderChestInventory().getMaxStackSize();
}
@Override
public void setMaxStackSize(int i) {
this.owner.getEnderChestInventory().setMaxStackSize(i);
}
@Override
public InventoryHolder getOwner() {
return this.owner.getEnderChestInventory().getOwner();
}
@Override
public @Nullable Location getLocation() {
return null;
}
@Override
public void addListener(ContainerListener listener) {
this.owner.getEnderChestInventory().addListener(listener);
}
@Override
public void removeListener(ContainerListener listener) {
this.owner.getEnderChestInventory().removeListener(listener);
}
@Override
public ItemStack getItem(int i) {
return i >= 0 && i < this.items.size() ? this.items.get(i) : ItemStack.EMPTY;
}
@Override
public ItemStack removeItem(int i, int j) {
ItemStack itemstack = ContainerHelper.removeItem(this.items, i, j);
if (!itemstack.isEmpty()) {
this.setChanged();
}
return itemstack;
}
@Override
public ItemStack addItem(ItemStack itemstack) {
ItemStack localItem = itemstack.copy();
this.moveItemToOccupiedSlotsWithSameType(localItem);
if (localItem.isEmpty()) {
return ItemStack.EMPTY;
} else {
this.moveItemToEmptySlots(localItem);
return localItem.isEmpty() ? ItemStack.EMPTY : localItem;
}
}
@Override
public boolean canAddItem(ItemStack itemstack) {
for (ItemStack itemstack1 : this.items) {
if (itemstack1.isEmpty() || ItemStack.isSameItemSameTags(itemstack1, itemstack) && itemstack1.getCount() < itemstack1.getMaxStackSize()) {
return true;
}
}
return false;
}
private void moveItemToEmptySlots(ItemStack itemstack) {
for(int i = 0; i < this.getContainerSize(); ++i) {
ItemStack localItem = this.getItem(i);
if (localItem.isEmpty()) {
this.setItem(i, itemstack.copy());
itemstack.setCount(0);
return;
}
}
}
private void moveItemToOccupiedSlotsWithSameType(ItemStack itemstack) {
for(int i = 0; i < this.getContainerSize(); ++i) {
ItemStack localItem = this.getItem(i);
if (ItemStack.isSameItemSameTags(localItem, itemstack)) {
this.moveItemsBetweenStacks(itemstack, localItem);
if (itemstack.isEmpty()) {
return;
}
}
}
}
private void moveItemsBetweenStacks(ItemStack itemstack, ItemStack itemstack1) {
int i = Math.min(this.getMaxStackSize(), itemstack1.getMaxStackSize());
int j = Math.min(itemstack.getCount(), i - itemstack1.getCount());
if (j > 0) {
itemstack1.grow(j);
itemstack.shrink(j);
this.setChanged();
}
}
@Override
public ItemStack removeItemNoUpdate(int i) {
ItemStack itemstack = this.items.get(i);
if (itemstack.isEmpty()) {
return ItemStack.EMPTY;
} else {
this.items.set(i, ItemStack.EMPTY);
return itemstack;
}
}
@Override
public void setItem(int i, ItemStack itemstack) {
this.items.set(i, itemstack);
if (!itemstack.isEmpty() && itemstack.getCount() > this.getMaxStackSize()) {
itemstack.setCount(this.getMaxStackSize());
}
this.setChanged();
}
@Override
public int getContainerSize() {
return this.owner.getEnderChestInventory().getContainerSize();
}
@Override
public boolean isEmpty() {
return this.items.stream().allMatch(ItemStack::isEmpty);
}
@Override
public void startOpen(Player player) {
}
@Override
public void stopOpen(Player player) {
}
@Override
public boolean canPlaceItem(int i, ItemStack itemstack) {
return true;
}
@Override
public void clearContent() {
this.items.clear();
this.setChanged();
}
@Override
public void fillStackedContents(StackedContents stackedContents) {
for (ItemStack itemstack : this.items) {
stackedContents.accountStack(itemstack);
}
}
@Override
public List<ItemStack> removeAllItems() {
List<ItemStack> list = this.items.stream().filter(Predicate.not(ItemStack::isEmpty)).collect(Collectors.toList());
this.clearContent();
return list;
}
@Override
public ItemStack removeItemType(Item item, int i) {
ItemStack itemstack = new ItemStack(item, 0);
for(int j = this.getContainerSize() - 1; j >= 0; --j) {
ItemStack localItem = this.getItem(j);
if (localItem.getItem().equals(item)) {
int k = i - itemstack.getCount();
ItemStack splitItem = localItem.split(k);
itemstack.grow(splitItem.getCount());
if (itemstack.getCount() == i) {
break;
}
}
}
if (!itemstack.isEmpty()) {
this.setChanged();
}
return itemstack;
}
@Override
public String toString() {
return this.items.stream().filter((itemStack) -> !itemStack.isEmpty()).toList().toString();
}
@Override
public void fromTag(ListTag listTag) {
for (int i = 0; i < this.getContainerSize(); ++i) {
this.setItem(i, ItemStack.EMPTY);
}
for (int i = 0; i < listTag.size(); ++i) {
CompoundTag compoundTag = listTag.getCompound(i);
int j = compoundTag.getByte("Slot") & 255;
if (j < this.getContainerSize()) {
this.setItem(j, ItemStack.of(compoundTag));
}
}
}
}
@@ -0,0 +1,788 @@
/*
* 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.internal.v1_19_R1;
import com.google.common.collect.ImmutableList;
import com.lishid.openinv.internal.ISpecialPlayerInventory;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import net.minecraft.CrashReport;
import net.minecraft.CrashReportCategory;
import net.minecraft.ReportedException;
import net.minecraft.core.NonNullList;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.game.ClientboundContainerSetSlotPacket;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.tags.TagKey;
import net.minecraft.world.Container;
import net.minecraft.world.ContainerHelper;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.entity.player.StackedContents;
import net.minecraft.world.item.ArmorItem;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.state.BlockState;
import org.bukkit.Location;
import org.bukkit.craftbukkit.v1_19_R1.entity.CraftHumanEntity;
import org.bukkit.craftbukkit.v1_19_R1.inventory.CraftInventory;
import org.bukkit.entity.HumanEntity;
import org.bukkit.inventory.InventoryHolder;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class SpecialPlayerInventory extends Inventory implements ISpecialPlayerInventory {
private final CraftInventory inventory;
private boolean playerOnline;
private Player player;
private NonNullList<ItemStack> items;
private NonNullList<ItemStack> armor;
private NonNullList<ItemStack> offhand;
private List<NonNullList<ItemStack>> compartments;
public SpecialPlayerInventory(@NotNull org.bukkit.entity.Player bukkitPlayer, @NotNull Boolean online) {
super(PlayerDataManager.getHandle(bukkitPlayer));
this.inventory = new CraftInventory(this);
this.playerOnline = online;
this.player = super.player;
this.selected = player.getInventory().selected;
this.items = this.player.getInventory().items;
this.armor = this.player.getInventory().armor;
this.offhand = this.player.getInventory().offhand;
this.compartments = ImmutableList.of(this.items, this.armor, this.offhand);
}
@Override
public void setPlayerOnline(@NotNull org.bukkit.entity.Player player) {
if (!this.playerOnline) {
Player entityPlayer = PlayerDataManager.getHandle(player);
entityPlayer.getInventory().transaction.addAll(this.transaction);
this.player = entityPlayer;
for (int i = 0; i < getContainerSize(); ++i) {
this.player.getInventory().setItem(i, getRawItem(i));
}
this.player.getInventory().selected = this.selected;
this.items = this.player.getInventory().items;
this.armor = this.player.getInventory().armor;
this.offhand = this.player.getInventory().offhand;
this.compartments = ImmutableList.of(this.items, this.armor, this.offhand);
this.playerOnline = true;
}
}
@Override
public @NotNull CraftInventory getBukkitInventory() {
return this.inventory;
}
@Override
public void setPlayerOffline() {
this.playerOnline = false;
}
@Override
public @NotNull HumanEntity getPlayer() {
return this.player.getBukkitEntity();
}
private @NotNull ItemStack getRawItem(int i) {
if (i < 0) {
return ItemStack.EMPTY;
}
NonNullList<ItemStack> list;
for (Iterator<NonNullList<ItemStack>> iterator = this.compartments.iterator(); iterator.hasNext(); i -= list.size()) {
list = iterator.next();
if (i < list.size()) {
return list.get(i);
}
}
return ItemStack.EMPTY;
}
private void setRawItem(int i, @NotNull ItemStack itemStack) {
if (i < 0) {
return;
}
NonNullList<ItemStack> list;
for (Iterator<NonNullList<ItemStack>> iterator = this.compartments.iterator(); iterator.hasNext(); i -= list.size()) {
list = iterator.next();
if (i < list.size()) {
list.set(i, itemStack);
}
}
}
private record IndexedCompartment(@Nullable NonNullList<ItemStack> compartment, int index) {}
private @NotNull SpecialPlayerInventory.IndexedCompartment getIndexedContent(int index) {
if (index < items.size()) {
return new IndexedCompartment(items, getReversedItemSlotNum(index));
}
index -= items.size();
if (index < armor.size()) {
return new IndexedCompartment(armor, getReversedArmorSlotNum(index));
}
index -= armor.size();
if (index < offhand.size()) {
return new IndexedCompartment(offhand, index);
}
index -= offhand.size();
return new IndexedCompartment(null, index);
}
private int getReversedArmorSlotNum(final int i) {
if (i == 0) {
return 3;
}
if (i == 1) {
return 2;
}
if (i == 2) {
return 1;
}
if (i == 3) {
return 0;
}
return i;
}
private int getReversedItemSlotNum(final int i) {
if (i >= 27) {
return i - 27;
}
return i + 9;
}
private boolean contains(Predicate<ItemStack> predicate) {
return this.compartments.stream().flatMap(NonNullList::stream).anyMatch(predicate);
}
@Override
public List<ItemStack> getArmorContents() {
return this.armor;
}
@Override
public void onOpen(CraftHumanEntity who) {
this.player.getInventory().onOpen(who);
}
@Override
public void onClose(CraftHumanEntity who) {
this.player.getInventory().onClose(who);
}
@Override
public List<HumanEntity> getViewers() {
return this.player.getInventory().getViewers();
}
@Override
public InventoryHolder getOwner() {
return this.player.getBukkitEntity();
}
@Override
public int getMaxStackSize() {
return this.player.getInventory().getMaxStackSize();
}
@Override
public void setMaxStackSize(int size) {
this.player.getInventory().setMaxStackSize(size);
}
@Override
public Location getLocation() {
return this.player.getBukkitEntity().getLocation();
}
@Override
public boolean hasCustomName() {
return false;
}
@Override
public List<ItemStack> getContents() {
return this.compartments.stream().flatMap(Collection::stream).collect(Collectors.toList());
}
@Override
public ItemStack getSelected() {
return isHotbarSlot(this.selected) ? this.items.get(this.selected) : ItemStack.EMPTY;
}
private boolean hasRemainingSpaceForItem(ItemStack itemstack, ItemStack itemstack1) {
return !itemstack.isEmpty() && ItemStack.isSameItemSameTags(itemstack, itemstack1) && itemstack.isStackable() && itemstack.getCount() < itemstack.getMaxStackSize() && itemstack.getCount() < this.getMaxStackSize();
}
@Override
public int canHold(ItemStack itemstack) {
int remains = itemstack.getCount();
for (int i = 0; i < this.items.size(); ++i) {
ItemStack itemstack1 = this.getRawItem(i);
if (itemstack1.isEmpty()) {
return itemstack.getCount();
}
if (this.hasRemainingSpaceForItem(itemstack1, itemstack)) {
remains -= (itemstack1.getMaxStackSize() < this.getMaxStackSize() ? itemstack1.getMaxStackSize() : this.getMaxStackSize()) - itemstack1.getCount();
}
if (remains <= 0) {
return itemstack.getCount();
}
}
ItemStack offhandItemStack = this.getRawItem(this.items.size() + this.armor.size());
if (this.hasRemainingSpaceForItem(offhandItemStack, itemstack)) {
remains -= (offhandItemStack.getMaxStackSize() < this.getMaxStackSize() ? offhandItemStack.getMaxStackSize() : this.getMaxStackSize()) - offhandItemStack.getCount();
}
return remains <= 0 ? itemstack.getCount() : itemstack.getCount() - remains;
}
@Override
public int getFreeSlot() {
for(int i = 0; i < this.items.size(); ++i) {
if (this.items.get(i).isEmpty()) {
return i;
}
}
return -1;
}
@Override
public void setPickedItem(ItemStack itemstack) {
int i = this.findSlotMatchingItem(itemstack);
if (isHotbarSlot(i)) {
this.selected = i;
} else if (i == -1) {
this.selected = this.getSuitableHotbarSlot();
if (!this.items.get(this.selected).isEmpty()) {
int j = this.getFreeSlot();
if (j != -1) {
this.items.set(j, this.items.get(this.selected));
}
}
this.items.set(this.selected, itemstack);
} else {
this.pickSlot(i);
}
}
@Override
public void pickSlot(int i) {
this.selected = this.getSuitableHotbarSlot();
ItemStack itemstack = this.items.get(this.selected);
this.items.set(this.selected, this.items.get(i));
this.items.set(i, itemstack);
}
@Override
public int findSlotMatchingItem(ItemStack itemstack) {
for(int i = 0; i < this.items.size(); ++i) {
if (!this.items.get(i).isEmpty() && ItemStack.isSameItemSameTags(itemstack, this.items.get(i))) {
return i;
}
}
return -1;
}
@Override
public int findSlotMatchingUnusedItem(ItemStack itemStack) {
for(int i = 0; i < this.items.size(); ++i) {
ItemStack localItem = this.items.get(i);
if (!this.items.get(i).isEmpty() && ItemStack.isSameItemSameTags(itemStack, this.items.get(i)) && !this.items.get(i).isDamaged() && !localItem.isEnchanted() && !localItem.hasCustomHoverName()) {
return i;
}
}
return -1;
}
@Override
public int getSuitableHotbarSlot() {
int i;
int j;
for(j = 0; j < 9; ++j) {
i = (this.selected + j) % 9;
if (this.items.get(i).isEmpty()) {
return i;
}
}
for(j = 0; j < 9; ++j) {
i = (this.selected + j) % 9;
if (!this.items.get(i).isEnchanted()) {
return i;
}
}
return this.selected;
}
@Override
public void swapPaint(double d0) {
if (d0 > 0.0D) {
d0 = 1.0D;
}
if (d0 < 0.0D) {
d0 = -1.0D;
}
this.selected = (int) (this.selected - d0);
while (this.selected < 0) {
this.selected += 9;
}
while(this.selected >= 9) {
this.selected -= 9;
}
}
@Override
public int clearOrCountMatchingItems(Predicate<ItemStack> predicate, int i, Container container) {
byte b0 = 0;
boolean flag = i == 0;
int j = b0 + ContainerHelper.clearOrCountMatchingItems(this, predicate, i - b0, flag);
j += ContainerHelper.clearOrCountMatchingItems(container, predicate, i - j, flag);
ItemStack itemstack = this.player.containerMenu.getCarried();
j += ContainerHelper.clearOrCountMatchingItems(itemstack, predicate, i - j, flag);
if (itemstack.isEmpty()) {
this.player.containerMenu.setCarried(ItemStack.EMPTY);
}
return j;
}
private int addResource(ItemStack itemstack) {
int i = this.getSlotWithRemainingSpace(itemstack);
if (i == -1) {
i = this.getFreeSlot();
}
return i == -1 ? itemstack.getCount() : this.addResource(i, itemstack);
}
private int addResource(int i, ItemStack itemstack) {
Item item = itemstack.getItem();
int j = itemstack.getCount();
ItemStack localItemStack = this.getRawItem(i);
if (localItemStack.isEmpty()) {
localItemStack = new ItemStack(item, 0);
if (itemstack.hasTag()) {
// hasTag ensures tag not null
//noinspection ConstantConditions
localItemStack.setTag(itemstack.getTag().copy());
}
this.setRawItem(i, localItemStack);
}
int k = Math.min(j, localItemStack.getMaxStackSize() - localItemStack.getCount());
if (k > this.getMaxStackSize() - localItemStack.getCount()) {
k = this.getMaxStackSize() - localItemStack.getCount();
}
if (k != 0) {
j -= k;
localItemStack.grow(k);
localItemStack.setPopTime(5);
}
return j;
}
@Override
public int getSlotWithRemainingSpace(ItemStack itemstack) {
if (this.hasRemainingSpaceForItem(this.getRawItem(this.selected), itemstack)) {
return this.selected;
} else if (this.hasRemainingSpaceForItem(this.getRawItem(40), itemstack)) {
return 40;
} else {
for(int i = 0; i < this.items.size(); ++i) {
if (this.hasRemainingSpaceForItem(this.items.get(i), itemstack)) {
return i;
}
}
return -1;
}
}
@Override
public void tick() {
for (NonNullList<ItemStack> compartment : this.compartments) {
for (int i = 0; i < compartment.size(); ++i) {
if (!compartment.get(i).isEmpty()) {
compartment.get(i).inventoryTick(this.player.level, this.player, i, this.selected == i);
}
}
}
}
@Override
public boolean add(ItemStack itemStack) {
return this.add(-1, itemStack);
}
@Override
public boolean add(int i, ItemStack itemStack) {
if (itemStack.isEmpty()) {
return false;
} else {
try {
if (itemStack.isDamaged()) {
if (i == -1) {
i = this.getFreeSlot();
}
if (i >= 0) {
this.items.set(i, itemStack.copy());
this.items.get(i).setPopTime(5);
itemStack.setCount(0);
return true;
} else if (this.player.getAbilities().instabuild) {
itemStack.setCount(0);
return true;
} else {
return false;
}
} else {
int j;
do {
j = itemStack.getCount();
if (i == -1) {
itemStack.setCount(this.addResource(itemStack));
} else {
itemStack.setCount(this.addResource(i, itemStack));
}
} while(!itemStack.isEmpty() && itemStack.getCount() < j);
if (itemStack.getCount() == j && this.player.getAbilities().instabuild) {
itemStack.setCount(0);
return true;
} else {
return itemStack.getCount() < j;
}
}
} catch (Throwable var6) {
CrashReport crashReport = CrashReport.forThrowable(var6, "Adding item to inventory");
CrashReportCategory crashReportCategory = crashReport.addCategory("Item being added");
crashReportCategory.setDetail("Item ID", Item.getId(itemStack.getItem()));
crashReportCategory.setDetail("Item data", itemStack.getDamageValue());
crashReportCategory.setDetail("Item name", () -> itemStack.getHoverName().getString());
throw new ReportedException(crashReport);
}
}
}
@Override
public void placeItemBackInInventory(ItemStack itemStack) {
this.placeItemBackInInventory(itemStack, true);
}
@Override
public void placeItemBackInInventory(ItemStack itemStack, boolean flag) {
while(true) {
if (!itemStack.isEmpty()) {
int i = this.getSlotWithRemainingSpace(itemStack);
if (i == -1) {
i = this.getFreeSlot();
}
if (i != -1) {
int j = itemStack.getMaxStackSize() - this.getRawItem(i).getCount();
if (this.add(i, itemStack.split(j)) && flag && this.player instanceof ServerPlayer) {
((ServerPlayer)this.player).connection.send(new ClientboundContainerSetSlotPacket(-2, 0, i, this.getRawItem(i)));
}
continue;
}
this.player.drop(itemStack, false);
}
return;
}
}
@Override
public ItemStack removeItem(int rawIndex, final int j) {
IndexedCompartment indexedCompartment = getIndexedContent(rawIndex);
if (indexedCompartment.compartment() == null
|| indexedCompartment.compartment().get(indexedCompartment.index()).isEmpty()) {
return ItemStack.EMPTY;
}
return ContainerHelper.removeItem(indexedCompartment.compartment(), indexedCompartment.index(), j);
}
@Override
public void removeItem(ItemStack itemStack) {
for (NonNullList<ItemStack> compartment : this.compartments) {
for (int i = 0; i < compartment.size(); ++i) {
if (compartment.get(i) == itemStack) {
compartment.set(i, ItemStack.EMPTY);
break;
}
}
}
}
@Override
public ItemStack removeItemNoUpdate(int rawIndex) {
IndexedCompartment indexedCompartment = getIndexedContent(rawIndex);
if (indexedCompartment.compartment() == null) {
return ItemStack.EMPTY;
}
ItemStack removed = indexedCompartment.compartment().set(indexedCompartment.index(), ItemStack.EMPTY);
if (removed.isEmpty()) {
return ItemStack.EMPTY;
}
return removed;
}
@Override
public void setItem(int rawIndex, final ItemStack itemStack) {
IndexedCompartment indexedCompartment = getIndexedContent(rawIndex);
if (indexedCompartment.compartment() == null) {
this.player.drop(itemStack, true);
return;
}
indexedCompartment.compartment().set(indexedCompartment.index(), itemStack);
}
@Override
public float getDestroySpeed(BlockState blockState) {
return this.items.get(this.selected).getDestroySpeed(blockState);
}
@Override
public ListTag save(ListTag listTag) {
for (int i = 0; i < this.items.size(); ++i) {
if (!this.items.get(i).isEmpty()) {
CompoundTag compoundTag = new CompoundTag();
compoundTag.putByte("Slot", (byte)i);
this.items.get(i).save(compoundTag);
listTag.add(compoundTag);
}
}
for (int i = 0; i < this.armor.size(); ++i) {
if (!this.armor.get(i).isEmpty()) {
CompoundTag compoundTag = new CompoundTag();
compoundTag.putByte("Slot", (byte)(i + 100));
this.armor.get(i).save(compoundTag);
listTag.add(compoundTag);
}
}
for (int i = 0; i < this.offhand.size(); ++i) {
if (!this.offhand.get(i).isEmpty()) {
CompoundTag compoundTag = new CompoundTag();
compoundTag.putByte("Slot", (byte)(i + 150));
this.offhand.get(i).save(compoundTag);
listTag.add(compoundTag);
}
}
return listTag;
}
@Override
public void load(ListTag listTag) {
this.items.clear();
this.armor.clear();
this.offhand.clear();
for(int i = 0; i < listTag.size(); ++i) {
CompoundTag compoundTag = listTag.getCompound(i);
int j = compoundTag.getByte("Slot") & 255;
ItemStack itemstack = ItemStack.of(compoundTag);
if (!itemstack.isEmpty()) {
if (j < this.items.size()) {
this.items.set(j, itemstack);
} else if (j >= 100 && j < this.armor.size() + 100) {
this.armor.set(j - 100, itemstack);
} else if (j >= 150 && j < this.offhand.size() + 150) {
this.offhand.set(j - 150, itemstack);
}
}
}
}
@Override
public int getContainerSize() {
return 45;
}
@Override
public boolean isEmpty() {
return !contains(itemStack -> !itemStack.isEmpty());
}
@Override
public ItemStack getItem(int rawIndex) {
IndexedCompartment indexedCompartment = getIndexedContent(rawIndex);
if (indexedCompartment.compartment() == null) {
return ItemStack.EMPTY;
}
return indexedCompartment.compartment().get(indexedCompartment.index());
}
@Override
public Component getName() {
return this.player.getName();
}
@Override
public ItemStack getArmor(int index) {
return this.armor.get(index);
}
@Override
public void hurtArmor(DamageSource damagesource, float damage, int[] armorIndices) {
if (damage > 0.0F) {
damage /= 4.0F;
if (damage < 1.0F) {
damage = 1.0F;
}
for (int index : armorIndices) {
ItemStack itemstack = this.armor.get(index);
if ((!damagesource.isFire() || !itemstack.getItem().isFireResistant()) && itemstack.getItem() instanceof ArmorItem) {
itemstack.hurtAndBreak((int) damage, this.player, localPlayer -> localPlayer.broadcastBreakEvent(EquipmentSlot.byTypeAndIndex(EquipmentSlot.Type.ARMOR, index)));
}
}
}
}
@Override
public void dropAll() {
for (NonNullList<ItemStack> compartment : this.compartments) {
for (int i = 0; i < compartment.size(); ++i) {
ItemStack itemstack = compartment.get(i);
if (!itemstack.isEmpty()) {
this.player.drop(itemstack, true, false);
compartment.set(i, ItemStack.EMPTY);
}
}
}
}
@Override
public void setChanged() {
super.setChanged();
}
@Override
public int getTimesChanged() {
return super.getTimesChanged();
}
@Override
public boolean stillValid(Player player) {
return true;
}
@Override
public boolean contains(ItemStack itemstack) {
return contains(itemStack -> itemStack.isEmpty() && itemStack.sameItem(itemstack));
}
@Override
public boolean contains(TagKey<Item> tagKey) {
return contains(itemStack -> !itemStack.isEmpty() && itemStack.is(tagKey));
}
@Override
public void replaceWith(Inventory inventory) {
Function<Integer, ItemStack> getter;
if (inventory instanceof SpecialPlayerInventory specialPlayerInventory) {
getter = specialPlayerInventory::getRawItem;
} else {
getter = inventory::getItem;
}
for(int i = 0; i < this.getContainerSize(); ++i) {
this.setRawItem(i, getter.apply(i));
}
this.selected = inventory.selected;
}
@Override
public void clearContent() {
for (NonNullList<ItemStack> compartment : this.compartments) {
compartment.clear();
}
}
@Override
public void fillStackedContents(StackedContents stackedContents) {
for (ItemStack itemstack : this.items) {
stackedContents.accountSimpleStack(itemstack);
}
}
@Override
public ItemStack removeFromSelected(boolean dropWholeStack) {
ItemStack itemstack = this.getSelected();
return itemstack.isEmpty() ? ItemStack.EMPTY : this.removeItem(this.selected, dropWholeStack ? itemstack.getCount() : 1);
}
}
-74
View File
@@ -1,74 +0,0 @@
<!--
~ Copyright (C) 2011-2018 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>
<groupId>com.lishid</groupId>
<artifactId>openinvinternal</artifactId>
<version>4.0.2</version>
</parent>
<artifactId>openinvadapter1_8_R3</artifactId>
<name>OpenInvAdapter1_8_R3</name>
<dependencies>
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot</artifactId>
<version>1.8.8-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.lishid</groupId>
<artifactId>openinvcommon</artifactId>
<version>4.0.2</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.0.0</version>
<configuration>
<minimizeJar>true</minimizeJar>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
@@ -1,248 +0,0 @@
/*
* Copyright (C) 2011-2018 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.v1_8_R3;
import com.lishid.openinv.internal.IAnySilentContainer;
import java.lang.reflect.Field;
import net.minecraft.server.v1_8_R3.AxisAlignedBB;
import net.minecraft.server.v1_8_R3.Block;
import net.minecraft.server.v1_8_R3.BlockChest;
import net.minecraft.server.v1_8_R3.BlockEnderChest;
import net.minecraft.server.v1_8_R3.BlockPosition;
import net.minecraft.server.v1_8_R3.Entity;
import net.minecraft.server.v1_8_R3.EntityOcelot;
import net.minecraft.server.v1_8_R3.EntityPlayer;
import net.minecraft.server.v1_8_R3.EnumDirection;
import net.minecraft.server.v1_8_R3.ITileInventory;
import net.minecraft.server.v1_8_R3.InventoryEnderChest;
import net.minecraft.server.v1_8_R3.InventoryLargeChest;
import net.minecraft.server.v1_8_R3.PlayerInteractManager;
import net.minecraft.server.v1_8_R3.StatisticList;
import net.minecraft.server.v1_8_R3.TileEntity;
import net.minecraft.server.v1_8_R3.TileEntityChest;
import net.minecraft.server.v1_8_R3.TileEntityEnderChest;
import net.minecraft.server.v1_8_R3.World;
import net.minecraft.server.v1_8_R3.WorldSettings.EnumGamemode;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.inventory.InventoryView;
import org.jetbrains.annotations.NotNull;
public class AnySilentContainer implements IAnySilentContainer {
private Field playerInteractManagerGamemode;
public AnySilentContainer() {
try {
this.playerInteractManagerGamemode = PlayerInteractManager.class.getDeclaredField("gamemode");
this.playerInteractManagerGamemode.setAccessible(true);
} catch (Exception e) {
System.err.println("[OpenInv] Unable to directly write player gamemode! SilentChest will fail.");
e.printStackTrace();
}
}
@Override
public boolean isAnySilentContainer(@NotNull org.bukkit.block.Block bukkitBlock) {
return bukkitBlock.getType() == Material.ENDER_CHEST || bukkitBlock.getState() instanceof org.bukkit.block.Chest;
}
@Override
public boolean isAnyContainerNeeded(@NotNull Player bukkitPlayer, @NotNull org.bukkit.block.Block bukkitBlock) {
World world = PlayerDataManager.getHandle(bukkitPlayer).world;
BlockPosition blockPosition = new BlockPosition(bukkitBlock.getX(), bukkitBlock.getY(), bukkitBlock.getZ());
Block block = world.getType(blockPosition).getBlock();
if (block instanceof BlockEnderChest) {
// Ender chests are not blocked by ocelots.
return world.getType(blockPosition.up()).getBlock().c();
}
// Check if chest is blocked or has an ocelot on top
if (isBlockedChest(world, blockPosition)) {
return true;
}
// Check for matching adjacent chests that are blocked or have an ocelot on top
for (EnumDirection localEnumDirection : EnumDirection.EnumDirectionLimit.HORIZONTAL) {
BlockPosition localBlockPosition = blockPosition.shift(localEnumDirection);
Block localBlock = world.getType(localBlockPosition).getBlock();
if (localBlock != block) {
continue;
}
TileEntity localTileEntity = world.getTileEntity(localBlockPosition);
if (!(localTileEntity instanceof TileEntityChest)) {
continue;
}
if (isBlockedChest(world, localBlockPosition)) {
return true;
}
}
return false;
}
private boolean isBlockedChest(World world, BlockPosition blockPosition) {
// For reference, loot at net.minecraft.server.BlockChest
return world.getType(blockPosition.up()).getBlock().c() || hasOcelotOnTop(world, blockPosition);
}
private boolean hasOcelotOnTop(World world, BlockPosition blockPosition) {
for (Entity localEntity : world.a(EntityOcelot.class,
new AxisAlignedBB(blockPosition.getX(), blockPosition.getY() + 1,
blockPosition.getZ(), blockPosition.getX() + 1, blockPosition.getY() + 2,
blockPosition.getZ() + 1))) {
EntityOcelot localEntityOcelot = (EntityOcelot) localEntity;
if (localEntityOcelot.isSitting()) {
return true;
}
}
return false;
}
@Override
public boolean activateContainer(@NotNull Player bukkitPlayer, boolean silent, @NotNull org.bukkit.block.Block bukkitBlock) {
EntityPlayer player = PlayerDataManager.getHandle(bukkitPlayer);
// Silent ender chest is pretty much API-only
if (silent && bukkitBlock.getType() == Material.ENDER_CHEST) {
bukkitPlayer.openInventory(bukkitPlayer.getEnderChest());
player.b(StatisticList.V);
return true;
}
World world = player.world;
BlockPosition blockPosition = new BlockPosition(bukkitBlock.getX(), bukkitBlock.getY(), bukkitBlock.getZ());
Object tile = world.getTileEntity(blockPosition);
if (tile == null) {
return false;
}
if (tile instanceof TileEntityEnderChest) {
// Anychest ender chest. See net.minecraft.server.BlockEnderChest
InventoryEnderChest enderChest = player.getEnderChest();
enderChest.a((TileEntityEnderChest) tile);
player.openContainer(enderChest);
player.b(StatisticList.V);
return true;
}
if (!(tile instanceof ITileInventory)) {
return false;
}
ITileInventory tileInventory = (ITileInventory) tile;
Block block = world.getType(blockPosition).getBlock();
if (block instanceof BlockChest) {
for (EnumDirection localEnumDirection : EnumDirection.EnumDirectionLimit.HORIZONTAL) {
BlockPosition localBlockPosition = blockPosition.shift(localEnumDirection);
Block localBlock = world.getType(localBlockPosition).getBlock();
if (localBlock != block) {
continue;
}
TileEntity localTileEntity = world.getTileEntity(localBlockPosition);
if (!(localTileEntity instanceof TileEntityChest)) {
continue;
}
if ((localEnumDirection == EnumDirection.WEST) || (localEnumDirection == EnumDirection.NORTH)) {
tileInventory = new InventoryLargeChest("container.chestDouble",
(TileEntityChest) localTileEntity, tileInventory);
} else {
tileInventory = new InventoryLargeChest("container.chestDouble",
tileInventory, (TileEntityChest) localTileEntity);
}
break;
}
BlockChest blockChest = (BlockChest) block;
if (blockChest.b == 0) {
player.b(StatisticList.aa);
} else if (blockChest.b == 1) {
player.b(StatisticList.U);
}
}
// AnyChest only - SilentChest not active, container unsupported, or unnecessary.
if (!silent || player.playerInteractManager.getGameMode() == EnumGamemode.SPECTATOR) {
player.openContainer(tileInventory);
return true;
}
// SilentChest requires access to setting players' gamemode directly.
if (this.playerInteractManagerGamemode == null) {
return false;
}
EnumGamemode gamemode = player.playerInteractManager.getGameMode();
this.forceGameMode(player, EnumGamemode.SPECTATOR);
player.openContainer(tileInventory);
this.forceGameMode(player, gamemode);
return true;
}
@Override
public void deactivateContainer(@NotNull final Player bukkitPlayer) {
if (this.playerInteractManagerGamemode == null) {
return;
}
InventoryView view = bukkitPlayer.getOpenInventory();
switch (view.getType()) {
case CHEST:
case ENDER_CHEST:
break;
default:
return;
}
EntityPlayer player = PlayerDataManager.getHandle(bukkitPlayer);
EnumGamemode gamemode = player.playerInteractManager.getGameMode();
this.forceGameMode(player, EnumGamemode.SPECTATOR);
player.activeContainer.b(player);
player.activeContainer = player.defaultContainer;
this.forceGameMode(player, gamemode);
}
private void forceGameMode(final EntityPlayer player, final EnumGamemode gameMode) {
if (this.playerInteractManagerGamemode == null) {
// No need to warn repeatedly, error on startup and lack of function should be enough.
return;
}
try {
if (!this.playerInteractManagerGamemode.isAccessible()) {
// Just in case, ensure accessible.
this.playerInteractManagerGamemode.setAccessible(true);
}
this.playerInteractManagerGamemode.set(player.playerInteractManager, gameMode);
} catch (Exception e) {
e.printStackTrace();
}
}
}
@@ -1,84 +0,0 @@
/*
* Copyright (C) 2011-2018 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.v1_8_R3;
import com.lishid.openinv.internal.IPlayerDataManager;
import com.lishid.openinv.internal.ISpecialInventory;
import com.mojang.authlib.GameProfile;
import net.minecraft.server.v1_8_R3.EntityPlayer;
import net.minecraft.server.v1_8_R3.MinecraftServer;
import net.minecraft.server.v1_8_R3.PlayerInteractManager;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.Server;
import org.bukkit.craftbukkit.v1_8_R3.CraftServer;
import org.bukkit.craftbukkit.v1_8_R3.entity.CraftPlayer;
import org.bukkit.entity.Player;
import org.bukkit.inventory.InventoryView;
import org.jetbrains.annotations.NotNull;
public class PlayerDataManager implements IPlayerDataManager {
@Override
public Player loadPlayer(@NotNull OfflinePlayer offline) {
// Ensure the player has data
if (!offline.hasPlayedBefore()) {
return null;
}
// Create a profile and entity to load the player data
GameProfile profile = new GameProfile(offline.getUniqueId(), offline.getName());
MinecraftServer server = ((CraftServer) Bukkit.getServer()).getServer();
EntityPlayer entity = new EntityPlayer(server, server.getWorldServer(0), profile,
new PlayerInteractManager(server.getWorldServer(0)));
// Get the bukkit entity
Player target = entity.getBukkitEntity();
if (target != null) {
// Load data
target.loadData();
}
// Return the entity
return target;
}
public static EntityPlayer getHandle(Player player) {
if (player instanceof CraftPlayer) {
return ((CraftPlayer) player).getHandle();
}
Server server = player.getServer();
EntityPlayer nmsPlayer = null;
if (server instanceof CraftServer) {
nmsPlayer = ((CraftServer) server).getHandle().getPlayer(player.getName());
}
if (nmsPlayer == null) {
// Could use reflection to examine fields, but it's honestly not worth the bother.
throw new RuntimeException("Unable to fetch EntityPlayer from provided Player implementation");
}
return nmsPlayer;
}
@Override
public InventoryView openInventory(@NotNull Player player, @NotNull ISpecialInventory inventory) {
return player.openInventory(inventory.getBukkitInventory());
}
}
@@ -1,83 +0,0 @@
/*
* Copyright (C) 2011-2018 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.v1_8_R3;
import com.lishid.openinv.internal.ISpecialEnderChest;
import java.lang.reflect.Field;
import net.minecraft.server.v1_8_R3.EntityPlayer;
import net.minecraft.server.v1_8_R3.IInventory;
import net.minecraft.server.v1_8_R3.InventoryEnderChest;
import net.minecraft.server.v1_8_R3.InventorySubcontainer;
import org.bukkit.craftbukkit.v1_8_R3.inventory.CraftInventory;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import org.jetbrains.annotations.NotNull;
public class SpecialEnderChest extends InventorySubcontainer implements IInventory, ISpecialEnderChest {
private final InventoryEnderChest enderChest;
private final CraftInventory inventory = new CraftInventory(this);
private boolean playerOnline;
public SpecialEnderChest(Player player, Boolean online) {
super(PlayerDataManager.getHandle(player).getEnderChest().getName(),
PlayerDataManager.getHandle(player).getEnderChest().hasCustomName(),
PlayerDataManager.getHandle(player).getEnderChest().getSize());
this.playerOnline = online;
EntityPlayer nmsPlayer = PlayerDataManager.getHandle(player);
this.enderChest = nmsPlayer.getEnderChest();
this.bukkitOwner = nmsPlayer.getBukkitEntity();
this.items = enderChest.getContents();
}
@Override
public @NotNull Inventory getBukkitInventory() {
return inventory;
}
@Override
public void setPlayerOnline(@NotNull Player player) {
if (!playerOnline) {
try {
EntityPlayer nmsPlayer = PlayerDataManager.getHandle(player);
this.bukkitOwner = nmsPlayer.getBukkitEntity();
InventoryEnderChest playerEnderChest = nmsPlayer.getEnderChest();
Field field = playerEnderChest.getClass().getField("items");
field.setAccessible(true);
field.set(playerEnderChest, this.items);
} catch (Exception ignored) {}
playerOnline = true;
}
}
@Override
public void setPlayerOffline() {
playerOnline = false;
}
@Override
public boolean isInUse() {
return !this.getViewers().isEmpty();
}
@Override
public void update() {
super.update();
enderChest.update();
}
}
@@ -1,230 +0,0 @@
/*
* Copyright (C) 2011-2018 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.v1_8_R3;
import com.lishid.openinv.internal.ISpecialPlayerInventory;
import net.minecraft.server.v1_8_R3.ItemStack;
import net.minecraft.server.v1_8_R3.PlayerInventory;
import org.bukkit.craftbukkit.v1_8_R3.inventory.CraftInventory;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import org.jetbrains.annotations.NotNull;
public class SpecialPlayerInventory extends PlayerInventory implements ISpecialPlayerInventory {
private final ItemStack[] extra = new ItemStack[5];
private final CraftInventory inventory = new CraftInventory(this);
private boolean playerOnline;
public SpecialPlayerInventory(Player bukkitPlayer, Boolean online) {
super(PlayerDataManager.getHandle(bukkitPlayer));
this.playerOnline = online;
this.items = player.inventory.items;
this.armor = player.inventory.armor;
}
@Override
public @NotNull Inventory getBukkitInventory() {
return inventory;
}
@Override
public void setPlayerOnline(@NotNull Player player) {
if (!playerOnline) {
this.player = PlayerDataManager.getHandle(player);
this.player.inventory.items = this.items;
this.player.inventory.armor = this.armor;
playerOnline = true;
}
}
@Override
public void setPlayerOffline() {
playerOnline = false;
}
@Override
public boolean isInUse() {
return !this.getViewers().isEmpty();
}
@Override
public ItemStack[] getContents() {
ItemStack[] contents = new ItemStack[getSize()];
System.arraycopy(items, 0, contents, 0, items.length);
System.arraycopy(armor, 0, contents, items.length, armor.length);
return contents;
}
@Override
public int getSize() {
return super.getSize() + 5;
}
@Override
public ItemStack getItem(int i) {
ItemStack[] is = this.items;
if (i >= is.length) {
i -= is.length;
is = this.armor;
} else {
i = getReversedItemSlotNum(i);
}
if (i >= is.length) {
i -= is.length;
is = this.extra;
} else if (is == this.armor) {
i = getReversedArmorSlotNum(i);
}
return is[i];
}
@Override
public ItemStack splitStack(int i, int j) {
ItemStack[] is = this.items;
if (i >= is.length) {
i -= is.length;
is = this.armor;
} else {
i = getReversedItemSlotNum(i);
}
if (i >= is.length) {
i -= is.length;
is = this.extra;
} else if (is == this.armor) {
i = getReversedArmorSlotNum(i);
}
if (is[i] != null) {
ItemStack itemstack;
if (is[i].count <= j) {
itemstack = is[i];
is[i] = null;
return itemstack;
} else {
itemstack = is[i].cloneAndSubtract(j);
if (is[i].count == 0) {
is[i] = null;
}
return itemstack;
}
}
return null;
}
@Override
public ItemStack splitWithoutUpdate(int i) {
ItemStack[] is = this.items;
if (i >= is.length) {
i -= is.length;
is = this.armor;
} else {
i = getReversedItemSlotNum(i);
}
if (i >= is.length) {
i -= is.length;
is = this.extra;
} else if (is == this.armor) {
i = getReversedArmorSlotNum(i);
}
if (is[i] != null) {
ItemStack itemstack = is[i];
is[i] = null;
return itemstack;
}
return null;
}
@Override
public void setItem(int i, ItemStack itemstack) {
ItemStack[] is = this.items;
if (i >= is.length) {
i -= is.length;
is = this.armor;
} else {
i = getReversedItemSlotNum(i);
}
if (i >= is.length) {
i -= is.length;
is = this.extra;
} else if (is == this.armor) {
i = getReversedArmorSlotNum(i);
}
// Effects
if (is == this.extra) {
player.drop(itemstack, true);
itemstack = null;
}
is[i] = itemstack;
player.defaultContainer.b();
}
private int getReversedItemSlotNum(int i) {
if (i >= 27) {
return i - 27;
}
return i + 9;
}
private int getReversedArmorSlotNum(int i) {
if (i == 0) {
return 3;
}
if (i == 1) {
return 2;
}
if (i == 2) {
return 1;
}
if (i == 3) {
return 0;
}
return i;
}
@Override
public String getName() {
if (player.getName().length() > 16) {
return player.getName().substring(0, 16);
}
return player.getName();
}
@Override
public boolean hasCustomName() {
return true;
}
}
+10 -27
View File
@@ -1,5 +1,5 @@
<!--
~ Copyright (C) 2011-2019 lishid. All rights reserved.
~ Copyright (C) 2011-2022 lishid. All rights reserved.
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
@@ -19,9 +19,9 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.lishid</groupId>
<artifactId>openinvparent</artifactId>
<version>4.0.2</version>
<groupId>com.lishid</groupId>
<version>4.2.1</version>
</parent>
<artifactId>openinvplugincore</artifactId>
@@ -29,15 +29,16 @@
<dependencies>
<dependency>
<artifactId>openinvapi</artifactId>
<groupId>com.lishid</groupId>
<artifactId>openinvcommon</artifactId>
<version>4.0.2</version>
</dependency>
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>annotations</artifactId>
<groupId>org.jetbrains</groupId>
</dependency>
<dependency>
<artifactId>spigot-api</artifactId>
<version>1.8.8-R0.1-SNAPSHOT</version>
<scope>provided</scope>
<groupId>org.spigotmc</groupId>
</dependency>
</dependencies>
@@ -48,31 +49,13 @@
<filtering>true</filtering>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.0.0</version>
<configuration>
<minimizeJar>true</minimizeJar>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2011-2018 lishid. All rights reserved.
* Copyright (C) 2011-2022 lishid. All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -14,52 +14,67 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.lishid.openinv.util;
package com.lishid.openinv;
import com.lishid.openinv.internal.IAnySilentContainer;
import com.lishid.openinv.internal.IPlayerDataManager;
import com.lishid.openinv.internal.ISpecialEnderChest;
import com.lishid.openinv.internal.ISpecialInventory;
import com.lishid.openinv.internal.ISpecialPlayerInventory;
import com.lishid.openinv.util.InventoryAccess;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.jetbrains.annotations.NotNull;
public class InternalAccessor {
class InternalAccessor {
private final Plugin plugin;
private final @NotNull Plugin plugin;
private final String version;
private boolean supported = false;
private IPlayerDataManager playerDataManager;
private IAnySilentContainer anySilentContainer;
public InternalAccessor(final Plugin plugin) {
InternalAccessor(@NotNull Plugin plugin) {
this.plugin = plugin;
String packageName = plugin.getServer().getClass().getPackage().getName();
this.version = packageName.substring(packageName.lastIndexOf('.') + 1);
try {
// TODO: implement support for CraftMagicNumbers#getMappingsVersion
Class.forName("com.lishid.openinv.internal." + this.version + ".SpecialPlayerInventory");
Class.forName("com.lishid.openinv.internal." + this.version + ".SpecialEnderChest");
this.playerDataManager = this.createObject(IPlayerDataManager.class, "PlayerDataManager");
this.anySilentContainer = this.createObject(IAnySilentContainer.class, "AnySilentContainer");
this.supported = InventoryAccess.isUseable();
this.supported = InventoryAccess.isUsable();
} catch (Exception ignored) {}
}
private <T> T createObject(final Class<? extends T> assignableClass, final String className,
final Object... params) throws ClassCastException, ClassNotFoundException,
InstantiationException, IllegalAccessException, IllegalArgumentException,
InvocationTargetException, NoSuchMethodException, SecurityException {
public String getReleasesLink() {
return switch (version) {
case "1_4_5", "1_4_6", "v1_4_R1", "v1_5_R2", "v1_5_R3", "v1_6_R1", "v1_6_R2", "v1_6_R3",
"v1_7_R1", "v1_7_R2", "v1_7_R3", "v1_7_R4", "v1_8_R1", "v1_8_R2",
"v1_9_R1", "v1_9_R2", "v1_10_R1", "v1_11_R1", "v1_12_R1"
-> "https://github.com/lishid/OpenInv/releases/tag/4.0.0 (OpenInv-legacy)";
case "v1_13_R1" -> "https://github.com/lishid/OpenInv/releases/tag/4.0.0";
case "v1_13_R2" -> "https://github.com/lishid/OpenInv/releases/tag/4.0.7";
case "v1_14_R1" -> "https://github.com/lishid/OpenInv/releases/tag/4.1.1";
case "v1_16_R1" -> "https://github.com/lishid/OpenInv/releases/tag/4.1.4";
case "v1_8_R3", "v1_15_R1", "v1_16_R2" -> "https://github.com/lishid/OpenInv/releases/tag/4.1.5";
case "v1_16_R3" -> "https://github.com/Jikoo/OpenInv/releases/tag/4.1.8";
case "v1_17_R1", "v1_18_R1" -> "https://github.com/Jikoo/OpenInv/releases/tag/4.1.10";
default -> "https://github.com/Jikoo/OpenInv/releases";
};
}
private @NotNull <T> T createObject(
@NotNull Class<? extends T> assignableClass,
@NotNull String className,
@NotNull Object @NotNull ... params)
throws ClassCastException, ReflectiveOperationException {
// Fetch internal class if it exists.
Class<?> internalClass = Class.forName("com.lishid.openinv.internal." + this.version + "." + className);
if (!assignableClass.isAssignableFrom(internalClass)) {
String message = String.format("Found class %s but cannot cast to %s!", internalClass.getName(), assignableClass.getName());
this.plugin.getLogger().warning(message);
throw new IllegalStateException(message);
}
// Quick return: no parameters, no need to fiddle about finding the correct constructor.
if (params.length == 0) {
@@ -90,7 +105,24 @@ public class InternalAccessor {
String message = builder.append(']').toString();
this.plugin.getLogger().warning(message);
throw new IllegalArgumentException(message);
throw new NoSuchMethodException(message);
}
private @NotNull <T extends ISpecialInventory> T createSpecialInventory(
@NotNull Class<? extends T> assignableClass,
@NotNull String className,
@NotNull Player player,
boolean online) throws InstantiationException {
if (!this.supported) {
throw new IllegalStateException(String.format("Unsupported server version %s!", this.version));
}
try {
return this.createObject(assignableClass, className, player, online);
} catch (Exception original) {
InstantiationException exception = new InstantiationException(String.format("Unable to create a new %s", className));
exception.initCause(original.fillInStackTrace());
throw exception;
}
}
/**
@@ -99,7 +131,7 @@ public class InternalAccessor {
* @return the IAnySilentContainer
* @throws IllegalStateException if server version is unsupported
*/
public IAnySilentContainer getAnySilentContainer() {
public @NotNull IAnySilentContainer getAnySilentContainer() {
if (!this.supported) {
throw new IllegalStateException(String.format("Unsupported server version %s!", this.version));
}
@@ -112,7 +144,7 @@ public class InternalAccessor {
* @return the IPlayerDataManager
* @throws IllegalStateException if server version is unsupported
*/
public IPlayerDataManager getPlayerDataManager() {
public @NotNull IPlayerDataManager getPlayerDataManager() {
if (!this.supported) {
throw new IllegalStateException(String.format("Unsupported server version %s!", this.version));
}
@@ -120,13 +152,12 @@ public class InternalAccessor {
}
/**
* Gets the server implementation version. If not initialized, returns the string "null"
* instead.
* Gets the server implementation version.
*
* @return the version, or "null"
* @return the version
*/
public String getVersion() {
return this.version != null ? this.version : "null";
public @NotNull String getVersion() {
return this.version;
}
/**
@@ -148,14 +179,7 @@ public class InternalAccessor {
* @throws InstantiationException if the ISpecialEnderChest could not be instantiated
*/
public ISpecialEnderChest newSpecialEnderChest(final Player player, final boolean online) throws InstantiationException {
if (!this.supported) {
throw new IllegalStateException(String.format("Unsupported server version %s!", this.version));
}
try {
return this.createObject(ISpecialEnderChest.class, "SpecialEnderChest", player, online);
} catch (Exception e) {
throw new InstantiationException(String.format("Unable to create a new ISpecialEnderChest: %s", e.getMessage()));
}
return this.createSpecialInventory(ISpecialEnderChest.class, "SpecialEnderChest", player, online);
}
/**
@@ -167,14 +191,7 @@ public class InternalAccessor {
* @throws InstantiationException if the ISpecialPlayerInventory could not be instantiated
*/
public ISpecialPlayerInventory newSpecialPlayerInventory(final Player player, final boolean online) throws InstantiationException {
if (!this.supported) {
throw new IllegalStateException(String.format("Unsupported server version %s!", this.version));
}
try {
return this.createObject(ISpecialPlayerInventory.class, "SpecialPlayerInventory", player, online);
} catch (Exception e) {
throw new InstantiationException(String.format("Unable to create a new ISpecialPlayerInventory: %s", e.getMessage()));
}
return this.createSpecialInventory(ISpecialPlayerInventory.class, "SpecialPlayerInventory", player, online);
}
}
@@ -0,0 +1,207 @@
/*
* 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;
import com.lishid.openinv.internal.ISpecialInventory;
import com.lishid.openinv.internal.ISpecialPlayerInventory;
import com.lishid.openinv.util.InventoryAccess;
import com.lishid.openinv.util.Permissions;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.bukkit.GameMode;
import org.bukkit.entity.HumanEntity;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.inventory.InventoryAction;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.inventory.InventoryCloseEvent;
import org.bukkit.event.inventory.InventoryDragEvent;
import org.bukkit.event.inventory.InventoryInteractEvent;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryView;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Listener for inventory-related events to prevent modification of inventories where not allowed.
*
* @author Jikoo
*/
record InventoryListener(OpenInv plugin) implements Listener {
@EventHandler
private void onInventoryClose(@NotNull final InventoryCloseEvent event) {
if (!(event.getPlayer() instanceof Player player)) {
return;
}
if (this.plugin.getSilentContainerStatus(player)) {
this.plugin.getAnySilentContainer().deactivateContainer(player);
}
ISpecialInventory specialInventory = InventoryAccess.getEnderChest(event.getInventory());
if (specialInventory != null) {
this.plugin.handleCloseInventory(event.getPlayer(), specialInventory);
} else {
specialInventory = InventoryAccess.getPlayerInventory(event.getInventory());
if (specialInventory != null) {
this.plugin.handleCloseInventory(event.getPlayer(), specialInventory);
}
}
}
@EventHandler(priority = EventPriority.LOWEST)
private void onInventoryClick(@NotNull final InventoryClickEvent event) {
if (handleInventoryInteract(event)) {
return;
}
// Safe cast - has to be a player to be the holder of a special player inventory.
Player player = (Player) event.getWhoClicked();
if (event.getAction() != InventoryAction.MOVE_TO_OTHER_INVENTORY) {
// All own-inventory interactions require updates to display properly.
// Update in same tick after event completion.
this.plugin.getServer().getScheduler().runTask(this.plugin, player::updateInventory);
return;
}
// Extra handling for MOVE_TO_OTHER_INVENTORY - apparently Mojang no longer removes the item from the target
// inventory prior to adding it to existing stacks.
ItemStack currentItem = event.getCurrentItem();
if (currentItem == null) {
// Other plugin doing some sort of handling (would be NOTHING for null item otherwise), ignore.
return;
}
ItemStack clone = currentItem.clone();
event.setCurrentItem(null);
// Complete add action in same tick after event completion.
this.plugin.getServer().getScheduler().runTask(this.plugin, () -> {
player.getInventory().addItem(clone);
player.updateInventory();
});
}
@EventHandler(priority = EventPriority.LOWEST)
private void onInventoryDrag(@NotNull final InventoryDragEvent event) {
if (handleInventoryInteract(event)) {
return;
}
InventoryView view = event.getView();
int topSize = view.getTopInventory().getSize();
// Get bottom inventory active slots as player inventory slots.
Set<Integer> slots = event.getRawSlots().stream()
.filter(slot -> slot >= topSize)
.map(slot -> plugin.convertToPlayerSlot(view, slot)).collect(Collectors.toSet());
int overlapLosses = 0;
// Count overlapping slots.
for (Map.Entry<Integer, ItemStack> newItem : event.getNewItems().entrySet()) {
int rawSlot = newItem.getKey();
// Skip bottom inventory slots.
if (rawSlot >= topSize) {
continue;
}
int convertedSlot = plugin.convertToPlayerSlot(view, rawSlot);
if (slots.contains(convertedSlot)) {
overlapLosses += getCountDiff(view.getItem(rawSlot), newItem.getValue());
}
}
// Allow no overlap to proceed as usual.
if (overlapLosses < 1) {
return;
}
ItemStack cursor = event.getCursor();
if (cursor != null) {
cursor.setAmount(cursor.getAmount() + overlapLosses);
} else {
cursor = event.getOldCursor().clone();
cursor.setAmount(overlapLosses);
}
event.setCursor(cursor);
}
private int getCountDiff(@Nullable ItemStack original, @NotNull ItemStack result) {
if (original == null || original.getType() != result.getType()) {
return result.getAmount();
}
return result.getAmount() - original.getAmount();
}
/**
* Handle common InventoryInteractEvent functions.
*
* @param event the InventoryInteractEvent
* @return true unless the top inventory is the holder's own inventory
*/
private boolean handleInventoryInteract(@NotNull final InventoryInteractEvent event) {
HumanEntity entity = event.getWhoClicked();
// Un-cancel spectator interactions.
if (Permissions.SPECTATE.hasPermission(entity) && entity.getGameMode() == GameMode.SPECTATOR) {
event.setCancelled(false);
}
if (event.isCancelled()) {
return true;
}
Inventory inventory = event.getView().getTopInventory();
// Is the inventory a special ender chest?
if (InventoryAccess.isEnderChest(inventory)) {
// Disallow ender chest interaction for users without edit permission.
if (!Permissions.EDITENDER.hasPermission(entity)) {
event.setCancelled(true);
}
return true;
}
ISpecialPlayerInventory playerInventory = InventoryAccess.getPlayerInventory(inventory);
// Ignore inventories other than special player inventories.
if (playerInventory == null) {
return true;
}
// Disallow player inventory interaction for users without edit permission.
if (!Permissions.EDITINV.hasPermission(entity)) {
event.setCancelled(true);
return true;
}
// Only specially handle actions in the player's own inventory.
return !event.getWhoClicked().equals(playerInventory.getPlayer());
}
}
@@ -0,0 +1,41 @@
/*
* 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;
import com.lishid.openinv.internal.ISpecialInventory;
import com.lishid.openinv.util.Permissions;
import java.util.Map;
import java.util.UUID;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import org.jetbrains.annotations.NotNull;
record OfflineHandler(
@NotNull BiFunction<Map<UUID, ? extends ISpecialInventory>, UUID, ISpecialInventory> fetch,
@NotNull Consumer<@NotNull ISpecialInventory> handle) {
static final OfflineHandler REMOVE_AND_CLOSE = new OfflineHandler(
Map::remove,
inventory -> OpenInv.ejectViewers(inventory, viewer -> true)
);
static final OfflineHandler REQUIRE_PERMISSIONS = new OfflineHandler(
Map::get,
inventory -> OpenInv.ejectViewers(inventory, viewer -> !Permissions.OPENOFFLINE.hasPermission(viewer))
);
}
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2011-2018 lishid. All rights reserved.
* Copyright (C) 2011-2022 lishid. All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -16,46 +16,42 @@
package com.lishid.openinv;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import com.lishid.openinv.commands.AnyChestPluginCommand;
import com.lishid.openinv.commands.OpenInvPluginCommand;
import com.lishid.openinv.commands.SearchEnchantPluginCommand;
import com.lishid.openinv.commands.SearchInvPluginCommand;
import com.lishid.openinv.commands.SilentChestPluginCommand;
import com.lishid.openinv.commands.ContainerSettingCommand;
import com.lishid.openinv.commands.OpenInvCommand;
import com.lishid.openinv.commands.SearchContainerCommand;
import com.lishid.openinv.commands.SearchEnchantCommand;
import com.lishid.openinv.commands.SearchInvCommand;
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.listeners.InventoryClickListener;
import com.lishid.openinv.listeners.InventoryCloseListener;
import com.lishid.openinv.listeners.InventoryDragListener;
import com.lishid.openinv.listeners.PlayerListener;
import com.lishid.openinv.listeners.PluginListener;
import com.lishid.openinv.util.Cache;
import com.lishid.openinv.util.ConfigUpdater;
import com.lishid.openinv.util.Function;
import com.lishid.openinv.util.InternalAccessor;
import com.lishid.openinv.util.LanguageManager;
import com.lishid.openinv.util.Permissions;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Stream;
import net.md_5.bungee.api.ChatMessageType;
import net.md_5.bungee.api.chat.TextComponent;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.command.PluginCommand;
import org.bukkit.entity.HumanEntity;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryView;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.scheduler.BukkitRunnable;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -66,167 +62,161 @@ import org.jetbrains.annotations.Nullable;
*/
public class OpenInv extends JavaPlugin implements IOpenInv {
private final Map<String, ISpecialPlayerInventory> inventories = new HashMap<>();
private final Map<String, ISpecialEnderChest> enderChests = new HashMap<>();
private final Multimap<String, Class<? extends Plugin>> pluginUsage = HashMultimap.create();
private final Cache<String, Player> playerCache = new Cache<>(300000L,
new Function<Player>() {
@Override
public boolean run(final Player value) {
String key = OpenInv.this.getPlayerID(value);
return OpenInv.this.inventories.containsKey(key)
&& OpenInv.this.inventories.get(key).isInUse()
|| OpenInv.this.enderChests.containsKey(key)
&& OpenInv.this.enderChests.get(key).isInUse()
|| OpenInv.this.pluginUsage.containsKey(key);
}
}, new Function<Player>() {
@Override
public boolean run(final Player value) {
String key = OpenInv.this.getPlayerID(value);
// Check if inventory is stored, and if it is, remove it and eject all viewers
if (OpenInv.this.inventories.containsKey(key)) {
Inventory inv = OpenInv.this.inventories.remove(key).getBukkitInventory();
List<HumanEntity> viewers = inv.getViewers();
for (HumanEntity entity : viewers.toArray(new HumanEntity[0])) {
entity.closeInventory();
}
}
// Check if ender chest is stored, and if it is, remove it and eject all viewers
if (OpenInv.this.enderChests.containsKey(key)) {
Inventory inv = OpenInv.this.enderChests.remove(key).getBukkitInventory();
List<HumanEntity> viewers = inv.getViewers();
for (HumanEntity entity : viewers.toArray(new HumanEntity[0])) {
entity.closeInventory();
}
}
if (!OpenInv.this.disableSaving() && !value.isOnline()) {
value.saveData();
}
return true;
}
});
private final Map<UUID, ISpecialPlayerInventory> inventories = new ConcurrentHashMap<>();
private final Map<UUID, ISpecialEnderChest> enderChests = new ConcurrentHashMap<>();
private InternalAccessor accessor;
private LanguageManager languageManager;
private boolean isSpigot = false;
private OfflineHandler offlineHandler;
/**
* Evicts all viewers lacking cross-world permissions from a Player's inventory.
*
* @param player the Player
*/
public void changeWorld(final Player player) {
@Override
public void reloadConfig() {
super.reloadConfig();
this.offlineHandler = disableOfflineAccess() ? OfflineHandler.REMOVE_AND_CLOSE : OfflineHandler.REQUIRE_PERMISSIONS;
}
String key = this.getPlayerID(player);
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (!isSpigot || !this.accessor.isSupported()) {
this.sendVersionError(sender::sendMessage);
return true;
}
return false;
}
// Check if the player is cached. If not, neither of their inventories is open.
if (!this.playerCache.containsKey(key)) {
@Override
public void onDisable() {
if (this.disableSaving()) {
return;
}
if (this.inventories.containsKey(key)) {
Iterator<HumanEntity> iterator = this.inventories.get(key).getBukkitInventory().getViewers().iterator();
while (iterator.hasNext()) {
HumanEntity human = iterator.next();
// If player has permission or is in the same world, allow continued access
// Just in case, also allow null worlds.
if (Permissions.CROSSWORLD.hasPermission(human) || human.getWorld() == null
|| human.getWorld().equals(player.getWorld())) {
continue;
}
human.closeInventory();
}
Stream.concat(inventories.values().stream(), enderChests.values().stream())
.map(inventory -> {
// Cheat a bit - rather than stream twice, evict all viewers during remapping.
ejectViewers(inventory, viewer -> true);
if (inventory.getPlayer() instanceof Player player) {
return player;
}
return null;
})
.filter(Objects::nonNull)
.distinct()
.forEach(player -> {
if (!player.isOnline()) {
player = accessor.getPlayerDataManager().inject(player);
}
player.saveData();
});
}
@Override
public void onEnable() {
// Save default configuration if not present.
this.saveDefaultConfig();
// Get plugin manager
PluginManager pm = this.getServer().getPluginManager();
this.accessor = new InternalAccessor(this);
this.languageManager = new LanguageManager(this, "en_us");
this.offlineHandler = disableOfflineAccess() ? OfflineHandler.REMOVE_AND_CLOSE : OfflineHandler.REQUIRE_PERMISSIONS;
try {
Class.forName("org.bukkit.entity.Player$Spigot");
isSpigot = true;
} catch (ClassNotFoundException e) {
e.printStackTrace();
isSpigot = false;
}
if (this.enderChests.containsKey(key)) {
Iterator<HumanEntity> iterator = this.enderChests.get(key).getBukkitInventory().getViewers().iterator();
while (iterator.hasNext()) {
HumanEntity human = iterator.next();
if (Permissions.CROSSWORLD.hasPermission(human) || human.getWorld() == null
|| human.getWorld().equals(player.getWorld())) {
continue;
}
human.closeInventory();
// Version check
if (isSpigot && this.accessor.isSupported()) {
// Update existing configuration. May require internal access.
new ConfigUpdater(this).checkForUpdates();
// Register listeners
pm.registerEvents(new PlayerListener(this), this);
pm.registerEvents(new InventoryListener(this), this);
// Register commands to their executors
this.setCommandExecutor(new OpenInvCommand(this), "openinv", "openender");
this.setCommandExecutor(new SearchContainerCommand(this), "searchcontainer");
this.setCommandExecutor(new SearchInvCommand(this), "searchinv", "searchender");
this.setCommandExecutor(new SearchEnchantCommand(this), "searchenchant");
this.setCommandExecutor(new ContainerSettingCommand(this), "silentcontainer", "anycontainer");
} else {
this.sendVersionError(this.getLogger()::warning);
}
}
private void setCommandExecutor(@NotNull CommandExecutor executor, String @NotNull ... commands) {
for (String commandName : commands) {
PluginCommand command = this.getCommand(commandName);
if (command != null) {
command.setExecutor(executor);
}
}
}
private void sendVersionError(@NotNull Consumer<String> messageMethod) {
if (!this.accessor.isSupported()) {
messageMethod.accept("Your server version (" + this.accessor.getVersion() + ") is not supported.");
messageMethod.accept("Please download the correct version of OpenInv here: " + this.accessor.getReleasesLink());
}
if (!isSpigot) {
messageMethod.accept("OpenInv requires that you use Spigot or a Spigot fork. Per the 1.14 update thread");
messageMethod.accept("(https://www.spigotmc.org/threads/369724/ \"A Note on CraftBukkit\"), if you are");
messageMethod.accept("encountering an inconsistency with vanilla that prevents you from using Spigot,");
messageMethod.accept("that is considered a Spigot bug and should be reported as such.");
}
}
@Override
public boolean isSupportedVersion() {
return this.accessor != null && this.accessor.isSupported();
}
@Override
public boolean disableSaving() {
return this.getConfig().getBoolean("settings.disable-saving", false);
}
@NotNull
@Override
public IAnySilentContainer getAnySilentContainer() {
public boolean disableOfflineAccess() {
return this.getConfig().getBoolean("settings.disable-offline-access", false);
}
@Override
public @NotNull IAnySilentContainer getAnySilentContainer() {
return this.accessor.getAnySilentContainer();
}
private int getLevenshteinDistance(final String string1, final String string2) {
if (string1 == null || string2 == null) {
throw new IllegalArgumentException("Strings must not be null");
}
if (string1.isEmpty()) {
return string2.length();
}
if (string2.isEmpty()) {
return string2.length();
}
if (string1.equals(string2)) {
return 0;
}
int len1 = string1.length();
int len2 = string2.length();
int[] prevDistances = new int[len1 + 1];
int[] distances = new int[len1 + 1];
for (int i = 0; i <= len1; ++i) {
prevDistances[i] = i;
}
for (int i = 1; i <= len2; ++i) {
// TODO: include tweaks available in Simmetrics?
char string2char = string2.charAt(i - 1);
distances[0] = i;
for (int j = 1; j <= len1; ++j) {
int cost = string1.charAt(j - 1) == string2char ? 0 : 1;
distances[j] = Math.min(Math.min(distances[j - 1] + 1, prevDistances[j] + 1), prevDistances[j - 1] + cost);
}
int[] swap = prevDistances;
prevDistances = distances;
distances = swap;
}
return prevDistances[len1];
}
@Override
public boolean getPlayerAnyChestStatus(@NotNull final OfflinePlayer player) {
public boolean getAnyContainerStatus(@NotNull final OfflinePlayer offline) {
boolean defaultState = false;
if (player.isOnline()) {
Player onlinePlayer = player.getPlayer();
if (offline.isOnline()) {
Player onlinePlayer = offline.getPlayer();
if (onlinePlayer != null) {
defaultState = Permissions.ANY_DEFAULT.hasPermission(onlinePlayer);
}
}
return this.getConfig().getBoolean("toggles.any-chest." + this.getPlayerID(player), defaultState);
return this.getConfig().getBoolean("toggles.any-chest." + offline.getUniqueId(), defaultState);
}
@Override
public boolean getPlayerSilentChestStatus(@NotNull final OfflinePlayer offline) {
public void setAnyContainerStatus(@NotNull final OfflinePlayer offline, final boolean status) {
this.getConfig().set("toggles.any-chest." + offline.getUniqueId(), status);
this.saveConfig();
}
@Override
public boolean getSilentContainerStatus(@NotNull final OfflinePlayer offline) {
boolean defaultState = false;
if (offline.isOnline()) {
@@ -236,61 +226,71 @@ public class OpenInv extends JavaPlugin implements IOpenInv {
}
}
return this.getConfig().getBoolean("toggles.silent-chest." + this.getPlayerID(offline), defaultState);
return this.getConfig().getBoolean("toggles.silent-chest." + offline.getUniqueId(), defaultState);
}
@NotNull
@Override
public ISpecialEnderChest getSpecialEnderChest(@NotNull final Player player, final boolean online)
public void setSilentContainerStatus(@NotNull final OfflinePlayer offline, final boolean status) {
this.getConfig().set("toggles.silent-chest." + offline.getUniqueId(), status);
this.saveConfig();
}
@Override
public @NotNull ISpecialEnderChest getSpecialEnderChest(@NotNull final Player player, final boolean online)
throws InstantiationException {
String id = this.getPlayerID(player);
if (this.enderChests.containsKey(id)) {
return this.enderChests.get(id);
UUID key = player.getUniqueId();
if (this.enderChests.containsKey(key)) {
return this.enderChests.get(key);
}
ISpecialEnderChest inv = this.accessor.newSpecialEnderChest(player, online);
this.enderChests.put(id, inv);
this.playerCache.put(id, player);
this.enderChests.put(key, inv);
return inv;
}
@NotNull
@Override
public ISpecialPlayerInventory getSpecialInventory(@NotNull final Player player, final boolean online)
public @NotNull ISpecialPlayerInventory getSpecialInventory(@NotNull final Player player, final boolean online)
throws InstantiationException {
String id = this.getPlayerID(player);
if (this.inventories.containsKey(id)) {
return this.inventories.get(id);
UUID key = player.getUniqueId();
if (this.inventories.containsKey(key)) {
return this.inventories.get(key);
}
ISpecialPlayerInventory inv = this.accessor.newSpecialPlayerInventory(player, online);
this.inventories.put(id, inv);
this.playerCache.put(id, player);
this.inventories.put(key, inv);
return inv;
}
@Override
public boolean isSupportedVersion() {
return this.accessor != null && this.accessor.isSupported();
public @Nullable InventoryView openInventory(@NotNull Player player, @NotNull ISpecialInventory inventory) {
return this.accessor.getPlayerDataManager().openInventory(player, inventory);
}
@Nullable
@Override
public Player loadPlayer(@NotNull final OfflinePlayer offline) {
public boolean isPlayerLoaded(@NotNull UUID playerUuid) {
return this.inventories.containsKey(playerUuid) || this.enderChests.containsKey(playerUuid);
}
String key = this.getPlayerID(offline);
if (this.playerCache.containsKey(key)) {
return this.playerCache.get(key);
@Override
public @Nullable Player loadPlayer(@NotNull final OfflinePlayer offline) {
UUID key = offline.getUniqueId();
if (this.inventories.containsKey(key)) {
return (Player) this.inventories.get(key).getPlayer();
}
// TODO: wrap Player to ensure all methods can safely be called offline
Player loaded;
if (offline.isOnline()) {
loaded = offline.getPlayer();
this.playerCache.put(key, loaded);
return loaded;
if (this.enderChests.containsKey(key)) {
return (Player) this.enderChests.get(key).getPlayer();
}
if (!this.isSupportedVersion()) {
Player player = offline.getPlayer();
if (player != null) {
return player;
}
if (disableOfflineAccess() || !this.isSupportedVersion()) {
return null;
}
@@ -301,179 +301,185 @@ public class OpenInv extends JavaPlugin implements IOpenInv {
Future<Player> future = Bukkit.getScheduler().callSyncMethod(this,
() -> OpenInv.this.accessor.getPlayerDataManager().loadPlayer(offline));
int ticks = 0;
while (!future.isDone() && !future.isCancelled() && ticks < 10) {
++ticks;
try {
Thread.sleep(50L);
} catch (InterruptedException e) {
e.printStackTrace();
return null;
}
}
if (!future.isDone() || future.isCancelled()) {
return null;
}
try {
loaded = future.get();
player = future.get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
return null;
}
if (loaded != null) {
this.playerCache.put(key, loaded);
return player;
}
@Override
public void unload(@NotNull final OfflinePlayer offline) {
setPlayerOffline(offline, OfflineHandler.REMOVE_AND_CLOSE);
}
/**
* Evict all viewers lacking cross-world permissions when a {@link Player} changes worlds.
*
* @param player the Player
*/
void changeWorld(@NotNull Player player) {
UUID key = player.getUniqueId();
if (this.inventories.containsKey(key)) {
kickCrossWorldViewers(player, this.inventories.get(key));
}
return loaded;
}
@Override
public @Nullable InventoryView openInventory(@NotNull Player player, @NotNull ISpecialInventory inventory) {
return this.accessor.getPlayerDataManager().openInventory(player, inventory);
}
@Override
public boolean notifyAnyChest() {
return this.getConfig().getBoolean("notify.any-chest", true);
}
@Override
public boolean notifySilentChest() {
return this.getConfig().getBoolean("notify.silent-chest", true);
}
@Override
public void onDisable() {
if (this.disableSaving()) {
return;
}
if (this.isSupportedVersion()) {
this.playerCache.invalidateAll();
if (this.enderChests.containsKey(key)) {
kickCrossWorldViewers(player, this.enderChests.get(key));
}
}
@Override
public void onEnable() {
private void kickCrossWorldViewers(@NotNull Player player, @NotNull ISpecialInventory inventory) {
ejectViewers(
inventory,
viewer ->
!Permissions.CROSSWORLD.hasPermission(viewer)
&& !Objects.equals(viewer.getWorld(), player.getWorld()));
}
// Save default configuration if not present.
this.saveDefaultConfig();
/**
* Convert a raw slot number into a player inventory slot number.
*
* <p>Note that this method is specifically for converting an ISpecialPlayerInventory slot number into a regular
* player inventory slot number.
*
* @param view the open inventory view
* @param rawSlot the raw slot in the view
* @return the converted slot number
*/
int convertToPlayerSlot(InventoryView view, int rawSlot) {
return this.accessor.getPlayerDataManager().convertToPlayerSlot(view, rawSlot);
}
// Get plugin manager
PluginManager pm = this.getServer().getPluginManager();
public @Nullable String getLocalizedMessage(@NotNull CommandSender sender, @NotNull String key) {
return this.languageManager.getValue(key, getLocale(sender));
}
this.accessor = new InternalAccessor(this);
public @Nullable String getLocalizedMessage(
@NotNull CommandSender sender,
@NotNull String key,
String @NotNull ... replacements) {
return this.languageManager.getValue(key, getLocale(sender), replacements);
}
// Version check
if (this.accessor.isSupported()) {
// Update existing configuration. May require internal access.
new ConfigUpdater(this).checkForUpdates();
// Register listeners
pm.registerEvents(new PlayerListener(this), this);
pm.registerEvents(new PluginListener(this), this);
pm.registerEvents(new InventoryClickListener(), this);
pm.registerEvents(new InventoryCloseListener(this), this);
// Bukkit will handle missing events for us, attempt to register InventoryDragEvent without a version check
pm.registerEvents(new InventoryDragListener(), this);
// Register commands to their executors
OpenInvPluginCommand openInv = new OpenInvPluginCommand(this);
this.getCommand("openinv").setExecutor(openInv);
this.getCommand("openender").setExecutor(openInv);
SearchInvPluginCommand searchInv = new SearchInvPluginCommand(this);
this.getCommand("searchinv").setExecutor(searchInv);
this.getCommand("searchender").setExecutor(searchInv);
this.getCommand("searchenchant").setExecutor(new SearchEnchantPluginCommand(this));
this.getCommand("silentchest").setExecutor(new SilentChestPluginCommand(this));
this.getCommand("anychest").setExecutor(new AnyChestPluginCommand(this));
private @NotNull String getLocale(@NotNull CommandSender sender) {
if (sender instanceof Player) {
return ((Player) sender).getLocale();
} else {
this.getLogger().info("Your version of CraftBukkit (" + this.accessor.getVersion() + ") is not supported.");
this.getLogger().info("If this version is a recent release, check for an update.");
this.getLogger().info("If this is an older version, ensure that you've downloaded the legacy support version.");
return this.getConfig().getString("settings.locale", "en_us");
}
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (!this.accessor.isSupported()) {
sender.sendMessage("Your version of CraftBukkit (" + this.accessor.getVersion() + ") is not supported.");
sender.sendMessage("If this version is a recent release, check for an update.");
sender.sendMessage("If this is an older version, ensure that you've downloaded the legacy support version.");
return true;
public void sendMessage(@NotNull CommandSender sender, @NotNull String key) {
String message = getLocalizedMessage(sender, key);
if (message != null && !message.isEmpty()) {
sender.sendMessage(message);
}
return false;
}
public void releaseAllPlayers(final Plugin plugin) {
Iterator<Map.Entry<String, Class<? extends Plugin>>> iterator = this.pluginUsage.entries().iterator();
public void sendMessage(@NotNull CommandSender sender, @NotNull String key, String @NotNull... replacements) {
String message = getLocalizedMessage(sender, key, replacements);
if (!iterator.hasNext()) {
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;
}
for (Map.Entry<String, Class<? extends Plugin>> entry = iterator.next(); iterator.hasNext(); entry = iterator.next()) {
if (entry.getValue().equals(plugin.getClass())) {
iterator.remove();
}
int newline = message.indexOf('\n');
if (newline != -1) {
// No newlines in action bar chat.
message = message.substring(0, newline);
}
}
@Override
public void releasePlayer(@NotNull final Player player, @NotNull final Plugin plugin) {
String key = this.getPlayerID(player);
if (!this.pluginUsage.containsEntry(key, plugin.getClass())) {
if (message.isEmpty()) {
return;
}
this.pluginUsage.remove(key, plugin.getClass());
}
@Override
public void retainPlayer(@NotNull final Player player, @NotNull final Plugin plugin) {
String key = this.getPlayerID(player);
if (this.pluginUsage.containsEntry(key, plugin.getClass())) {
return;
}
this.pluginUsage.put(key, plugin.getClass());
}
@Override
public void setPlayerAnyChestStatus(@NotNull final OfflinePlayer offline, final boolean status) {
this.getConfig().set("toggles.any-chest." + this.getPlayerID(offline), status);
this.saveConfig();
player.spigot().sendMessage(ChatMessageType.ACTION_BAR, TextComponent.fromLegacyText(message));
}
/**
* Method for handling a Player going offline.
*
* @param player the Player
* @throws IllegalStateException if the server version is unsupported
*/
public void setPlayerOffline(final Player player) {
void setPlayerOffline(@NotNull Player player) {
setPlayerOffline(player, offlineHandler);
}
String key = this.getPlayerID(player);
private void setPlayerOffline(@NotNull OfflinePlayer player, @NotNull OfflineHandler handler) {
UUID key = player.getUniqueId();
// Check if the player is cached. If not, neither of their inventories is open.
if (!this.playerCache.containsKey(key)) {
setPlayerOffline(inventories, key, handler);
setPlayerOffline(enderChests, key, handler);
}
private void setPlayerOffline(
@NotNull Map<UUID, ? extends ISpecialInventory> map,
@NotNull UUID key,
@NotNull OfflineHandler handler) {
ISpecialInventory inventory = handler.fetch().apply(map, key);
if (inventory == null) {
return;
}
inventory.setPlayerOffline();
if (!inventory.isInUse()) {
map.remove(key);
} else {
handler.handle().accept(inventory);
}
}
void handleCloseInventory(@NotNull HumanEntity exViewer, @NotNull ISpecialInventory inventory) {
Map<UUID, ? extends ISpecialInventory> map = inventory instanceof ISpecialPlayerInventory ? inventories : enderChests;
UUID key = inventory.getPlayer().getUniqueId();
@Nullable ISpecialInventory loaded = map.get(key);
if (loaded == null) {
// Loaded inventory has already been removed. Removal will handle saving if necessary.
return;
}
if (this.inventories.containsKey(key)) {
this.inventories.get(key).setPlayerOffline();
// This should only be possible if a plugin is doing funky things with our inventories.
if (loaded != inventory) {
Inventory bukkitInventory = inventory.getBukkitInventory();
// Just in case, respect contents of the inventory that was just used.
loaded.getBukkitInventory().setContents(bukkitInventory.getContents());
// We need to close this inventory to reduce risk of duplication bugs if the user is offline.
// We don't want to risk recursively closing the same inventory repeatedly, so we schedule dumping viewers.
// Worst case we schedule a couple redundant tasks if several people had the inventory open.
if (inventory.isInUse()) {
getServer().getScheduler().runTask(this, () -> ejectViewers(inventory, viewer -> true));
}
}
if (this.enderChests.containsKey(key)) {
this.enderChests.get(key).setPlayerOffline();
}
// Schedule task to check in use status later this tick. Closing user is still in viewer list.
getServer().getScheduler().runTask(this, () -> {
if (loaded.isInUse()) {
return;
}
// Re-fetch from map - prevents duplicate saves on multi-close.
ISpecialInventory current = map.remove(key);
if (!disableSaving()
&& current != null
&& current.getPlayer() instanceof Player player && !player.isOnline()) {
this.accessor.getPlayerDataManager().inject(player).saveData();
}
});
}
/**
@@ -482,78 +488,49 @@ public class OpenInv extends JavaPlugin implements IOpenInv {
* @param player the Player
* @throws IllegalStateException if the server version is unsupported
*/
public void setPlayerOnline(final Player player) {
void setPlayerOnline(@NotNull Player player) {
setPlayerOnline(inventories, player, player::updateInventory);
setPlayerOnline(enderChests, player, null);
}
String key = this.getPlayerID(player);
private void setPlayerOnline(
@NotNull Map<UUID, ? extends ISpecialInventory> map,
@NotNull Player player,
@Nullable Runnable task) {
ISpecialInventory inventory = map.get(player.getUniqueId());
// Check if the player is cached. If not, neither of their inventories is open.
if (!this.playerCache.containsKey(key)) {
if (inventory == null) {
// Inventory not open.
return;
}
this.playerCache.put(key, player);
inventory.setPlayerOnline(player);
if (this.inventories.containsKey(key)) {
this.inventories.get(key).setPlayerOnline(player);
new BukkitRunnable() {
@Override
public void run() {
if (player.isOnline()) {
player.updateInventory();
}
}
}.runTask(this);
}
// Eject viewers lacking permission.
ejectViewers(
inventory,
viewer ->
!Permissions.OPENONLINE.hasPermission(viewer)
|| !Permissions.CROSSWORLD.hasPermission(viewer)
&& !Objects.equals(viewer.getWorld(), inventory.getPlayer().getWorld()));
if (this.enderChests.containsKey(key)) {
this.enderChests.get(key).setPlayerOnline(player);
if (task != null) {
getServer().getScheduler().runTask(this, task);
}
}
@Override
public void setPlayerSilentChestStatus(@NotNull final OfflinePlayer offline, final boolean status) {
this.getConfig().set("toggles.silent-chest." + this.getPlayerID(offline), status);
this.saveConfig();
}
/**
* Displays all applicable help for OpenInv commands.
*
* @param player the Player to help
*/
public void showHelp(final Player player) {
// Get registered commands
for (String commandName : this.getDescription().getCommands().keySet()) {
PluginCommand command = this.getCommand(commandName);
// Ensure command is successfully registered and player can use it
if (command == null || !command.testPermissionSilent(player)) {
static void ejectViewers(@NotNull ISpecialInventory inventory, @NotNull Predicate<@NotNull HumanEntity> predicate) {
Inventory bukkitInventory = inventory.getBukkitInventory();
for (HumanEntity viewer : new ArrayList<>(bukkitInventory.getViewers())) {
if (viewer.getUniqueId().equals(inventory.getPlayer().getUniqueId())
&& !viewer.getOpenInventory().getTopInventory().equals(bukkitInventory)) {
// Skip owner with other inventory open. They aren't actually a viewer.
continue;
}
// Send usage
player.sendMessage(command.getUsage().replace("<command>", commandName));
List<String> aliases = command.getAliases();
if (aliases.isEmpty()) {
continue;
if (predicate.test(viewer)) {
viewer.closeInventory();
}
// Assemble alias list
StringBuilder aliasBuilder = new StringBuilder(" (aliases: ");
for (String alias : aliases) {
aliasBuilder.append(alias).append(", ");
}
aliasBuilder.delete(aliasBuilder.length() - 2, aliasBuilder.length()).append(')');
// Send all aliases
player.sendMessage(aliasBuilder.toString());
}
}
@Override
public void unload(@NotNull final OfflinePlayer offline) {
this.playerCache.invalidate(this.getPlayerID(offline));
}
}
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2011-2018 lishid. All rights reserved.
* Copyright (C) 2011-2022 lishid. All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -14,9 +14,8 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.lishid.openinv.listeners;
package com.lishid.openinv;
import com.lishid.openinv.OpenInv;
import com.lishid.openinv.util.Permissions;
import org.bukkit.entity.Player;
import org.bukkit.event.Event.Result;
@@ -28,56 +27,59 @@ import org.bukkit.event.player.PlayerChangedWorldEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.jetbrains.annotations.NotNull;
public class PlayerListener implements Listener {
record PlayerListener(OpenInv plugin) implements Listener {
private final OpenInv plugin;
public PlayerListener(OpenInv plugin) {
this.plugin = plugin;
}
@EventHandler(priority = EventPriority.MONITOR)
public void onPlayerJoin(final PlayerJoinEvent event) {
@EventHandler(priority = EventPriority.LOWEST)
private void onPlayerJoin(@NotNull PlayerJoinEvent event) {
plugin.setPlayerOnline(event.getPlayer());
}
@EventHandler(priority = EventPriority.MONITOR)
public void onPlayerQuit(PlayerQuitEvent event) {
private void onPlayerQuit(@NotNull PlayerQuitEvent event) {
plugin.setPlayerOffline(event.getPlayer());
}
@EventHandler
public void onWorldChange(PlayerChangedWorldEvent event) {
private void onWorldChange(@NotNull PlayerChangedWorldEvent event) {
plugin.changeWorld(event.getPlayer());
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onPlayerInteract(PlayerInteractEvent event) {
private void onPlayerInteract(@NotNull PlayerInteractEvent event) {
// Do not cancel 3rd party plugins' custom events
if (!PlayerInteractEvent.class.equals(event.getClass())) {
return;
}
if (event.getAction() != Action.RIGHT_CLICK_BLOCK || event.getPlayer().isSneaking()
|| event.useInteractedBlock() == Result.DENY
|| event.useInteractedBlock() == Result.DENY || event.getClickedBlock() == null
|| !plugin.getAnySilentContainer().isAnySilentContainer(event.getClickedBlock())) {
return;
}
Player player = event.getPlayer();
boolean any = Permissions.ANYCHEST.hasPermission(player) && plugin.getPlayerAnyChestStatus(player);
boolean needsAny = plugin.getAnySilentContainer().isAnyContainerNeeded(player, event.getClickedBlock());
boolean any = Permissions.ANYCHEST.hasPermission(player) && plugin.getAnyContainerStatus(player);
boolean needsAny = plugin.getAnySilentContainer().isAnyContainerNeeded(event.getClickedBlock());
if (!any && needsAny) {
return;
}
boolean silent = Permissions.SILENT.hasPermission(player) && plugin.getPlayerSilentChestStatus(player);
boolean silent = Permissions.SILENT.hasPermission(player) && plugin.getSilentContainerStatus(player);
// If anycontainer or silentcontainer is active
if ((any || silent) && plugin.getAnySilentContainer().activateContainer(player, silent, event.getClickedBlock())) {
if (silent && plugin.notifySilentChest() && needsAny && plugin.notifyAnyChest()) {
player.sendMessage("You are opening a blocked container silently.");
} else if (silent && plugin.notifySilentChest()) {
player.sendMessage("You are opening a container silently.");
} else if (needsAny && plugin.notifyAnyChest()) {
player.sendMessage("You are opening a blocked container.");
if (any || silent) {
if (plugin.getAnySilentContainer().activateContainer(player, silent, event.getClickedBlock())) {
if (silent && needsAny) {
plugin.sendSystemMessage(player, "messages.info.containerBlockedSilent");
} else if (needsAny) {
plugin.sendSystemMessage(player, "messages.info.containerBlocked");
} else if (silent) {
plugin.sendSystemMessage(player, "messages.info.containerSilent");
}
}
event.setCancelled(true);
}
@@ -1,54 +0,0 @@
/*
* Copyright (C) 2011-2018 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.commands;
import com.lishid.openinv.OpenInv;
import org.bukkit.ChatColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
public class AnyChestPluginCommand implements CommandExecutor {
private final OpenInv plugin;
public AnyChestPluginCommand(final OpenInv plugin) {
this.plugin = plugin;
}
@Override
public boolean onCommand(final CommandSender sender, final Command command, final String label, final String[] args) {
if (!(sender instanceof Player)) {
sender.sendMessage(ChatColor.RED + "You can't use this from the console.");
return true;
}
Player player = (Player) sender;
if (args.length > 0 && args[0].equalsIgnoreCase("check")) {
sender.sendMessage("AnyChest is " + (this.plugin.getPlayerAnyChestStatus(player) ? "ON" : "OFF") + ".");
return true;
}
this.plugin.setPlayerAnyChestStatus(player, !this.plugin.getPlayerAnyChestStatus(player));
sender.sendMessage("AnyChest is now " + (this.plugin.getPlayerAnyChestStatus(player) ? "ON" : "OFF") + ".");
return true;
}
}
@@ -0,0 +1,86 @@
/*
* 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.commands;
import com.lishid.openinv.OpenInv;
import com.lishid.openinv.util.TabCompleter;
import java.util.Collections;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Predicate;
import org.bukkit.OfflinePlayer;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabExecutor;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
public class ContainerSettingCommand implements TabExecutor {
private final OpenInv plugin;
public ContainerSettingCommand(final OpenInv plugin) {
this.plugin = plugin;
}
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (!(sender instanceof Player player)) {
plugin.sendMessage(sender, "messages.error.consoleUnsupported");
return true;
}
boolean any = command.getName().startsWith("any");
Predicate<Player> getSetting = any ? plugin::getAnyContainerStatus : plugin::getSilentContainerStatus;
BiConsumer<OfflinePlayer, Boolean> setSetting = any ? plugin::setAnyContainerStatus : plugin::setSilentContainerStatus;
if (args.length > 0) {
args[0] = args[0].toLowerCase();
if (args[0].equals("on")) {
setSetting.accept(player, true);
} else if (args[0].equals("off")) {
setSetting.accept(player, false);
} else if (!args[0].equals("check")) {
// Invalid argument, show usage.
return false;
}
} else {
setSetting.accept(player, !getSetting.test(player));
}
String onOff = plugin.getLocalizedMessage(player, getSetting.test(player) ? "messages.info.on" : "messages.info.off");
if (onOff == null) {
onOff = String.valueOf(getSetting.test(player));
}
plugin.sendMessage(sender, "messages.info.settingState","%setting%", any ? "AnyContainer" : "SilentContainer", "%state%", onOff);
return true;
}
@Override
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (!command.testPermissionSilent(sender) || args.length != 1) {
return Collections.emptyList();
}
return TabCompleter.completeString(args[0], new String[] {"check", "on", "off"});
}
}
@@ -0,0 +1,214 @@
/*
* 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.commands;
import com.lishid.openinv.OpenInv;
import com.lishid.openinv.internal.ISpecialInventory;
import com.lishid.openinv.util.Permissions;
import com.lishid.openinv.util.TabCompleter;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.StringJoiner;
import org.bukkit.OfflinePlayer;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.PluginCommand;
import org.bukkit.command.TabExecutor;
import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitRunnable;
import org.jetbrains.annotations.NotNull;
public class OpenInvCommand implements TabExecutor {
private final OpenInv plugin;
private final HashMap<Player, String> openInvHistory = new HashMap<>();
private final HashMap<Player, String> openEnderHistory = new HashMap<>();
public OpenInvCommand(final OpenInv plugin) {
this.plugin = plugin;
}
@Override
public boolean onCommand(@NotNull final CommandSender sender, @NotNull final Command command, @NotNull final String label, @NotNull final String[] args) {
boolean openInv = command.getName().equals("openinv");
if (openInv && args.length > 0 && (args[0].equalsIgnoreCase("help") || args[0].equals("?"))) {
this.showHelp(sender);
return true;
}
if (!(sender instanceof Player player)) {
plugin.sendMessage(sender, "messages.error.consoleUnsupported");
return true;
}
// History management
String history = (openInv ? this.openInvHistory : this.openEnderHistory).get(player);
if (history == null || history.isEmpty()) {
history = player.getName();
(openInv ? this.openInvHistory : this.openEnderHistory).put(player, history);
}
final String name;
// Read from history if target is not named
if (args.length < 1) {
name = history;
} else {
name = args[0];
}
new BukkitRunnable() {
@Override
public void run() {
final OfflinePlayer offlinePlayer = OpenInvCommand.this.plugin.matchPlayer(name);
if (offlinePlayer == null || !offlinePlayer.hasPlayedBefore() && !offlinePlayer.isOnline()) {
plugin.sendMessage(player, "messages.error.invalidPlayer");
return;
}
new BukkitRunnable() {
@Override
public void run() {
if (!player.isOnline()) {
return;
}
OpenInvCommand.this.openInventory(player, offlinePlayer, openInv);
}
}.runTask(OpenInvCommand.this.plugin);
}
}.runTaskAsynchronously(this.plugin);
return true;
}
private void showHelp(final CommandSender sender) {
// Get registered commands
for (String commandName : plugin.getDescription().getCommands().keySet()) {
PluginCommand command = plugin.getCommand(commandName);
// Ensure command is successfully registered and sender can use it
if (command == null || !command.testPermissionSilent(sender)) {
continue;
}
// Send usage
sender.sendMessage(command.getUsage().replace("<command>", commandName));
List<String> aliases = command.getAliases();
if (!aliases.isEmpty()) {
// Assemble alias list
StringJoiner aliasJoiner = new StringJoiner(", ", " (aliases: ", ")");
for (String alias : aliases) {
aliasJoiner.add(alias);
}
// Send all aliases
sender.sendMessage(aliasJoiner.toString());
}
}
}
private void openInventory(final Player player, final OfflinePlayer target, boolean openinv) {
Player onlineTarget;
boolean online = target.isOnline();
if (!online) {
if (!plugin.disableOfflineAccess() && Permissions.OPENOFFLINE.hasPermission(player)) {
// Try loading the player's data
onlineTarget = this.plugin.loadPlayer(target);
} else {
plugin.sendMessage(player, "messages.error.permissionPlayerOffline");
return;
}
} else {
if (Permissions.OPENONLINE.hasPermission(player)) {
onlineTarget = target.getPlayer();
} else {
plugin.sendMessage(player, "messages.error.permissionPlayerOnline");
return;
}
}
if (onlineTarget == null) {
plugin.sendMessage(player, "messages.error.invalidPlayer");
return;
}
// Permissions checks
if (onlineTarget.equals(player)) {
// Inventory: Additional permission required to open own inventory
if (openinv && !Permissions.OPENSELF.hasPermission(player)) {
plugin.sendMessage(player, "messages.error.permissionOpenSelf");
return;
}
} else {
// Enderchest: Additional permission required to open others' ender chests
if (!openinv && !Permissions.ENDERCHEST_ALL.hasPermission(player)) {
plugin.sendMessage(player, "messages.error.permissionEnderAll");
return;
}
// Protected check
if (!Permissions.OVERRIDE.hasPermission(player)
&& Permissions.EXEMPT.hasPermission(onlineTarget)) {
plugin.sendMessage(player, "messages.error.permissionExempt",
"%target%", onlineTarget.getDisplayName());
return;
}
// Crossworld check
if (!Permissions.CROSSWORLD.hasPermission(player)
&& !onlineTarget.getWorld().equals(player.getWorld())) {
plugin.sendMessage(player, "messages.error.permissionCrossWorld",
"%target%", onlineTarget.getDisplayName());
return;
}
}
// Record the target
(openinv ? this.openInvHistory : this.openEnderHistory).put(player, target.getUniqueId().toString());
// Create the inventory
final ISpecialInventory inv;
try {
inv = openinv ? this.plugin.getSpecialInventory(onlineTarget, online) : this.plugin.getSpecialEnderChest(onlineTarget, online);
} catch (Exception e) {
plugin.sendMessage(player, "messages.error.commandException");
e.printStackTrace();
return;
}
// Open the inventory
plugin.openInventory(player, inv);
}
@Override
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (!command.testPermissionSilent(sender) || args.length != 1) {
return Collections.emptyList();
}
return TabCompleter.completeOnlinePlayer(sender, args[0]);
}
}
@@ -1,164 +0,0 @@
/*
* Copyright (C) 2011-2018 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.commands;
import com.lishid.openinv.OpenInv;
import com.lishid.openinv.internal.ISpecialInventory;
import com.lishid.openinv.util.Permissions;
import java.util.HashMap;
import org.bukkit.ChatColor;
import org.bukkit.OfflinePlayer;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitRunnable;
public class OpenInvPluginCommand implements CommandExecutor {
private final OpenInv plugin;
private final HashMap<Player, String> openInvHistory = new HashMap<Player, String>();
private final HashMap<Player, String> openEnderHistory = new HashMap<Player, String>();
public OpenInvPluginCommand(final OpenInv plugin) {
this.plugin = plugin;
}
@Override
public boolean onCommand(final CommandSender sender, final Command command, final String label, final String[] args) {
if (!(sender instanceof Player)) {
sender.sendMessage(ChatColor.RED + "You can't use this from the console.");
return true;
}
if (args.length > 0 && args[0].equalsIgnoreCase("?")) {
this.plugin.showHelp((Player) sender);
return true;
}
final Player player = (Player) sender;
final boolean openinv = command.getName().equals("openinv");
// History management
String history = (openinv ? this.openInvHistory : this.openEnderHistory).get(player);
if (history == null || history.isEmpty()) {
history = player.getName();
(openinv ? this.openInvHistory : this.openEnderHistory).put(player, history);
}
final String name;
// Read from history if target is not named
if (args.length < 1) {
name = history;
} else {
name = args[0];
}
new BukkitRunnable() {
@Override
public void run() {
final OfflinePlayer offlinePlayer = OpenInvPluginCommand.this.plugin.matchPlayer(name);
if (offlinePlayer == null || !offlinePlayer.hasPlayedBefore() && !offlinePlayer.isOnline()) {
player.sendMessage(ChatColor.RED + "Player not found!");
return;
}
new BukkitRunnable() {
@Override
public void run() {
if (!player.isOnline()) {
return;
}
OpenInvPluginCommand.this.openInventory(player, offlinePlayer, openinv);
}
}.runTask(OpenInvPluginCommand.this.plugin);
}
}.runTaskAsynchronously(this.plugin);
return true;
}
private void openInventory(final Player player, final OfflinePlayer target, boolean openinv) {
Player onlineTarget;
boolean online = target.isOnline();
if (!online) {
// Try loading the player's data
onlineTarget = this.plugin.loadPlayer(target);
if (onlineTarget == null) {
player.sendMessage(ChatColor.RED + "Player not found!");
return;
}
} else {
onlineTarget = target.getPlayer();
}
// Permissions checks
if (onlineTarget.equals(player)) {
// Inventory: Additional permission required to open own inventory
if (openinv && !Permissions.OPENSELF.hasPermission(player)) {
player.sendMessage(ChatColor.RED + "You're not allowed to open your own inventory!");
return;
}
} else {
// Enderchest: Additional permission required to open others' ender chests
if (!openinv && !Permissions.ENDERCHEST_ALL.hasPermission(player)) {
player.sendMessage(ChatColor.RED + "You do not have permission to access other players' ender chests.");
return;
}
// Protected check
if (!Permissions.OVERRIDE.hasPermission(player)
&& Permissions.EXEMPT.hasPermission(onlineTarget)) {
player.sendMessage(ChatColor.RED + onlineTarget.getDisplayName() + "'s inventory is protected!");
return;
}
// Crossworld check
if (!Permissions.CROSSWORLD.hasPermission(player)
&& !onlineTarget.getWorld().equals(player.getWorld())) {
player.sendMessage(
ChatColor.RED + onlineTarget.getDisplayName() + " is not in your world!");
return;
}
}
// Record the target
(openinv ? this.openInvHistory : this.openEnderHistory).put(player, this.plugin.getPlayerID(target));
// Create the inventory
final ISpecialInventory inv;
try {
inv = openinv ? this.plugin.getSpecialInventory(onlineTarget, online) : this.plugin.getSpecialEnderChest(onlineTarget, online);
} catch (Exception e) {
player.sendMessage(ChatColor.RED + "An error occurred creating " + onlineTarget.getDisplayName() + "'s inventory!");
e.printStackTrace();
return;
}
// Open the inventory
plugin.openInventory(player, inv);
}
}
@@ -0,0 +1,127 @@
/*
* 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.commands;
import com.lishid.openinv.OpenInv;
import com.lishid.openinv.util.TabCompleter;
import java.util.Collections;
import java.util.List;
import org.bukkit.Chunk;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.BlockState;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabExecutor;
import org.bukkit.entity.Player;
import org.bukkit.inventory.InventoryHolder;
import org.jetbrains.annotations.NotNull;
/**
* Command for searching containers in a radius of chunks.
*/
public class SearchContainerCommand implements TabExecutor {
private final OpenInv plugin;
public SearchContainerCommand(OpenInv plugin) {
this.plugin = plugin;
}
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (!(sender instanceof Player senderPlayer)) {
plugin.sendMessage(sender, "messages.error.consoleUnsupported");
return true;
}
if (args.length < 1) {
// Must supply material
return false;
}
Material material = Material.getMaterial(args[0].toUpperCase());
if (material == null) {
plugin.sendMessage(sender, "messages.error.invalidMaterial", "%target%", args[0]);
return false;
}
int radius = 5;
if (args.length > 1) {
try {
radius = Integer.parseInt(args[1]);
} catch (NumberFormatException e) {
// Invalid radius supplied
return false;
}
}
World world = senderPlayer.getWorld();
Chunk centerChunk = senderPlayer.getLocation().getChunk();
StringBuilder locations = new StringBuilder();
for (int dX = -radius; dX <= radius; ++dX) {
for (int dZ = -radius; dZ <= radius; ++dZ) {
if (!world.loadChunk(centerChunk.getX() + dX, centerChunk.getZ() + dZ, false)) {
continue;
}
Chunk chunk = world.getChunkAt(centerChunk.getX() + dX, centerChunk.getZ() + dZ);
for (BlockState tileEntity : chunk.getTileEntities()) {
if (!(tileEntity instanceof InventoryHolder holder)) {
continue;
}
if (!holder.getInventory().contains(material)) {
continue;
}
locations.append(holder.getInventory().getType().name().toLowerCase()).append(" (")
.append(tileEntity.getX()).append(',').append(tileEntity.getY()).append(',')
.append(tileEntity.getZ()).append("), ");
}
}
}
// Matches found, delete trailing comma and space
if (locations.length() > 0) {
locations.delete(locations.length() - 2, locations.length());
} else {
plugin.sendMessage(sender, "messages.info.container.noMatches",
"%target%", material.name());
return true;
}
plugin.sendMessage(sender, "messages.info.container.matches",
"%target%", material.name(), "%detail%", locations.toString());
return true;
}
@Override
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] args) {
if (args.length < 1 || args.length > 2 || !command.testPermissionSilent(sender)) {
return Collections.emptyList();
}
String argument = args[args.length - 1];
if (args.length == 1) {
return TabCompleter.completeEnum(argument, Material.class);
} else {
return TabCompleter.completeInteger(argument);
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2011-2018 lishid. All rights reserved.
* Copyright (C) 2011-2022 lishid. All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -17,15 +17,21 @@
package com.lishid.openinv.commands;
import com.lishid.openinv.OpenInv;
import com.lishid.openinv.util.TabCompleter;
import java.util.Collections;
import java.util.List;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabExecutor;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Command adding the ability to search online players' inventories for enchantments of a specific
@@ -33,16 +39,16 @@ import org.bukkit.inventory.meta.ItemMeta;
*
* @author Jikoo
*/
public class SearchEnchantPluginCommand implements CommandExecutor {
public class SearchEnchantCommand implements TabExecutor {
private final OpenInv plugin;
public SearchEnchantPluginCommand(OpenInv plugin) {
public SearchEnchantCommand(OpenInv plugin) {
this.plugin = plugin;
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (args.length == 0) {
return false;
}
@@ -51,14 +57,28 @@ public class SearchEnchantPluginCommand implements CommandExecutor {
int level = 0;
for (String argument : args) {
Enchantment localEnchant = Enchantment.getByName(argument.toUpperCase());
if (localEnchant != null) {
enchant = localEnchant;
continue;
}
try {
level = Integer.parseInt(argument);
continue;
} catch (NumberFormatException ignored) {}
argument = argument.toLowerCase();
int colon = argument.indexOf(':');
NamespacedKey key;
try {
if (colon > -1 && colon < argument.length() - 1) {
key = new NamespacedKey(argument.substring(0, colon), argument.substring(colon + 1));
} else {
key = NamespacedKey.minecraft(argument);
}
} catch (IllegalArgumentException ignored) {
continue;
}
Enchantment localEnchant = Enchantment.getByKey(key);
if (localEnchant != null) {
enchant = localEnchant;
}
}
// Arguments not set correctly
@@ -94,16 +114,18 @@ public class SearchEnchantPluginCommand implements CommandExecutor {
// Matches found, delete trailing comma and space
players.delete(players.length() - 2, players.length());
} else {
sender.sendMessage("No players found with " + (enchant == null ? "any enchant" : enchant.getName())
+ " of level " + level + " or higher.");
plugin.sendMessage(sender, "messages.info.player.noMatches",
"%target%", (enchant != null ? enchant.getKey().toString() : "") + " >= " + level);
return true;
}
sender.sendMessage("Players: " + players.toString());
plugin.sendMessage(sender, "messages.info.player.matches",
"%target%", (enchant != null ? enchant.getKey().toString() : "") + " >= " + level,
"%detail%", players.toString());
return true;
}
private boolean containsEnchantment(Inventory inventory, Enchantment enchant, int minLevel) {
private boolean containsEnchantment(Inventory inventory, @Nullable Enchantment enchant, int minLevel) {
for (ItemStack item : inventory.getContents()) {
if (item == null || item.getType() == Material.AIR) {
continue;
@@ -117,7 +139,7 @@ public class SearchEnchantPluginCommand implements CommandExecutor {
continue;
}
ItemMeta meta = item.getItemMeta();
if (!meta.hasEnchants()) {
if (meta == null || !meta.hasEnchants()) {
continue;
}
for (int enchLevel : meta.getEnchants().values()) {
@@ -130,4 +152,17 @@ public class SearchEnchantPluginCommand implements CommandExecutor {
return false;
}
@Override
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (!command.testPermissionSilent(sender) || args.length < 1 || args.length > 2) {
return Collections.emptyList();
}
if (args.length == 1) {
return TabCompleter.completeObject(args[0], enchantment -> enchantment.getKey().toString(), Enchantment.values());
} else {
return TabCompleter.completeInteger(args[1]);
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2011-2018 lishid. All rights reserved.
* Copyright (C) 2011-2021 lishid. All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -17,50 +17,54 @@
package com.lishid.openinv.commands;
import com.lishid.openinv.OpenInv;
import org.bukkit.ChatColor;
import com.lishid.openinv.util.TabCompleter;
import java.util.Collections;
import java.util.List;
import org.bukkit.Material;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabExecutor;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import org.jetbrains.annotations.NotNull;
public class SearchInvPluginCommand implements CommandExecutor {
public class SearchInvCommand implements TabExecutor {
private final OpenInv plugin;
public SearchInvPluginCommand(OpenInv plugin) {
public SearchInvCommand(OpenInv plugin) {
this.plugin = plugin;
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
Material material = null;
int count = 1;
if (args.length >= 1) {
String[] gData = args[0].split(":");
material = Material.matchMaterial(gData[0]);
material = Material.getMaterial(args[0].toUpperCase());
}
if (material == null) {
plugin.sendMessage(sender, "messages.error.invalidMaterial", "%target%", args.length > 0 ? args[0] : "null");
return false;
}
int count = 1;
if (args.length >= 2) {
try {
count = Integer.parseInt(args[1]);
} catch (NumberFormatException ex) {
sender.sendMessage(ChatColor.RED + "'" + args[1] + "' is not a number!");
plugin.sendMessage(sender, "messages.error.invalidNumber", "%target%", args[1]);
return false;
}
}
if (material == null) {
sender.sendMessage(ChatColor.RED + "Unknown item");
return false;
}
StringBuilder players = new StringBuilder();
boolean searchInv = command.getName().equals("searchinv");
for (Player player : plugin.getServer().getOnlinePlayers()) {
Inventory inventory = command.getName().equals("searchinv") ? player.getInventory() : player.getEnderChest();
Inventory inventory = searchInv ? player.getInventory() : player.getEnderChest();
if (inventory.contains(material, count)) {
players.append(player.getName()).append(", ");
}
@@ -70,11 +74,28 @@ public class SearchInvPluginCommand implements CommandExecutor {
if (players.length() > 0) {
players.delete(players.length() - 2, players.length());
} else {
sender.sendMessage("No players found with " + material.toString());
plugin.sendMessage(sender, "messages.info.player.noMatches",
"%target%", material.name());
return true;
}
sender.sendMessage("Players with the item " + material.toString() + ": " + players.toString());
plugin.sendMessage(sender, "messages.info.player.matches",
"%target%", material.name(), "%detail%", players.toString());
return true;
}
@Override
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (args.length < 1 || args.length > 2 || !command.testPermissionSilent(sender)) {
return Collections.emptyList();
}
String argument = args[args.length - 1];
if (args.length == 1) {
return TabCompleter.completeEnum(argument, Material.class);
} else {
return TabCompleter.completeInteger(argument);
}
}
}
@@ -1,52 +0,0 @@
/*
* Copyright (C) 2011-2018 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.commands;
import com.lishid.openinv.OpenInv;
import org.bukkit.ChatColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
public class SilentChestPluginCommand implements CommandExecutor {
private final OpenInv plugin;
public SilentChestPluginCommand(OpenInv plugin) {
this.plugin = plugin;
}
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (!(sender instanceof Player)) {
sender.sendMessage(ChatColor.RED + "You can't use this from the console.");
return true;
}
Player player = (Player) sender;
if (args.length > 0 && args[0].equalsIgnoreCase("check")) {
sender.sendMessage("SilentChest is " + (plugin.getPlayerAnyChestStatus(player) ? "ON" : "OFF") + ".");
return true;
}
plugin.setPlayerSilentChestStatus(player, !plugin.getPlayerSilentChestStatus(player));
sender.sendMessage("SilentChest is now " + (plugin.getPlayerSilentChestStatus(player) ? "ON" : "OFF") + ".");
return true;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2011-2018 lishid. All rights reserved.
* Copyright (C) 2011-2021 lishid. All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -34,6 +34,13 @@ public interface IPlayerDataManager {
*/
@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);
/**
* Opens an ISpecialInventory for a Player.
*
@@ -42,6 +49,18 @@ public interface IPlayerDataManager {
*`
* @return the InventoryView opened
*/
InventoryView openInventory(@NotNull Player player, @NotNull ISpecialInventory inventory);
@Nullable InventoryView openInventory(@NotNull Player player, @NotNull ISpecialInventory inventory);
/**
* Convert a raw slot number into a player inventory slot number.
*
* <p>Note that this method is specifically for converting an ISpecialPlayerInventory slot number into a regular
* player inventory slot number.
*
* @param view the open inventory view
* @param rawSlot the raw slot in the view
* @return the converted slot number
*/
int convertToPlayerSlot(InventoryView view, int rawSlot);
}
@@ -0,0 +1,80 @@
/*
* 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.internal;
import com.lishid.openinv.OpenInv;
import java.util.Objects;
import org.bukkit.entity.HumanEntity;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.InventoryType;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryView;
import org.jetbrains.annotations.NotNull;
public class OpenInventoryView extends InventoryView {
private final Player player;
private final ISpecialInventory inventory;
private final String titleKey;
private final String titleDefaultSuffix;
private String title;
public OpenInventoryView(Player player, ISpecialInventory inventory, String titleKey, String titleDefaultSuffix) {
this.player = player;
this.inventory = inventory;
this.titleKey = titleKey;
this.titleDefaultSuffix = titleDefaultSuffix;
}
@Override
public @NotNull Inventory getTopInventory() {
return inventory.getBukkitInventory();
}
@Override
public @NotNull Inventory getBottomInventory() {
return getPlayer().getInventory();
}
@Override
public @NotNull HumanEntity getPlayer() {
return player;
}
@Override
public @NotNull InventoryType getType() {
return inventory.getBukkitInventory().getType();
}
@Override
public @NotNull String getTitle() {
if (title == null) {
HumanEntity owner = inventory.getPlayer();
String localTitle = OpenInv.getPlugin(OpenInv.class)
.getLocalizedMessage(
owner,
titleKey,
"%player%",
owner.getName());
title = Objects.requireNonNullElseGet(localTitle, () -> owner.getName() + titleDefaultSuffix);
}
return title;
}
}
@@ -1,40 +0,0 @@
/*
* Copyright (C) 2011-2018 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.listeners;
import com.lishid.openinv.util.InventoryAccess;
import com.lishid.openinv.util.Permissions;
import org.bukkit.entity.HumanEntity;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.inventory.Inventory;
public class InventoryClickListener implements Listener {
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void onInventoryClick(InventoryClickEvent event) {
HumanEntity entity = event.getWhoClicked();
Inventory inventory = event.getInventory();
if (InventoryAccess.isPlayerInventory(inventory) && !Permissions.EDITINV.hasPermission(entity)
|| InventoryAccess.isEnderChest(inventory) && !Permissions.EDITENDER.hasPermission(entity)) {
event.setCancelled(true);
}
}
}
@@ -1,46 +0,0 @@
/*
* Copyright (C) 2011-2018 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.listeners;
import com.lishid.openinv.IOpenInv;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.inventory.InventoryCloseEvent;
public class InventoryCloseListener implements Listener {
private final IOpenInv plugin;
public InventoryCloseListener(final IOpenInv plugin) {
this.plugin = plugin;
}
@EventHandler
public void onInventoryClose(final InventoryCloseEvent event) {
if (!(event.getPlayer() instanceof Player)) {
return;
}
Player player = (Player) event.getPlayer();
if (this.plugin.getPlayerSilentChestStatus(player)) {
this.plugin.getAnySilentContainer().deactivateContainer(player);
}
}
}
@@ -1,45 +0,0 @@
/*
* Copyright (C) 2011-2019 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.listeners;
import com.lishid.openinv.util.InventoryAccess;
import com.lishid.openinv.util.Permissions;
import org.bukkit.entity.HumanEntity;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.inventory.InventoryDragEvent;
import org.bukkit.inventory.Inventory;
/**
* Listener for InventoryDragEvents to prevent unpermitted modification of special inventories.
*
* @author Jikoo
*/
public class InventoryDragListener implements Listener {
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void onInventoryDrag(InventoryDragEvent event) {
HumanEntity entity = event.getWhoClicked();
Inventory inventory = event.getInventory();
if (InventoryAccess.isPlayerInventory(inventory) && !Permissions.EDITINV.hasPermission(entity)
|| InventoryAccess.isEnderChest(inventory) && !Permissions.EDITENDER.hasPermission(entity)) {
event.setCancelled(true);
}
}
}
@@ -1,42 +0,0 @@
/*
* Copyright (C) 2011-2018 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.listeners;
import com.lishid.openinv.OpenInv;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.server.PluginDisableEvent;
/**
* Listener for plugin-related events.
*
* @author Jikoo
*/
public class PluginListener implements Listener {
private final OpenInv plugin;
public PluginListener(OpenInv plugin) {
this.plugin = plugin;
}
@EventHandler
public void onPluginDisable(PluginDisableEvent event) {
plugin.releaseAllPlayers(event.getPlugin());
}
}
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2011-2018 lishid. All rights reserved.
* Copyright (C) 2011-2022 lishid. All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -24,19 +24,13 @@ import java.util.Map;
import java.util.Set;
import org.bukkit.OfflinePlayer;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.scheduler.BukkitRunnable;
public class ConfigUpdater {
private final OpenInv plugin;
public ConfigUpdater(OpenInv plugin) {
this.plugin = plugin;
}
public record ConfigUpdater(OpenInv plugin) {
public void checkForUpdates() {
final int version = plugin.getConfig().getInt("config-version", 1);
if (version >= plugin.getConfig().getDefaults().getInt("config-version")) {
ConfigurationSection defaults = plugin.getConfig().getDefaults();
if (defaults == null || version >= defaults.getInt("config-version")) {
return;
}
@@ -50,73 +44,82 @@ public class ConfigUpdater {
plugin.getLogger().warning("Could not back up config.yml before updating!");
}
new BukkitRunnable() {
@Override
public void run() {
if (version < 2) {
updateConfig1To2();
}
if (version < 3) {
updateConfig2To3();
}
new BukkitRunnable() {
@Override
public void run() {
plugin.saveConfig();
plugin.getLogger().info("Configuration update complete!");
}
}.runTaskLater(plugin, 1L); // Run on 1 tick delay; on older versions Bukkit's scheduler is not guaranteed FIFO
plugin.getServer().getScheduler().runTaskAsynchronously(plugin, () -> {
if (version < 2) {
updateConfig1To2();
}
}.runTaskAsynchronously(plugin);
if (version < 3) {
updateConfig2To3();
}
if (version < 4) {
updateConfig3To4();
}
if (version < 5) {
updateConfig4To5();
}
plugin.getServer().getScheduler().runTask(plugin, () -> {
plugin.saveConfig();
plugin.getLogger().info("Configuration update complete!");
});
});
}
private void updateConfig4To5() {
plugin.getServer().getScheduler().runTask(plugin, () -> {
plugin.getConfig().set("settings.disable-offline-access", false);
plugin.getConfig().set("config-version", 5);
});
}
private void updateConfig3To4() {
plugin.getServer().getScheduler().runTask(plugin, () -> {
plugin.getConfig().set("notify", null);
plugin.getConfig().set("settings.locale", "en_US");
plugin.getConfig().set("config-version", 4);
});
}
private void updateConfig2To3() {
new BukkitRunnable() {
@Override
public void run() {
plugin.getConfig().set("config-version", 3);
plugin.getConfig().set("items.open-inv", null);
plugin.getConfig().set("ItemOpenInv", null);
plugin.getConfig().set("toggles.items.open-inv", null);
plugin.getConfig().set("settings.disable-saving",
plugin.getConfig().getBoolean("DisableSaving", false));
plugin.getConfig().set("DisableSaving", null);
}
}.runTask(plugin);
plugin.getServer().getScheduler().runTask(plugin, () -> {
plugin.getConfig().set("config-version", 3);
plugin.getConfig().set("items.open-inv", null);
plugin.getConfig().set("ItemOpenInv", null);
plugin.getConfig().set("toggles.items.open-inv", null);
plugin.getConfig().set("settings.disable-saving",
plugin.getConfig().getBoolean("DisableSaving", false));
plugin.getConfig().set("DisableSaving", null);
});
}
private void updateConfig1To2() {
new BukkitRunnable() {
@Override
public void run() {
// Get the old config settings
boolean notifySilentChest = plugin.getConfig().getBoolean("NotifySilentChest", true);
boolean notifyAnyChest = plugin.getConfig().getBoolean("NotifyAnyChest", true);
plugin.getConfig().set("ItemOpenInvItemID", null);
plugin.getConfig().set("NotifySilentChest", null);
plugin.getConfig().set("NotifyAnyChest", null);
plugin.getConfig().set("config-version", 2);
plugin.getConfig().set("notify.any-chest", notifyAnyChest);
plugin.getConfig().set("notify.silent-chest", notifySilentChest);
}
}.runTask(plugin);
plugin.getServer().getScheduler().runTask(plugin, () -> {
// Get the old config settings
boolean notifySilentChest = plugin.getConfig().getBoolean("NotifySilentChest", true);
boolean notifyAnyChest = plugin.getConfig().getBoolean("NotifyAnyChest", true);
plugin.getConfig().set("ItemOpenInvItemID", null);
plugin.getConfig().set("NotifySilentChest", null);
plugin.getConfig().set("NotifyAnyChest", null);
plugin.getConfig().set("config-version", 2);
plugin.getConfig().set("notify.any-chest", notifyAnyChest);
plugin.getConfig().set("notify.silent-chest", notifySilentChest);
});
updateToggles("AnyChest", "toggles.any-chest");
updateToggles("SilentChest", "toggles.silent-chest");
}
private void updateToggles(final String sectionName, final String newSectionName) {
ConfigurationSection section = plugin.getConfig().getConfigurationSection(sectionName);
// Ensure section exists
if (!plugin.getConfig().isConfigurationSection(sectionName)) {
if (section == null) {
return;
}
ConfigurationSection section = plugin.getConfig().getConfigurationSection(sectionName);
Set<String> keys = section.getKeys(false);
// Ensure section has content
if (keys == null || keys.isEmpty()) {
if (keys.isEmpty()) {
return;
}
@@ -125,29 +128,24 @@ public class ConfigUpdater {
for (String playerName : keys) {
OfflinePlayer player = plugin.matchPlayer(playerName);
if (player != null) {
toggles.put(plugin.getPlayerID(player), section.getBoolean(playerName + ".toggle", false));
toggles.put(player.getUniqueId().toString(), section.getBoolean(playerName + ".toggle", false));
}
}
new BukkitRunnable() {
@Override
public void run() {
// Wipe old ConfigurationSection
plugin.getConfig().set(sectionName, null);
plugin.getServer().getScheduler().runTask(plugin, () -> {
// Wipe old ConfigurationSection
plugin.getConfig().set(sectionName, null);
// Prepare new ConfigurationSection
ConfigurationSection newSection;
if (plugin.getConfig().isConfigurationSection(newSectionName)) {
newSection = plugin.getConfig().getConfigurationSection(newSectionName);
} else {
newSection = plugin.getConfig().createSection(newSectionName);
}
// Set new values
for (Map.Entry<String, Boolean> entry : toggles.entrySet()) {
newSection.set(entry.getKey(), entry.getValue());
}
// Prepare new ConfigurationSection
ConfigurationSection newSection = plugin.getConfig().getConfigurationSection(newSectionName);
if (newSection == null) {
newSection = plugin.getConfig().createSection(newSectionName);
}
}.runTask(plugin);
// Set new values
for (Map.Entry<String, Boolean> entry : toggles.entrySet()) {
newSection.set(entry.getKey(), entry.getValue());
}
});
}
}
@@ -0,0 +1,168 @@
/*
* 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 com.lishid.openinv.OpenInv;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import org.bukkit.ChatColor;
import org.bukkit.configuration.file.YamlConfiguration;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* A simple language manager supporting both custom and bundled languages.
*
* @author Jikoo
*/
public class LanguageManager {
private final OpenInv plugin;
private final String defaultLocale;
private final Map<String, YamlConfiguration> locales;
public LanguageManager(@NotNull OpenInv plugin, @NotNull String defaultLocale) {
this.plugin = plugin;
this.defaultLocale = defaultLocale;
this.locales = new HashMap<>();
getOrLoadLocale(defaultLocale);
}
private @NotNull YamlConfiguration getOrLoadLocale(@NotNull String locale) {
YamlConfiguration loaded = locales.get(locale);
if (loaded != null) {
return loaded;
}
InputStream resourceStream = plugin.getResource("locale/" + locale + ".yml");
YamlConfiguration localeConfigDefaults;
if (resourceStream == null) {
localeConfigDefaults = new YamlConfiguration();
} else {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceStream))) {
localeConfigDefaults = YamlConfiguration.loadConfiguration(reader);
} catch (IOException e) {
plugin.getLogger().log(Level.WARNING, "[LanguageManager] Unable to load resource " + locale + ".yml", e);
localeConfigDefaults = new YamlConfiguration();
}
}
File file = new File(plugin.getDataFolder(), locale + ".yml");
YamlConfiguration localeConfig;
if (!file.exists()) {
localeConfig = localeConfigDefaults;
try {
localeConfigDefaults.save(file);
} catch (IOException e) {
plugin.getLogger().log(Level.WARNING, "[LanguageManager] Unable to save resource " + locale + ".yml", e);
}
} else {
localeConfig = YamlConfiguration.loadConfiguration(file);
// Add new language keys
List<String> newKeys = new ArrayList<>();
for (String key : localeConfigDefaults.getKeys(true)) {
if (localeConfigDefaults.isConfigurationSection(key)) {
continue;
}
if (localeConfig.isSet(key)) {
continue;
}
localeConfig.set(key, localeConfigDefaults.get(key));
newKeys.add(key);
}
if (!newKeys.isEmpty()) {
plugin.getLogger().info("[LanguageManager] Added new language keys: " + String.join(", ", newKeys));
try {
localeConfig.save(file);
} catch (IOException e) {
plugin.getLogger().log(Level.WARNING, "[LanguageManager] Unable to save resource " + locale + ".yml", e);
}
}
}
if (!locale.equals(defaultLocale)) {
localeConfigDefaults = locales.get(defaultLocale);
// Check for missing keys
List<String> newKeys = new ArrayList<>();
for (String key : localeConfigDefaults.getKeys(true)) {
if (localeConfigDefaults.isConfigurationSection(key)) {
continue;
}
if (localeConfig.isSet(key)) {
continue;
}
newKeys.add(key);
}
if (!newKeys.isEmpty()) {
plugin.getLogger().info("[LanguageManager] Missing translations from " + locale + ".yml: " + String.join(", ", newKeys));
}
// Fall through to default locale
localeConfig.setDefaults(localeConfigDefaults);
}
locales.put(locale, localeConfig);
return localeConfig;
}
public @Nullable String getValue(@NotNull String key, @Nullable String locale) {
String value = getOrLoadLocale(locale == null ? defaultLocale : locale.toLowerCase()).getString(key);
if (value == null || value.isEmpty()) {
return null;
}
value = ChatColor.translateAlternateColorCodes('&', value);
return value;
}
public @Nullable String getValue(@NotNull String key, @Nullable String locale, String @NotNull ... replacements) {
if (replacements.length % 2 != 0) {
plugin.getLogger().log(Level.WARNING, "[LanguageManager] Replacement data is uneven", new Exception());
}
String value = getValue(key, locale);
if (value == null) {
return null;
}
for (int i = 0; i < replacements.length; i += 2) {
value = value.replace(replacements[i], replacements[i + 1]);
}
return value;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2011-2018 lishid. All rights reserved.
* Copyright (C) 2011-2022 lishid. All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -17,6 +17,7 @@
package com.lishid.openinv.util;
import org.bukkit.permissions.Permissible;
import org.jetbrains.annotations.NotNull;
public enum Permissions {
@@ -25,26 +26,35 @@ public enum Permissions {
EXEMPT("exempt"),
CROSSWORLD("crossworld"),
SILENT("silent"),
SILENT_DEFAULT("silent.default"),
SILENT_DEFAULT("silent.default", true),
ANYCHEST("anychest"),
ANY_DEFAULT("any.default"),
ANY_DEFAULT("any.default", true),
ENDERCHEST("openender"),
ENDERCHEST_ALL("openenderall"),
SEARCH("search"),
EDITINV("editinv"),
EDITENDER("editender"),
OPENSELF("openself");
OPENSELF("openself"),
OPENONLINE("openonline"),
OPENOFFLINE("openoffline"),
SPECTATE("spectate");
private final String permission;
private final boolean uninheritable;
Permissions(String permission) {
this.permission = "OpenInv." + permission;
this(permission, false);
}
public boolean hasPermission(Permissible permissible) {
Permissions(String permission, boolean uninheritable) {
this.permission = "OpenInv." + permission;
this.uninheritable = uninheritable;
}
public boolean hasPermission(@NotNull Permissible permissible) {
boolean hasPermission = permissible.hasPermission(permission);
if (hasPermission || permissible.isPermissionSet(permission)) {
if (uninheritable || hasPermission || permissible.isPermissionSet(permission)) {
return hasPermission;
}
@@ -0,0 +1,147 @@
/*
* Copyright (C) 2011-2021 lishid. All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.lishid.openinv.util;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.function.Function;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.util.StringUtil;
/**
* Utility class for common tab completions.
*/
public class TabCompleter {
/**
* Offer tab completions for whole numbers.
*
* @param argument the argument to complete
* @return integer options
*/
public static List<String> completeInteger(String argument) {
// Ensure existing argument is actually a number
if (!argument.isEmpty()) {
try {
Integer.parseInt(argument);
} catch (NumberFormatException e) {
return Collections.emptyList();
}
}
List<String> completions = new ArrayList<>(10);
for (int i = 0; i < 10; ++i) {
completions.add(argument + i);
}
return completions;
}
/**
* Offer tab completions for a given Enum.
*
* @param argument the argument to complete
* @param enumClazz the Enum to complete for
* @return the matching Enum values
*/
public static List<String> completeEnum(String argument, Class<? extends Enum<?>> enumClazz) {
argument = argument.toLowerCase(Locale.ENGLISH);
List<String> completions = new ArrayList<>();
for (Enum<?> enumConstant : enumClazz.getEnumConstants()) {
String name = enumConstant.name().toLowerCase();
if (name.startsWith(argument)) {
completions.add(name);
}
}
return completions;
}
/**
* Offer tab completions for a given array of Strings.
*
* @param argument the argument to complete
* @param options the Strings which may be completed
* @return the matching Strings
*/
public static List<String> completeString(String argument, String[] options) {
argument = argument.toLowerCase(Locale.ENGLISH);
List<String> completions = new ArrayList<>();
for (String option : options) {
if (option.startsWith(argument)) {
completions.add(option);
}
}
return completions;
}
/**
* Offer tab completions for visible online Players' names.
*
* @param sender the command's sender
* @param argument the argument to complete
* @return the matching Players' names
*/
public static List<String> completeOnlinePlayer(CommandSender sender, String argument) {
List<String> completions = new ArrayList<>();
Player senderPlayer = sender instanceof Player ? (Player) sender : null;
for (Player player : Bukkit.getOnlinePlayers()) {
if (senderPlayer != null && !senderPlayer.canSee(player)) {
continue;
}
if (StringUtil.startsWithIgnoreCase(player.getName(), argument)) {
completions.add(player.getName());
}
}
return completions;
}
/**
* Offer tab completions for a given array of Objects.
*
* @param argument the argument to complete
* @param converter the Function for converting the Object into a comparable String
* @param options the Objects which may be completed
* @return the matching Strings
*/
public static <T> List<String> completeObject(String argument, Function<T, String> converter, T[] options) {
argument = argument.toLowerCase(Locale.ENGLISH);
List<String> completions = new ArrayList<>();
for (T option : options) {
String optionString = converter.apply(option).toLowerCase();
if (optionString.startsWith(argument)) {
completions.add(optionString);
}
}
return completions;
}
private TabCompleter() {}
}
+3 -4
View File
@@ -1,6 +1,5 @@
config-version: 3
notify:
any-chest: true
silent-chest: true
config-version: 5
settings:
disable-offline-access: false
disable-saving: false
locale: 'en_us'
@@ -0,0 +1,30 @@
messages:
error:
consoleUnsupported: 'Du kannst diesen Befehl nicht von der Konsole ausführen.'
lootNotGenerated: '&cBeute noch nicht erstellt! Bitte schalte &b/silentcontainer&c aus.'
invalidMaterial: '&cUngültiges Material: "%target%"'
invalidNumber: '&cUngültige Nummer: "%target%"'
invalidPlayer: '&cSpieler nicht gefunden.'
permissionOpenSelf: '&cDu darfst dein eigenes Inventar nicht öffnen.'
permissionEnderAll: '&cDu darfst die Endertruhen von anderen Spielern nicht öffnen.'
permissionExempt: '&c%target%''s Inventar ist geschützt.'
permissionCrossWorld: '&c%target% ist nicht in der selben Welt wie du.'
permissionPlayerOnline: '&cDu darfst keine Inventare von Spielern öffnen, die online sind.'
permissionPlayerOffline: '&cDu darfst keine Inventare von Spielern öffnen, die offline sind.'
commandException: '&cAn error occurred. Please check console for details.'
info:
containerBlocked: 'Du öffnest einen gesperrten Container.'
containerBlockedSilent: 'Du öffnest einen gesperrten Container lautlos.'
containerSilent: 'Du öffnest einen Container lautlos.'
settingState: '%setting%: %state%'
player:
noMatches: 'Keine Spieler mit %target% gefunden.'
matches: 'Spieler hält %target%: %detail%'
container:
noMatches: 'Keine Container mit %target% gefunden.'
matches: 'Container hat %target%: %detail%'
on: 'an'
off: 'aus'
container:
player: '%player%''s Inventar'
enderchest: '%player%''s Endertruhe'
@@ -0,0 +1,30 @@
messages:
error:
consoleUnsupported: 'You cannot use this command from console.'
lootNotGenerated: '&cLoot not generated! Please disable &b/silentcontainer&c.'
invalidMaterial: '&cInvalid material: "%target%"'
invalidNumber: '&cInvalid number: "%target%"'
invalidPlayer: '&cPlayer not found!'
permissionOpenSelf: '&cYou''re not allowed to open your own inventory.'
permissionEnderAll: '&cYou''re not allowed to access other players'' ender chests.'
permissionExempt: '&c%target%''s inventory is protected.'
permissionCrossWorld: '&c%target% is not in your world.'
permissionPlayerOnline: '&cYou''re not allowed to open the inventory of online players.'
permissionPlayerOffline: '&cYou''re not allowed to open the inventory of offline players.'
commandException: '&cAn error occurred. Please check console for details.'
info:
containerBlocked: 'You are opening a blocked container.'
containerBlockedSilent: 'You are opening a blocked container silently.'
containerSilent: 'You are opening a container silently.'
settingState: '%setting%: %state%'
player:
noMatches: 'No players found with %target%.'
matches: 'Players holding %target%: %detail%'
container:
noMatches: 'No containers found with %target%.'
matches: 'Containers holding %target%: %detail%'
on: 'on'
off: 'off'
container:
player: '%player%''s Inventory'
enderchest: '%player%''s Ender Chest'
@@ -0,0 +1,31 @@
messages:
error:
consoleUnsupported: 'No puedes usar este comando desde la consola.'
lootNotGenerated: '&c¡Botín no generado! Desactiva &b/silentcontainer&c.'
invalidMaterial: '&cMaterial no válido: "%target%"'
invalidNumber: '&cNúmero no válido: "%target%"'
invalidPlayer: '&cJugador no encontrado'
permissionOpenSelf: '&cNo tienes permisos para abrir tu inventario de esa forma.'
permissionEnderAll: '&cNo tienes permiso para abrir cofres de ender de otros jugadores.'
permissionExempt: '&c El inventario de %target% está protegido.'
permissionCrossWorld: '&c%target% no está en tu dimensión.'
permissionPlayerOnline: '&cNo estás autorizado para abrir inventarios de jugadores conectados.'
permissionPlayerOffline: '&cNo estás autorizado para abrir inventarios de jugadores desconectados.'
commandException: '&cSe ha producido un error, lee la consola para mas información.'
info:
containerBlocked: 'Estás abriendo un contenedor protegido.'
containerBlockedSilent: 'Estás abriendo sigilosamente un contenedor protegido.'
containerSilent: 'Estás abriendo un contenedor sigilosamente.'
settingState: '%setting%: %state%'
player:
noMatches: 'No se encontraron jugadores con %target%.'
matches: 'Jugadores con %target%: %detail%'
container:
noMatches: 'No se encontraron contenedores con %target%.'
matches: 'Contenedores con %target%: %detail%'
on: 'activado'
off: 'desactivado'
container:
player: 'Inventario de %player%'
enderchest: 'Cofre de Ender de %player%'
@@ -0,0 +1,30 @@
messages:
error:
consoleUnsupported: 'Voce nao consegue usar esse comando pelo console.'
lootNotGenerated: '&cPilhagem nao gerada! Por favor desabilite &b/silentcontainer&c.'
invalidMaterial: '&cMaterial invalido: "%target%"'
invalidNumber: '&cNumero invalido: "%target%"'
invalidPlayer: '&cJogador nao encontrado!'
permissionOpenSelf: '&cVoce nao pode abrir seu proprio inventario.'
permissionEnderAll: '&cVoce nao tem permissao para abrir baus de ender de outros jogadores.'
permissionExempt: '&cO inventario de %target% e protegido.'
permissionCrossWorld: '&c%target% nao esta no seu mundo.'
permissionPlayerOnline: '&cVoce nao tem permissao para abrir o inventario de jogadores online.'
permissionPlayerOffline: '&cVoce nao tem permissao para abrir o inventario de jogadores offline.'
commandException: '&cUm erro ocorreu. Por favor cheque o console para detalhes.'
info:
containerBlocked: 'Voce esta abrindo um recipiente bloqueado.'
containerBlockedSilent: 'Voce esta abrindo um recipiente bloqueado silenciosamente.'
containerSilent: 'Voce esta abrindo um recipiente silenciosamente.'
settingState: '%setting%: %state%'
player:
noMatches: 'Nenhum jogador encontrado com %target%.'
matches: 'Jogadores segurando %target%: %detail%'
container:
noMatches: 'Nenhum recipiente encontrado com %target%.'
matches: 'Recipientes contendo %target%: %detail%'
on: 'ligado'
off: 'desligado'
container:
player: 'Inventario de %player%'
enderchest: 'Bau de Ender de %player%'
@@ -0,0 +1,31 @@
# Translated into Chinese Simplified by Flandre_tw
messages:
error:
consoleUnsupported: 该命令无法在后台执行。
lootNotGenerated: '&c奖励箱尚未生成 ! 请关闭 &b/silentcontainer&c。'
invalidMaterial: '&c无效的物品 : "%target%"'
invalidNumber: '&c无效的数字 : "%target%"'
invalidPlayer: '&c玩家不存在 !'
permissionOpenSelf: '&c你无法开启自己的物品栏。'
permissionEnderAll: '&c你无法开启其他玩家的末影箱。'
permissionExempt: '&c%target% 的物品栏受到保护。'
permissionCrossWorld: '&c%target% 不在你所在的世界。'
permissionPlayerOnline: '&c你无法开启线上玩家的物品栏。'
permissionPlayerOffline: '&c你无法开启离线玩家的物品栏。'
commandException: '&c发生错误,请查看后台。'
info:
containerBlocked: 你正在开启受阻挡的储物箱。
containerBlockedSilent: 你正在悄悄开启受阻挡的储物箱。
containerSilent: 你正在悄悄开启储物箱。
settingState: '%setting% : %state%'
player:
noMatches: 找不到持有 %target% 的玩家。
matches: '找到持有 %target% 的玩家 : %detail%'
container:
noMatches: 找不到放有 %target% 的储物箱。
matches: '找到放有 %target% 的储物箱 : %detail%'
'true': '开启'
'false': '关闭'
container:
player: '%player% 的物品栏'
enderchest: '%player% 的末影箱'
@@ -0,0 +1,31 @@
# Translated into Chinese Traditional by Flandre_tw
messages:
error:
consoleUnsupported: 該指令無法在控制台執行。
lootNotGenerated: '&c獎勵箱尚未生成 ! 請關閉 &b/silentcontainer&c。'
invalidMaterial: '&c無效的物品 : "%target%"'
invalidNumber: '&c無效的數字 : "%target%"'
invalidPlayer: '&c玩家不存在 !'
permissionOpenSelf: '&c你無法開啟自己的物品欄。'
permissionEnderAll: '&c你無法開啟其他玩家的終界箱。'
permissionExempt: '&c%target% 的物品欄受到保護。'
permissionCrossWorld: '&c%target% 不在你所在的世界。'
permissionPlayerOnline: '&c你無法開啟線上玩家的物品欄。'
permissionPlayerOffline: '&c你無法開啟離線玩家的物品欄。'
commandException: '&c發生錯誤,請查看控制台。'
info:
containerBlocked: 你正在開啟受阻擋的儲物箱。
containerBlockedSilent: 你正在悄悄開啟受阻擋的儲物箱。
containerSilent: 你正在悄悄開啟儲物箱。
settingState: '%setting% : %state%'
player:
noMatches: 找不到持有 %target% 的玩家。
matches: '找到持有 %target% 的玩家 : %detail%'
container:
noMatches: 找不到放有 %target% 的儲物箱。
matches: '找到放有 %target% 的儲物箱 : %detail%'
'true': '開啟'
'false': '關閉'
container:
player: '%player% 的物品欄'
enderchest: '%player% 的終界箱'
+44 -15
View File
@@ -5,7 +5,7 @@ author: lishid
authors: [Jikoo, ShadowRanger]
description: >
This plugin allows you to open a player's inventory as a chest and interact with it in real time.
api-version: "1.8"
api-version: "1.16"
permissions:
OpenInv.any.default:
@@ -14,47 +14,76 @@ permissions:
OpenInv.silent.default:
description: Permission for SilentContainer to default on prior to toggling.
default: false
OpenInv.*:
description: Permission for all OpenInv features.
default: op
children:
OpenInv.openinv: true
OpenInv.openender: true
OpenInv.search: true
OpenInv.silent: true
OpenInv.anychest: true
OpenInv.searchenchant: true
OpenInv.searchcontainer: true
OpenInv.openonline: true
OpenInv.openoffline: true
OpenInv.spectate: true
OpenInv.openinv:
default: op
children:
OpenInv.openonline: true
OpenInv.openoffline: true
OpenInv.openender:
default: op
children:
OpenInv.openonline: true
OpenInv.openoffline: true
commands:
openinv:
aliases: [oi, inv, open]
description: Open a player's inventory
permission: OpenInv.*;OpenInv.openinv
permission: OpenInv.openinv
usage: |-
/<command> [Player] - Open a player's inventory
openender:
aliases: [oe]
description: Opens the enderchest of a player
permission: OpenInv.*;OpenInv.openender
permission: OpenInv.openender
usage: |-
/<command> [Player] - Open a player's enderchest
searchinv:
aliases: [si]
description: Search and list players having a specific item
permission: OpenInv.*;OpenInv.search
permission: OpenInv.search
usage: |-
/<command> <Item> [MinAmount] - Item is the ID or the Bukkit Material, MinAmount is the minimum amount required
/<command> <Material> [MinAmount] - MinAmount is optional, the minimum amount required
searchender:
aliases: [se]
permission: OpenInv.*;OpenInv.search
permission: OpenInv.search
description: Searches and lists players having a specific item in their ender chest
usage: |-
/<command> <item> [MinAmount] - Item is the ID or the Bukkit Material, MinAmount is the minimum amount required
/<command> <Material> [MinAmount] - MinAmount is optional, the minimum amount required
silentcontainer:
aliases: [sc, silent, silentchest]
description: Toggle SilentContainer function, which stops sounds and animations when using containers.
permission: OpenInv.*;OpenInv.silent
description: SilentContainer stops sounds and animations when using containers.
permission: OpenInv.silent
usage: |-
/<command> [Check] - Check or toggle silent chest
/<command> [check|on|off] - Check, toggle, or set SilentContainer
anycontainer:
aliases: [ac, anychest]
description: Toggle AnyContainer function, which allows opening of blocked containers.
permission: OpenInv.*;OpenInv.anychest
description: AnyContainer allows using blocked containers.
permission: OpenInv.anychest
usage: |-
/<command> [Check] - Checks or toggle anychest
/<command> [check|on|off] - Check, toggle, or set AnyContainer
searchenchant:
aliases: [searchenchants]
description: Search and list players with a specific enchantment.
permission: OpenInv.*;OpenInv.searchenchant
permission: OpenInv.searchenchant
usage: |-
/<command> <[enchantment] [MinLevel]> - Enchantment is the enchantment type, MinLevel is the minimum level. One is optional
/<command> <[Enchantment] [MinLevel]> - Enchantment is the enchantment type, MinLevel is the minimum level. One is optional
searchcontainer:
aliases: [searchchest]
description: Search and list containers with a specific material.
permission: OpenInv.searchcontainer
usage: /<command> <Material> [ChunkRadius] - ChunkRadius is optional, the length that will be searched for matching items. Default 5
+138 -75
View File
@@ -1,5 +1,5 @@
<!--
~ Copyright (C) 2011-2018 lishid. All rights reserved.
~ Copyright (C) 2011-2022 lishid. All rights reserved.
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
@@ -19,62 +19,43 @@
<groupId>com.lishid</groupId>
<artifactId>openinvparent</artifactId>
<name>OpenInvParent</name>
<name>OpenInv</name>
<url>http://dev.bukkit.org/bukkit-plugins/openinv/</url>
<version>4.0.2</version>
<version>4.2.1</version>
<packaging>pom</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<!-- Silence IDE warning - property is declared in individual internal modules. -->
<spigot.version>unknown</spigot.version>
</properties>
<modules>
<module>api</module>
<module>common</module>
<module>plugin</module>
<module>internal</module>
<module>assembly</module>
</modules>
<profiles>
<!--
~ N.B.: All version-specific code is handled in submodules of the internal module.
~
~ Internal submodules built by each profile are handled in internal/pom.xml
-->
<profile>
<id>all</id>
<activation>
<property>
<name>all</name>
<value>true</value>
</property>
</activation>
<modules>
<module>api</module>
<module>plugin</module>
<module>internal/v1_18_R2</module>
<module>internal/v1_19_R1</module>
<module>assembly</module>
</modules>
</profile>
<profile>
<id>latest</id>
<id>default</id>
<activation>
<property>
<name>latest</name>
<value>true</value>
</property>
<activeByDefault>true</activeByDefault>
</activation>
<modules>
<module>api</module>
<module>plugin</module>
<module>assembly</module>
</modules>
</profile>
<profile>
<id>recent</id>
<activation>
<property>
<name>recent</name>
<value>true</value>
</property>
</activation>
</profile>
</profiles>
<repositories>
@@ -84,42 +65,124 @@
</repository>
</repositories>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.0.0</version>
<configuration>
<filters>
<filter>
<artifact>*:*</artifact>
<!-- Keep the file clean, don't include every single pom from all modules -->
<excludes>
<exclude>META-INF/maven/**</exclude>
</excludes>
</filter>
</filters>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
<dependencyManagement>
<dependencies>
<dependency>
<artifactId>annotations</artifactId>
<groupId>org.jetbrains</groupId>
<scope>provided</scope>
<version>23.0.0</version>
</dependency>
<dependency>
<artifactId>spigot-api</artifactId>
<groupId>org.spigotmc</groupId>
<scope>provided</scope>
<version>1.17.1-R0.1-SNAPSHOT</version>
</dependency>
<dependency>
<artifactId>openinvapi</artifactId>
<groupId>com.lishid</groupId>
<scope>compile</scope>
<version>4.2.1</version>
</dependency>
<dependency>
<artifactId>openinvplugincore</artifactId>
<groupId>com.lishid</groupId>
<scope>compile</scope>
<version>4.2.1</version>
<exclusions>
<exclusion>
<groupId>com.lishid</groupId>
<artifactId>openinvapi</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</dependencyManagement>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
<build>
<pluginManagement>
<plugins>
<plugin>
<artifactId>maven-shade-plugin</artifactId>
<configuration>
<filters>
<filter>
<!-- Always shade entirety of required modules. -->
<artifact>com.lishid:openinv*</artifact>
<includes>
<include>**</include>
</includes>
</filter>
<filter>
<artifact>*:*</artifact>
<!-- Don't warn about file conflicts that'll be clobbered anyway. -->
<excludes>
<exclude>META-INF/MANIFEST.MF</exclude>
</excludes>
</filter>
</filters>
<minimizeJar>true</minimizeJar>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
<groupId>org.apache.maven.plugins</groupId>
<version>3.3.0</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<groupId>org.apache.maven.plugins</groupId>
<version>3.8.1</version>
</plugin>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<groupId>org.apache.maven.plugins</groupId>
<version>3.4.2</version>
</plugin>
<plugin>
<groupId>net.md-5</groupId>
<artifactId>specialsource-maven-plugin</artifactId>
<version>1.2.4</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>remap</goal>
</goals>
<id>remap-obf</id>
<configuration>
<srgIn>org.spigotmc:minecraft-server:${spigot.version}:txt:maps-mojang</srgIn>
<reverse>true</reverse>
<remappedDependencies>org.spigotmc:spigot:${spigot.version}:jar:remapped-mojang</remappedDependencies>
<remappedArtifactAttached>true</remappedArtifactAttached>
<remappedClassifierName>remapped-obf</remappedClassifierName>
</configuration>
</execution>
<execution>
<phase>package</phase>
<goals>
<goal>remap</goal>
</goals>
<id>remap-spigot</id>
<configuration>
<inputFile>${project.build.directory}/${project.artifactId}-${project.version}-remapped-obf.jar</inputFile>
<srgIn>org.spigotmc:minecraft-server:${spigot.version}:csrg:maps-spigot</srgIn>
<remappedDependencies>org.spigotmc:spigot:${spigot.version}:jar:remapped-obf</remappedDependencies>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
+82
View File
@@ -0,0 +1,82 @@
#!/bin/bash
#
# Copyright (C) 2011-2021 lishid. All rights reserved.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# Note that this script is designed for use in GitHub Actions, and is not
# particularly robust nor configurable. Run from project parent directory.
# Query GitHub for the username of the given email address.
# Falls through to the given author name.
function lookup_email_username() {
lookup=$(curl -G --data-urlencode "q=$1 in:email" https://api.github.com/search/users -H 'Accept: application/vnd.github.v3+json' | grep '"login":' | sed -e 's/^.*": "//g' -e 's/",.*$//g')
if [[ $lookup ]]; then
echo -n "@$lookup"
else
echo "$2"
fi
}
# Get a pretty list of supported Minecraft versions
function get_minecraft_versions() {
readarray -t versions <<< "$(. ./scripts/get_spigot_versions.sh)"
for version in "${versions[@]}"; do
# Append comma if variable is set, then append version
minecraft_versions="${minecraft_versions:+${minecraft_versions},}${version%%-R*}"
done
echo "${minecraft_versions}"
}
previous_tag=$(git describe --tags --abbrev=0 @^)
# Use formatted log to pull authors list
authors_raw=$(git log --pretty=format:"%ae|%an" "$previous_tag"..@)
readarray -t authors <<<"$authors_raw"
# Use associative array to map email to author name
declare -A author_data
for author in "${authors[@]}"; do
# Match author email
author_email=${author%|*}
# Convert to lower case
author_email=${author_email,,}
# Match author name
author_name=${author##*|}
if [[ -n ${author_data[$author_email]} ]]; then
# Skip emails we already have data for
continue
fi
# Fetch and store author GitHub username by email
author_data[$author_email]=$(lookup_email_username "$author_email" "$author_name")
done
# Fetch actual formatted changelog
changelog=$(git log --pretty=format:"* %s (%h) - %ae" "$previous_tag"..@)
for author_email in "${!author_data[@]}"; do
# Ignore case when matching
shopt -s nocasematch
# Match and replace email
changelog=${changelog//$author_email/${author_data[$author_email]}}
done
minecraft_versions=$(get_minecraft_versions)
printf "## Supported Minecraft versions\n%s\n\n## Changelog\n%s" "${minecraft_versions}" "${changelog}"
+52
View File
@@ -0,0 +1,52 @@
#!/bin/bash
#
# Copyright (C) 2011-2022 lishid. All rights reserved.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# Note that this script is designed for use in GitHub Actions, and is not
# particularly robust nor configurable. Run from project parent directory.
# Use a nameref as a cache - maven evaluation is pretty slow.
# Re-calling the script and relying on it to handle caching is way easier than passing around info.
declare -a spigot_versions
# We don't care about concatenation - either it's not null and we handle entries or it's null and we instantiate.
# shellcheck disable=SC2199
if [[ ${spigot_versions[@]} ]]; then
for spigot_version in "${spigot_versions[@]}"; do
echo "$spigot_version"
done
return
fi
old_maven_opts=$MAVEN_OPTS
# Add JVM parameters to allow help plugin access to packages it needs.
export MAVEN_OPTS="$old_maven_opts --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED --add-opens java.base/java.text=ALL-UNNAMED --add-opens java.desktop/java.awt.font=ALL-UNNAMED"
# Pull Spigot dependency information from Maven.
# Since we only care about Spigot versions, only check modules in the folder internal.
readarray -t modules <<< "$(mvn help:evaluate -Dexpression=project.modules -q -DforceStdout -P all | grep -oP '(?<=<string>)(internal/.*)(?=</string>)')"
declare -n versions="spigot_versions"
for module in "${modules[@]}"; do
# Get Spigot version.
spigot_version=$(mvn help:evaluate -Dexpression=spigot.version -q -DforceStdout -P all -pl "$module")
versions+=("$spigot_version")
echo "$spigot_version"
done
# Reset JVM parameters
export MAVEN_OPTS=$old_maven_opts
+53
View File
@@ -0,0 +1,53 @@
#!/bin/bash
#
# Copyright (C) 2011-2021 lishid. All rights reserved.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# Note that this script is designed for use in GitHub Actions, and is not
# particularly robust nor configurable. Run from project parent directory.
buildtools_dir=~/buildtools
buildtools=$buildtools_dir/BuildTools.jar
get_buildtools () {
if [[ -d $buildtools_dir && -f $buildtools ]]; then
return
fi
mkdir $buildtools_dir
wget https://hub.spigotmc.org/jenkins/job/BuildTools/lastSuccessfulBuild/artifact/target/BuildTools.jar -O $buildtools
}
readarray -t versions <<< "$(. ./scripts/get_spigot_versions.sh)"
echo Found Spigot dependencies: "${versions[@]}"
# Install dependencies aside from Spigot prior to running in offline mode.
# Note that the default SuperPOM declares maven-dependency-plugin 2.8.0.
# Unfortunately, we run into MDEP-204 and require a version >= 3.1.2.
mvn org.apache.maven.plugins:maven-dependency-plugin:3.2.0:go-offline -DexcludeArtifactIds=spigot
for version in "${versions[@]}"; do
set -e
exit_code=0
mvn dependency:get -Dartifact=org.spigotmc:spigot:"$version":remapped-mojang -q -o || exit_code=$?
if [ $exit_code -ne 0 ]; then
echo Installing missing Spigot version "$version"
revision=${version%%-R*}
get_buildtools
java -jar $buildtools -rev "$revision" --remapped
else
echo Spigot "$version" is already installed
fi
done
+55
View File
@@ -0,0 +1,55 @@
#!/bin/bash
#
# Copyright (C) 2011-2021 lishid. All rights reserved.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# Note that this script is designed for use in GitHub Actions, and is not
# particularly robust nor configurable. Run from project parent directory.
# Parse Spigot dependency information into major Minecraft versions
function get_curseforge_minecraft_versions() {
readarray -t versions <<< "$(. ./scripts/get_spigot_versions.sh)"
for version in "${versions[@]}"; do
# Parse Minecraft major version
version="${version%[.-]"${version#*.*[.-]}"}"
# Skip already listed versions
if [[ "$minecraft_versions" =~ "$version"($|,) ]]; then
continue
fi
# Append comma if variable is set, then append version
minecraft_versions="${minecraft_versions:+${minecraft_versions},}Minecraft ${version}"
done
echo "${minecraft_versions}"
}
# Modify provided changelog to not break when inserted into yaml file.
function get_yaml_safe_changelog() {
changelog=$1
# Since we're using a flow scalar, newlines need to be doubled.
echo "${changelog//
/
}"
}
minecraft_versions=$(get_curseforge_minecraft_versions)
echo "CURSEFORGE_MINECRAFT_VERSIONS=$minecraft_versions" >> "$GITHUB_ENV"
changelog=$(get_yaml_safe_changelog "$1")
printf "CURSEFORGE_CHANGELOG<<EOF\n%s\nEOF\n" "$changelog" >> "$GITHUB_ENV"
+32
View File
@@ -0,0 +1,32 @@
#!/bin/bash
#
# Copyright (C) 2011-2021 lishid. All rights reserved.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# Note that this script is designed for use in GitHub Actions, and is not
# particularly robust nor configurable. Run from project parent directory.
# Get a pretty string of the project's name and version
# Disable SC warning about variable expansion for this function - those are Maven variables.
# shellcheck disable=SC2016
function get_versioned_name() {
mvn -q -Dexec.executable=echo -Dexec.args='${project.name} ${project.version}' --non-recursive exec:exec
}
# Set GitHub environmental variables
echo "VERSIONED_NAME=$(get_versioned_name)" >> "$GITHUB_ENV"
changelog="$(. ./scripts/generate_changelog.sh)"
printf "GENERATED_CHANGELOG<<EOF\n%s\nEOF\n" "$changelog" >> "$GITHUB_ENV"
+37
View File
@@ -0,0 +1,37 @@
#!/bin/bash
#
# Copyright (C) 2011-2021 lishid. All rights reserved.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
if [[ ! $1 ]]; then
echo "Please provide a version string."
return
fi
version="$1"
snapshot="${version%.*}.$((${version##*.} + 1))-SNAPSHOT"
mvn versions:set -DnewVersion="$version"
git add .
git commit -S -m "Bump version to $version for release"
git tag -s "$version" -m "Release $version"
mvn clean package -am -P all
mvn versions:set -DnewVersion="$snapshot"
git add .
git commit -S -m "Bump version to $snapshot for development"