Compare commits

...
236 Commits
Author SHA1 Message Date
Jikoo c9e17e4a12 Update dependencies, use more descriptive record 2021-11-28 20:18:01 -05:00
Jikoo 78908932c5 Progress on tab completion
Complete type, amount, and enchant options
  - Still missing player and chunk bucket completion
Update to Java 16 for main plugin
  - Min supported version 1.16 requires Java 16
Determine uniqueness based on identifier for pseudojson
2021-11-27 16:23:22 -05:00
Jikoo 5f1a613b48 Repackage and rename for clarity 2021-11-27 13:56:31 -05:00
Jikoo 18c3dbaee0 Merge branch 'master' into dev/rewrite-search 2021-11-27 07:09:00 -05:00
Jikoo d236dd27b3 Update to 1.18-pre5 2021-11-23 13:00:42 -05:00
Jikoo 6775988347 Stash changes 2021-11-23 12:45:40 -05:00
Jikoo 2580797633 Merge branch 'master' into dev/rewrite-search
# Conflicts:
#	plugin/pom.xml
#	plugin/src/main/java/com/lishid/openinv/commands/SearchContainerCommand.java
#	plugin/src/main/java/com/lishid/openinv/commands/SearchEnchantCommand.java
#	plugin/src/main/java/com/lishid/openinv/commands/SearchInvCommand.java
#	pom.xml
2021-11-17 10:59:17 -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 28929fd61e Clean up parsing, prep for tab completion 2021-05-29 09:38:06 -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 2445e7f8b6 Disallow concurrent searches from a single user 2021-05-13 07:53:00 -04:00
Jikoo 0af6403570 Move pseudo-JSON to a separate class
This and generifying parsing will make tab completion much easier to write.
2021-04-29 16:45:12 -04:00
Jikoo 55018ca228 Fix last parameter, issues with async chunks 2021-04-24 11:37:59 -04:00
Jikoo 64bd918f32 Merge branch 'master' into dev/rewrite-search
# Conflicts:
#	README.MD
#	api/pom.xml
#	internal/v1_16_R3/pom.xml
#	plugin/pom.xml
#	pom.xml
2021-04-23 14:00:24 -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 fb6a3a7bcf Simplify dependency management
Use dependencyManagement for versioning shared dependencies
Use pluginManagement for versioning and configuring plugins

Uberjar is still including classes it shouldn't, but that's not a new problem.
2021-04-21 11:07:15 -04:00
Jikoo 4d4aa86399 Simplify enchantment spec 2021-04-21 11:06:53 -04:00
Jikoo 1dce65cecb Drop PaperLib 2021-04-21 11:05:21 -04:00
Jikoo c1145ae111 Search progress
Change detail specs to JSON-esque expressions
Implement matching option parsing
Split search bucket specs into multiple arguments
2021-04-16 12:51:19 -04:00
Jikoo 19f029f17f Search progress
Allow multi-world searches
Search player crafting inventory if open
2021-04-13 20:36:41 -04:00
Jikoo 916a8c0c55 Start reworking search 2021-04-11 17:35:15 -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
Jikoo 64ad0a5267 Bump version to 4.0.2 for release 2019-05-27 20:22:50 -04:00
Jikoo cba7f44192 Include api-version in plugin.yml (Closes #102) 2019-05-27 20:22:02 -04:00
Jikoo e8ace6bd06 Update to 1.14.2 2019-05-27 20:12:39 -04:00
Jikoo fd70f260ba Fix mistake in string comparison utility 2019-05-27 20:05:58 -04:00
Jikoo be9874ba1d Fix pom changes causing Spigot API to be shaded into final jar 2019-05-27 20:05:06 -04:00
Jikoo 096faef161 Bump version to 4.0.2-SNAPSHOT for development 2019-05-15 19:20:50 -04:00
Jikoo df96d5106a Bump version to 4.0.1 for release 2019-05-15 19:20:25 -04:00
Jikoo fe77a1d2e7 Drop support for 1.14, add support for 1.14.1 2019-05-15 19:19:40 -04:00
Jikoo 3096e43540 Update to Java 8 and Minecraft 1.8.8 2019-05-05 22:20:43 -04:00
Jikoo 2939551d65 Delete modules for versions no longer being supported 2019-05-05 18:03:22 -04:00
Jikoo fb74fd3ced Bump version to 4.0.1-SNAPSHOT for development 2019-05-05 16:55:36 -04:00
Jikoo 393fe1d837 Bump version to 4.0.0 for release 2019-05-05 16:55:12 -04:00
Jikoo f80df9d83b Copy pasted javadocs are super great 2019-05-05 16:49:41 -04:00
Jikoo 5c006f8ebd QOL: Warn users when plugin is running on an unsupported version 2019-05-05 15:43:41 -04:00
Jikoo 57097e626e Reduce duplicate code in core plugin 2019-05-05 15:39:54 -04:00
Jikoo 0f266c8a64 Custom inventory titles for 1.14 2019-05-05 15:15:17 -04:00
Jikoo 185f037254 Revert API changes, fix SpecialPlayerInventory implementation 2019-05-05 12:40:47 -04:00
Jikoo e4d0020296 Update to 1.14 (#101) 2019-04-28 20:37:45 -04:00
Jikoo 9e37cbbca8 Prepare for 1.14
To continue providing renamed ender chests/player inventories we can no longer just provide an Inventory, a full InventoryView is required. To avoid confusion the old method has been removed entirely, leading to a major API revision bump.
2019-04-28 20:37:05 -04:00
Jikoo adc35e9ad5 Correct players not being properly released 2019-04-28 12:11:59 -04:00
Jikoo a469c732cc Store UUID instead of name for faster re-open lookups 2019-04-28 11:03:00 -04:00
Jikoo c5c1f47167 Hacky workaround for Java 9 reflection changes (#93) 2019-04-12 20:56:05 -04:00
Jikoo dc59356f76 Bump version to 3.3.8-SNAPSHOT for development 2019-01-18 18:36:28 -05:00
Jikoo d497772ad7 Bump version to 3.3.7 for release 2019-01-18 18:35:50 -05:00
Jikoo 4f4d46974a Support 1.13.2 (#94) 2019-01-15 20:01:41 -05:00
Jikoo 96c38895e2 Bump version to 3.3.7-SNAPSHOT for development 2018-08-26 14:47:13 -04:00
Jikoo 5753648d77 Bump version to 3.3.6 for release 2018-08-26 14:46:33 -04:00
Jikoo 3725711b94 Support Spigot 1.13.1 2018-08-26 14:43:21 -04:00
Jikoo c22e18c096 Correct unnecessary AnyContainer usage 2018-08-26 14:37:29 -04:00
Jikoo 6bff759d72 Bump version to 3.3.6-SNAPSHOT for development 2018-08-13 21:31:46 -04:00
Jikoo 582c7201dd Bump version to 3.3.5 for release 2018-08-13 21:31:17 -04:00
Jikoo 59a8ed70bb Correct double chest logic for Silent/AnyContainer in 1.13 2018-08-09 18:50:24 -04:00
Jikoo 27812c133d Bump version to 3.3.5-SNAPSHOT for development 2018-07-22 09:45:01 -04:00
Jikoo 165f3fb00e Bump version to 3.3.4 for release 2018-07-22 09:44:27 -04:00
Jikoo 60dcb64558 Bump version for alpha release 2018-07-15 12:05:52 -04:00
Jikoo 077b34d304 Support 1.13-pre7 2018-07-15 12:01:45 -04:00
Jikoo 8ce6e2f8d3 Reverse order of wildcard permission check, respect explicit values (#85) 2018-05-26 06:58:56 -04:00
Jikoo f5dce23f18 Bump version to 3.3.4-SNAPSHOT for development 2018-02-07 18:36:15 -05:00
Jikoo 669cfd72aa Bump version to 3.3.3 for release 2018-02-07 18:35:39 -05:00
Jikoo 9e66885690 First load should not "update" nonexistent config, just use a default 2018-02-07 18:17:33 -05:00
Jikoo a802769265 Sneaky tabs, trying to slip in there. 2018-02-04 10:35:35 -05:00
Jikoo 50c45b6616 Cleaned up code, updated copyright at long last. 2018-02-04 10:29:09 -05:00
Jikoo eaf01fa32c Don't cache simultaneously added players forever 2017-11-19 14:46:52 -05:00
Jikoo 4abaa6ea51 Backport spectate abuse to 1.8-1.10 to support PaperSpigot
This may break functionality on C(K)auldron and other server implementations with different remapping names.
2017-11-08 19:07:23 -05:00
Jikoo 7c621bf899 Bump version to 3.3.3-SNAPSHOT for development 2017-11-08 17:53:16 -05:00
Jikoo f5c682ff2b Bump version to 3.3.2 for release 2017-11-08 17:52:41 -05:00
Jikoo e652b43670 Classic mistake, changed my mind and only changed part of the code.
Also the actual mistake. Never forget. (#81)
2017-11-08 17:40:01 -05:00
Jikoo f07cef5621 Bump version to 3.3.2-SNAPSHOT for development 2017-11-07 18:18:33 -05:00
Jikoo ded6421e33 Bump version to 3.3.1 for release 2017-11-07 18:17:12 -05:00
Jikoo cbb2b4d232 Lowercase permissions can wait for a major version bump
I don't want to double our permissions checks for backwards compatibility.
2017-11-07 18:16:38 -05:00
Jikoo 48c61eeb82 Support lowercase permissions for consistency. 2017-11-06 19:23:26 -05:00
Jikoo e3d1af010b Add permissions to alter the default state of Any/SilentChest (#76) 2017-11-05 20:00:22 -05:00
Jikoo 2195677651 Actually fix CME (#71) 2017-08-06 16:35:18 -04:00
Jikoo c72af5dbac Bump version to 3.3.1-SNAPSHOT for development 2017-07-05 05:53:38 -04:00
Jikoo 931cdb6fb1 Bump version to 3.3.0 for release 2017-07-05 05:52:33 -04:00
Jikoo 9db18613a1 Abuse spectator mode for SilentContainer (#64, #68)
Potentially causes issues with Cauldron and similar mods that use different mapping names.
2017-07-04 13:22:03 -04:00
Jikoo 784935a975 Synchronize cache (for real), updated dependencies
Closes #69
2017-07-03 18:06:18 -04:00
Jikoo d9abe76531 Bump version to 3.2.2-SNAPSHOT for development 2017-06-09 20:21:30 -04:00
Jikoo ca85a392aa Correct field name for list of inventory contents (#66) 2017-06-09 20:15:51 -04:00
Jikoo 307f47e4d3 Bump version to 3.2.1-SNAPSHOT for development 2017-06-08 18:44:10 -04:00
Jikoo 96c59f163d Slightly improve API, bump version to 3.2.0 for release
Methods are now properly annotated @Nullable when they may return null. More descriptive exceptions are thrown when issues occur instead of just returning null.
2017-06-08 18:36:01 -04:00
Jikoo 01f147b13c Fix incorrect method call for checking if a block is occluding
Switched to using the API for statistic manipulation.
2017-06-08 18:26:26 -04:00
Jikoo 1c104b775f For improved compatibility, our own Levenshtein implementation 2017-05-28 09:53:59 -04:00
Jikoo 9a53e46dd3 Replaced modern profile with recent
Going forward, the primary file uploaded to Bukkit will probably be the smaller build made with the `recent` profile, supporting only the last 2 versions. The uberjar created by the `all` will be provided as an additional download. OpenInv v3.1.3-SNAPSHOT `all` uberjar currently is 276KB. The `recent` jar is only 81KB. It's getting silly, and will only continue to get worse.
2017-05-26 05:52:35 -04:00
Jikoo 6744e71127 Support 1.12-pre2 (#65) 2017-05-14 11:47:15 -04:00
Jikoo 12c3ebb798 Bump version to 3.1.3-SNAPSHOT for development 2017-02-13 20:02:12 -05:00
Jikoo 32ec8d8147 Bump version to 3.1.2 for release 2017-02-13 20:01:18 -05:00
Jikoo 443e0c489e Synchronize cache (#63) 2017-02-13 19:56:32 -05:00
Jikoo 7ab0003b62 Bump version to 3.1.2-SNAPSHOT for development 2017-01-30 13:38:36 -05:00
Jikoo 12c0cac311 Bump version to 3.1.1 for release 2017-01-30 13:38:10 -05:00
Jikoo f54481e872 Fix (NoSuchMethod)Exception being caught instead of NoSuchMethodError
Closes #62
2017-01-30 13:35:58 -05:00
Jikoo f27dd8a233 Bump version to 3.1.1-SNAPSHOT for development 2017-01-20 13:02:47 -05:00
Jikoo d6df1a0871 Bump version to 3.1.0 for release 2017-01-20 12:59:03 -05:00
Jikoo e34e51d127 Better support other Player implementations
Closes #60
Removed all volatile comments - they require me to occasionally manually maintain them and NMS/OBC imports are already kept separate
2017-01-20 12:56:31 -05:00
Jikoo 5ca2ae461a Properly use Maven versioning for better support of dependent plugins
Closes #59
Bumped version to 3.1.0-SNAPSHOT for development, really should have bumped the minor version a while back.
2017-01-17 18:06:35 -05:00
Jikoo 4095e8039d RIPo.bukkit.org
repo.bukkit.org is dead, Spigot doesn't host builds that old. Didn't realize that I'd installed it locally when compiling CB 1.4.5 to use with OpenInv. Whoops.

Corrected dependency order - no idea why this compiled at all before. My bad.
2017-01-15 08:08:51 -05:00
Jikoo 73f390ab20 Jitpack's Maven version is pretty outdated. Hopefully fixes build.
The `all` profile is no longer active by default, just in case.
2017-01-15 07:33:53 -05:00
Jikoo 2f7d3bc235 Call InventoryOpenEvent when using SilentChest
Backport fix for incorrect parameter as well, just in case.
Fixed 1.7 and lower forcing all chests' default names to be "Large chest" instead of the translatable string.
2017-01-10 14:12:58 -05:00
Jikoo ef486032f1 Fix incorrect parameter for SilentChest PacketPlayOutOpenWindow
Probably fixes issues with ProtocolSupport, and if it doesn't there's nothing else I can do as we'll be as close to identical to NMS as possible.
2017-01-10 12:50:24 -05:00
Jikoo 72ef873772 Fixes for shulker boxes and AnyChest in 1.11
Fixes IBlockData used being the default for a shulker box, closes #55
Corrected using wrong method to check if blocked in 1.11.2. Something something NMS version bumps.
2016-12-31 07:05:52 -05:00
Jikoo 9b6e32a775 Fix statistic changes breaking silent/anycontainer in 1.11.1+
C'mon Spigot team, this is why the revision bumps existed in the first place, to allow plugins that deal with NMS to easily and gracefully fail on unsupported versions. 1.11.1 should be R2, 1.11.2 should be R3. I get that NMS isn't officially supported, but honestly, either do away with the versioned packages entirely or keep up with them properly. It's a single string change, it's not like it's prohibitively difficult.
2016-12-29 09:05:01 -05:00
Jikoo f05110c9b8 Fix getOnlinePlayers call to support all versions
Moved IPlayerDataManager from api module to common module - it is not part of the API as there is no supported way to obtain an instance of it.
2016-12-22 09:55:51 -05:00
Jikoo a41f89b011 Release any players held by a disabling plugin 2016-12-16 15:13:04 -05:00
Jikoo d24827ffcb Allow plugins to indicate to OpenInv that a Player is in use
This allows API users to prevent issues caused by multiple different copies of the Player being loaded, such as #49. Multiple instances of the same player could be obtained by calling IOpenInv#loadPlayer, waiting for OpenInv to remove it from the cache, then calling the method again.
2016-12-14 19:49:18 -05:00
Jikoo 802ce28103 Fix issue caused by module cleanup while backporting awarding achievement 2016-12-05 19:02:43 -05:00
Jikoo f31356b227 Fix permissions mistakes in readme 2016-12-05 19:01:21 -05:00
Jikoo 7942466863 Maven cleanliness and API progress
The project was very messy and due to older Bukkit packaging conventions, 1_4_5 and 1_4_6 were sorted away from the rest of the versioned code. All of the versioned internals are now submodules of the internal module.
Rather than use the hackish existing method of abusing the shade plugin to combine "dependencies" for a dummy assembly project, we're actually using the assembly plugin.
Profiles are still split up between the parent pom and the internal module pom, but they're much more clean.

The API is now its own module and can be compiled and released as a separate file for developers. Soon, Bukkit ticket 20, you'll be closed.
2016-11-30 21:26:56 -05:00
Jikoo db2cade4e2 Don't convert names in config to UUIDs on the main thread
This is pretty messy, but I can't think of a better way to avoid saving about 5 times in a row. Then again, I did just wake up, so my brain may not be on point yet.
2016-11-27 17:30:39 -05:00
Jikoo 718b4bb5dd Added /searchenchant, fixed a couple little mistakes
Mistakes being breaking UUID-based lookups and /anychest's toggle.
MY BAD, SORRY.
2016-11-27 06:38:24 -05:00
Jikoo 6aa25dd2dc Don't use reflection to increment container counter
Fixes #53 (pretty please with a cherry on top)
2016-11-27 06:13:17 -05:00
Jikoo 0bbcf6cdb2 Strip removed config options, add DisableSaving and bump config version 2016-11-26 17:05:06 -05:00
ShadowRanger f11d60f78c Reimplement ShadowRanger's configuration updater and path changes
Bumped version for release
2016-11-26 16:57:03 -05:00
Jikoo 8a6b98614f Added support for UUID-based player lookups in 1.7.5+
You could argue that ShadowRanger's conversion of everything to UUID is better, but that would result in us having to contact Mojang's servers simply to fetch a player by UUID for versions < 1.7.5. It seems excessive (not to mention that uncached contact can result in rate limiting) when the server itself will not remember who they are across name changes. If they can re-obtain everything in their inventory, they can re-run /ac.
2016-11-26 15:31:53 -05:00
233 changed files with 9886 additions and 16481 deletions

No files matched your search

+77
View File
@@ -0,0 +1,77 @@
name: OpenInv CI
on:
push:
pull_request_target:
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v2
- name: Set Up Java
uses: actions/setup-java@v2
with:
distribution: 'adopt'
java-version: '17'
# Use cache to speed up build
- name: Cache Maven Repo
uses: actions/cache@v2
id: cache
with:
path: ~/.m2/repository
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
# Install Spigot dependencies.
# This script uses Maven to check all required installations and ensure that they are present.
- 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@v2
with:
name: dist
path: ./target/OpenInv.jar
- name: Upload API Jar
id: upload-api
uses: actions/upload-artifact@v2
with:
name: api
path: ./api/target/openinvapi*.jar
release:
name: Create Github Release
needs: [ build ]
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Set Release Variables
run: bash ./scripts/set_release_env.sh
- name: Download Artifacts
uses: actions/download-artifact@v2
- name: Create Release
id: create-release
uses: softprops/action-gh-release@v0.1.5
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@0.0.5
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 -7
View File
@@ -1,10 +1,10 @@
**/.settings
**/.gitignore
**/.project
**/.classpath
**/.idea
**/target
**/bin
**/lib
**/out
**dependency-reduced-pom.xml
**/.idea/
**.iml
**/target/
**/bin/
**/out/
**/dependency-reduced-pom.xml
**/pom.xml.versionsBackup
-28
View File
@@ -1,28 +0,0 @@
<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>openinv</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>openinvadapter1_4_5</artifactId>
<name>OpenInvAdapter1_4_5</name>
<dependencies>
<dependency>
<groupId>com.lishid</groupId>
<artifactId>openinvplugin</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.bukkit</groupId>
<artifactId>craftbukkit</artifactId>
<version>1.4.5-R1.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
@@ -1,168 +0,0 @@
/*
* Copyright (C) 2011-2014 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_4_5;
import java.lang.reflect.Field;
import com.lishid.openinv.internal.IAnySilentContainer;
import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.entity.Player;
// Volatile
import net.minecraft.server.v1_4_5.AxisAlignedBB;
import net.minecraft.server.v1_4_5.BlockEnderChest;
import net.minecraft.server.v1_4_5.EntityOcelot;
import net.minecraft.server.v1_4_5.EntityPlayer;
import net.minecraft.server.v1_4_5.IInventory;
import net.minecraft.server.v1_4_5.InventoryEnderChest;
import net.minecraft.server.v1_4_5.InventoryLargeChest;
import net.minecraft.server.v1_4_5.Packet100OpenWindow;
import net.minecraft.server.v1_4_5.TileEntityChest;
import net.minecraft.server.v1_4_5.TileEntityEnderChest;
import net.minecraft.server.v1_4_5.World;
import org.bukkit.craftbukkit.v1_4_5.entity.CraftPlayer;
public class AnySilentContainer implements IAnySilentContainer {
@Override
public boolean isAnySilentContainer(org.bukkit.block.Block block) {
return block.getType() == Material.ENDER_CHEST || block.getState() instanceof org.bukkit.block.Chest;
}
@Override
public boolean isAnyContainerNeeded(Player p, org.bukkit.block.Block block) {
// FOR REFERENCE, LOOK AT net.minecraft.server.BlockChest
EntityPlayer player = ((CraftPlayer) p).getHandle();
World world = player.world;
if (block instanceof BlockEnderChest) {
// Ender chests are not blocked by ocelots.
return world.t(block.getX(), block.getY() + 1, block.getZ());
}
// If block or ocelot on top
if (isBlockedChest(world, block.getX(), block.getY() + 1, block.getZ())) {
return true;
}
int id = world.getTypeId(block.getX(), block.getY(), block.getZ());
// If block next to chest is chest and has a block or ocelot on top
if (world.getTypeId(block.getX(), block.getY(), block.getZ() + 1) == id) {
return isBlockedChest(world, block.getX(), block.getY() + 1, block.getZ() + 1);
} else if(world.getTypeId(block.getX(), block.getY(), block.getZ() - 1) == id) {
return isBlockedChest(world, block.getX(), block.getY() + 1, block.getZ() - 1);
} else if (world.getTypeId(block.getX() + 1, block.getY(), block.getZ()) == id) {
return isBlockedChest(world, block.getX() + 1, block.getY() + 1, block.getZ());
} else if (world.getTypeId(block.getX() - 1, block.getY(), block.getZ()) == id) {
return isBlockedChest(world, block.getX() - 1, block.getY() + 1, block.getZ());
}
return false;
}
private boolean isBlockedChest(World world, int x, int y, int z) {
return world.t(x, y + 1, z) || hasOcelotOnTop(world, x, y, z);
}
private boolean hasOcelotOnTop(World world, int x, int y, int z) {
for (Object localEntity : world.a(EntityOcelot.class,
AxisAlignedBB.a(x, y + 1, z, x + 1, y + 2, z + 1))) {
EntityOcelot localEntityOcelot = (EntityOcelot) localEntity;
if (localEntityOcelot.isSitting()) {
return true;
}
}
return false;
}
@Override
public boolean activateContainer(Player p, boolean silentchest, org.bukkit.block.Block block) {
EntityPlayer player = ((CraftPlayer) p).getHandle();
// Silent ender chest is API-only
if (silentchest && block.getType() == Material.ENDER_CHEST) {
p.openInventory(p.getEnderChest());
return true;
}
World world = player.world;
Object tile = world.getTileEntity(block.getX(), block.getY(), block.getZ());
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);
return true;
}
if (!(tile instanceof IInventory)) {
return false;
}
int id = world.getTypeId(block.getX(), block.getY(), block.getZ());
if (world.getTypeId(block.getX(), block.getY(), block.getZ() + 1) == id) {
tile = new InventoryLargeChest("Large chest", (IInventory) tile, (TileEntityChest) world.getTileEntity(block.getX(), block.getY(), block.getZ() + 1));
} else if (world.getTypeId(block.getX(), block.getY(), block.getZ() - 1) == id) {
tile = new InventoryLargeChest("Large chest", (TileEntityChest) world.getTileEntity(block.getX(), block.getY(), block.getZ() - 1), (IInventory) tile);
} else if (world.getTypeId(block.getX() + 1, block.getY(), block.getZ()) == id) {
tile = new InventoryLargeChest("Large chest", (IInventory) tile, (TileEntityChest) world.getTileEntity(block.getX() + 1, block.getY(), block.getZ()));
} else if (world.getTypeId(block.getX() - 1, block.getY(), block.getZ()) == id) {
tile = new InventoryLargeChest("Large chest", (TileEntityChest) world.getTileEntity(block.getX() - 1, block.getY(), block.getZ()), (IInventory) tile);
}
boolean returnValue = false;
if (!silentchest) {
player.openContainer((IInventory) tile);
returnValue = true;
} else {
try {
int windowId = 0;
try {
Field windowID = player.getClass().getDeclaredField("containerCounter");
windowID.setAccessible(true);
windowId = windowID.getInt(player);
windowId = windowId % 100 + 1;
windowID.setInt(player, windowId);
} catch (NoSuchFieldException e) {}
player.netServerHandler.sendPacket(new Packet100OpenWindow(windowId, 0, ((IInventory) tile).getName(), ((IInventory) tile).getSize()));
player.activeContainer = new SilentContainerChest(player.inventory, ((IInventory) tile));
player.activeContainer.windowId = windowId;
player.activeContainer.addSlotListener(player);
returnValue = true;
} catch (Exception e) {
e.printStackTrace();
p.sendMessage(ChatColor.RED + "Error while sending silent chest.");
}
}
return returnValue;
}
}
@@ -1,79 +0,0 @@
/*
* Copyright (C) 2011-2014 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_4_5;
import com.lishid.openinv.internal.IInventoryAccess;
import com.lishid.openinv.internal.ISpecialEnderChest;
import com.lishid.openinv.internal.ISpecialPlayerInventory;
import com.lishid.openinv.internal.InternalAccessor;
import org.bukkit.inventory.Inventory;
// Volatile
import net.minecraft.server.v1_4_5.IInventory;
import org.bukkit.craftbukkit.v1_4_5.inventory.CraftInventory;
public class InventoryAccess implements IInventoryAccess {
@Override
public boolean isSpecialPlayerInventory(Inventory inventory) {
if (inventory instanceof CraftInventory) {
return ((CraftInventory) inventory).getInventory() instanceof ISpecialPlayerInventory;
}
return InternalAccessor.grabFieldOfTypeFromObject(IInventory.class, inventory) instanceof ISpecialPlayerInventory;
}
@Override
public ISpecialPlayerInventory getSpecialPlayerInventory(Inventory inventory) {
IInventory inv;
if (inventory instanceof CraftInventory) {
inv = ((CraftInventory) inventory).getInventory();
} else {
inv = InternalAccessor.grabFieldOfTypeFromObject(IInventory.class, inventory);
}
if (inv instanceof SpecialPlayerInventory) {
return (SpecialPlayerInventory) inv;
}
return null;
}
@Override
public boolean isSpecialEnderChest(Inventory inventory) {
if (inventory instanceof CraftInventory) {
return ((CraftInventory) inventory).getInventory() instanceof ISpecialEnderChest;
}
return InternalAccessor.grabFieldOfTypeFromObject(IInventory.class, inventory) instanceof ISpecialEnderChest;
}
@Override
public ISpecialEnderChest getSpecialEnderChest(Inventory inventory) {
IInventory inv;
if (inventory instanceof CraftInventory) {
inv = ((CraftInventory) inventory).getInventory();
} else {
inv = InternalAccessor.grabFieldOfTypeFromObject(IInventory.class, inventory);
}
if (inv instanceof SpecialEnderChest) {
return (SpecialEnderChest) inv;
}
return null;
}
}
@@ -1,62 +0,0 @@
/*
* Copyright (C) 2011-2014 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_4_5;
import com.lishid.openinv.internal.IPlayerDataManager;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
// Volatile
import net.minecraft.server.v1_4_5.EntityPlayer;
import net.minecraft.server.v1_4_5.ItemInWorldManager;
import net.minecraft.server.v1_4_5.MinecraftServer;
import org.bukkit.craftbukkit.v1_4_5.CraftServer;
public class PlayerDataManager implements IPlayerDataManager {
@Override
public Player loadPlayer(OfflinePlayer offline) {
// Ensure the player has data
if (offline == null || !offline.hasPlayedBefore()) {
return null;
}
MinecraftServer server = ((CraftServer) Bukkit.getServer()).getServer();
// Create an entity to load the player data
EntityPlayer entity = new EntityPlayer(server, server.getWorldServer(0), offline.getName(),
new ItemInWorldManager(server.getWorldServer(0)));
// Get the bukkit entity
Player target = (entity == null) ? null : entity.getBukkitEntity();
if (target != null) {
// Load data
target.loadData();
}
// Return the entity
return target;
}
@Override
public String getPlayerDataID(OfflinePlayer player) {
return player.getName();
}
}
@@ -1,46 +0,0 @@
/*
* Copyright (C) 2011-2014 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_4_5;
// Volatile
import net.minecraft.server.v1_4_5.ContainerChest;
import net.minecraft.server.v1_4_5.EntityHuman;
import net.minecraft.server.v1_4_5.IInventory;
import net.minecraft.server.v1_4_5.ItemStack;
import net.minecraft.server.v1_4_5.PlayerInventory;
public class SilentContainerChest extends ContainerChest {
public SilentContainerChest(IInventory i1, IInventory i2) {
super(i1, i2);
// Send close signal
i2.f();
}
@Override
public void b(EntityHuman entityHuman) {
// Don't send close signal twice, might screw up
PlayerInventory playerinventory = entityHuman.inventory;
if (playerinventory.getCarried() != null) {
ItemStack carried = playerinventory.getCarried();
playerinventory.setCarried(null);
entityHuman.drop(carried);
}
}
}
@@ -1,128 +0,0 @@
/*
* Copyright (C) 2011-2014 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_4_5;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import com.lishid.openinv.internal.ISpecialEnderChest;
import org.bukkit.entity.HumanEntity;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryHolder;
// Volatile
import net.minecraft.server.v1_4_5.IInventory;
import net.minecraft.server.v1_4_5.InventoryEnderChest;
import net.minecraft.server.v1_4_5.InventorySubcontainer;
import net.minecraft.server.v1_4_5.ItemStack;
import org.bukkit.craftbukkit.v1_4_5.entity.CraftHumanEntity;
import org.bukkit.craftbukkit.v1_4_5.entity.CraftPlayer;
import org.bukkit.craftbukkit.v1_4_5.inventory.CraftInventory;
public class SpecialEnderChest extends InventorySubcontainer implements IInventory, ISpecialEnderChest {
private final InventoryEnderChest enderChest;
private final CraftInventory inventory = new CraftInventory(this);
private final List<HumanEntity> transaction = new ArrayList<HumanEntity>();
private boolean playerOnline = false;
private CraftPlayer owner;
private int maxStack = MAX_STACK;
public SpecialEnderChest(Player p, Boolean online) {
super(((CraftPlayer) p).getHandle().getEnderChest().getName(),
((CraftPlayer) p).getHandle().getEnderChest().getSize());
CraftPlayer player = (CraftPlayer) p;
this.enderChest = player.getHandle().getEnderChest();
this.owner = player;
this.items = enderChest.getContents();
}
@Override
public Inventory getBukkitInventory() {
return inventory;
}
@Override
public void setPlayerOnline(Player player) {
if (!playerOnline) {
try {
owner = (CraftPlayer) player;
InventoryEnderChest playerEnderChest = owner.getHandle().getEnderChest();
Field field = playerEnderChest.getClass().getField("items");
field.setAccessible(true);
field.set(playerEnderChest, this.items);
} catch (Exception e) {}
playerOnline = true;
}
}
@Override
public void setPlayerOffline() {
playerOnline = false;
}
@Override
public boolean isInUse() {
return !this.getViewers().isEmpty();
}
@Override
public ItemStack[] getContents() {
return this.items;
}
@Override
public void onOpen(CraftHumanEntity who) {
transaction.add(who);
}
@Override
public void onClose(CraftHumanEntity who) {
transaction.remove(who);
}
@Override
public List<HumanEntity> getViewers() {
return transaction;
}
@Override
public InventoryHolder getOwner() {
return this.owner;
}
@Override
public void setMaxStackSize(int size) {
maxStack = size;
}
@Override
public int getMaxStackSize() {
return maxStack;
}
@Override
public void update() {
super.update();
enderChest.update();
}
}
@@ -1,237 +0,0 @@
/*
* Copyright (C) 2011-2014 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_4_5;
import com.lishid.openinv.internal.ISpecialPlayerInventory;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
// Volatile
import net.minecraft.server.v1_4_5.ItemStack;
import net.minecraft.server.v1_4_5.PlayerInventory;
import org.bukkit.craftbukkit.v1_4_5.entity.CraftPlayer;
import org.bukkit.craftbukkit.v1_4_5.inventory.CraftInventory;
public class SpecialPlayerInventory extends PlayerInventory implements ISpecialPlayerInventory {
private final ItemStack[] extra = new ItemStack[5];
private final CraftInventory inventory = new CraftInventory(this);
private boolean playerOnline = false;
public SpecialPlayerInventory(Player bukkitPlayer, Boolean online) {
super(((CraftPlayer) bukkitPlayer).getHandle());
this.playerOnline = online;
this.items = player.inventory.items;
this.armor = player.inventory.armor;
}
@Override
public Inventory getBukkitInventory() {
return inventory;
}
@Override
public void setPlayerOnline(Player player) {
if (!playerOnline) {
this.player = ((CraftPlayer) player).getHandle();
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].a(j);
if (is[i].count == 0) {
is[i] = null;
}
return itemstack;
}
}
else {
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;
}
else {
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);
itemstack = null;
}
is[i] = itemstack;
player.defaultContainer.b();
}
private int getReversedItemSlotNum(int i) {
if (i >= 27)
return i - 27;
else
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;
else
return i;
}
@Override
public String getName() {
if (player.getName().length() > 16) {
return player.getName().substring(0, 16);
}
return player.getName();
}
}
-28
View File
@@ -1,28 +0,0 @@
<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>openinv</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>openinvadapter1_4_6</artifactId>
<name>OpenInvAdapter1_4_6</name>
<dependencies>
<dependency>
<groupId>com.lishid</groupId>
<artifactId>openinvplugin</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.bukkit</groupId>
<artifactId>craftbukkit</artifactId>
<version>1.4.6-R0.3</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
@@ -1,168 +0,0 @@
/*
* Copyright (C) 2011-2014 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_4_6;
import java.lang.reflect.Field;
import com.lishid.openinv.internal.IAnySilentContainer;
import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.entity.Player;
// Volatile
import net.minecraft.server.v1_4_6.AxisAlignedBB;
import net.minecraft.server.v1_4_6.BlockEnderChest;
import net.minecraft.server.v1_4_6.EntityOcelot;
import net.minecraft.server.v1_4_6.EntityPlayer;
import net.minecraft.server.v1_4_6.IInventory;
import net.minecraft.server.v1_4_6.InventoryEnderChest;
import net.minecraft.server.v1_4_6.InventoryLargeChest;
import net.minecraft.server.v1_4_6.Packet100OpenWindow;
import net.minecraft.server.v1_4_6.TileEntityChest;
import net.minecraft.server.v1_4_6.TileEntityEnderChest;
import net.minecraft.server.v1_4_6.World;
import org.bukkit.craftbukkit.v1_4_6.entity.CraftPlayer;
public class AnySilentContainer implements IAnySilentContainer {
@Override
public boolean isAnySilentContainer(org.bukkit.block.Block block) {
return block.getType() == Material.ENDER_CHEST || block.getState() instanceof org.bukkit.block.Chest;
}
@Override
public boolean isAnyContainerNeeded(Player p, org.bukkit.block.Block block) {
// FOR REFERENCE, LOOK AT net.minecraft.server.BlockChest
EntityPlayer player = ((CraftPlayer) p).getHandle();
World world = player.world;
if (block instanceof BlockEnderChest) {
// Ender chests are not blocked by ocelots.
return world.t(block.getX(), block.getY() + 1, block.getZ());
}
// If block or ocelot on top
if (isBlockedChest(world, block.getX(), block.getY() + 1, block.getZ())) {
return true;
}
int id = world.getTypeId(block.getX(), block.getY(), block.getZ());
// If block next to chest is chest and has a block or ocelot on top
if (world.getTypeId(block.getX(), block.getY(), block.getZ() + 1) == id) {
return isBlockedChest(world, block.getX(), block.getY() + 1, block.getZ() + 1);
} else if(world.getTypeId(block.getX(), block.getY(), block.getZ() - 1) == id) {
return isBlockedChest(world, block.getX(), block.getY() + 1, block.getZ() - 1);
} else if (world.getTypeId(block.getX() + 1, block.getY(), block.getZ()) == id) {
return isBlockedChest(world, block.getX() + 1, block.getY() + 1, block.getZ());
} else if (world.getTypeId(block.getX() - 1, block.getY(), block.getZ()) == id) {
return isBlockedChest(world, block.getX() - 1, block.getY() + 1, block.getZ());
}
return false;
}
private boolean isBlockedChest(World world, int x, int y, int z) {
return world.t(x, y + 1, z) || hasOcelotOnTop(world, x, y, z);
}
private boolean hasOcelotOnTop(World world, int x, int y, int z) {
for (Object localEntity : world.a(EntityOcelot.class,
AxisAlignedBB.a(x, y + 1, z, x + 1, y + 2, z + 1))) {
EntityOcelot localEntityOcelot = (EntityOcelot) localEntity;
if (localEntityOcelot.isSitting()) {
return true;
}
}
return false;
}
@Override
public boolean activateContainer(Player p, boolean silentchest, org.bukkit.block.Block block) {
EntityPlayer player = ((CraftPlayer) p).getHandle();
// Silent ender chest is API-only
if (silentchest && block.getType() == Material.ENDER_CHEST) {
p.openInventory(p.getEnderChest());
return true;
}
World world = player.world;
Object tile = world.getTileEntity(block.getX(), block.getY(), block.getZ());
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);
return true;
}
if (!(tile instanceof IInventory)) {
return false;
}
int id = world.getTypeId(block.getX(), block.getY(), block.getZ());
if (world.getTypeId(block.getX(), block.getY(), block.getZ() + 1) == id) {
tile = new InventoryLargeChest("Large chest", (IInventory) tile, (TileEntityChest) world.getTileEntity(block.getX(), block.getY(), block.getZ() + 1));
} else if (world.getTypeId(block.getX(), block.getY(), block.getZ() - 1) == id) {
tile = new InventoryLargeChest("Large chest", (TileEntityChest) world.getTileEntity(block.getX(), block.getY(), block.getZ() - 1), (IInventory) tile);
} else if (world.getTypeId(block.getX() + 1, block.getY(), block.getZ()) == id) {
tile = new InventoryLargeChest("Large chest", (IInventory) tile, (TileEntityChest) world.getTileEntity(block.getX() + 1, block.getY(), block.getZ()));
} else if (world.getTypeId(block.getX() - 1, block.getY(), block.getZ()) == id) {
tile = new InventoryLargeChest("Large chest", (TileEntityChest) world.getTileEntity(block.getX() - 1, block.getY(), block.getZ()), (IInventory) tile);
}
boolean returnValue = false;
if (!silentchest) {
player.openContainer((IInventory) tile);
returnValue = true;
} else {
try {
int windowId = 0;
try {
Field windowID = player.getClass().getDeclaredField("containerCounter");
windowID.setAccessible(true);
windowId = windowID.getInt(player);
windowId = windowId % 100 + 1;
windowID.setInt(player, windowId);
} catch (NoSuchFieldException e) {}
player.playerConnection.sendPacket(new Packet100OpenWindow(windowId, 0, ((IInventory) tile).getName(), ((IInventory) tile).getSize()));
player.activeContainer = new SilentContainerChest(player.inventory, ((IInventory) tile));
player.activeContainer.windowId = windowId;
player.activeContainer.addSlotListener(player);
returnValue = true;
} catch (Exception e) {
e.printStackTrace();
p.sendMessage(ChatColor.RED + "Error while sending silent chest.");
}
}
return returnValue;
}
}
@@ -1,79 +0,0 @@
/*
* Copyright (C) 2011-2014 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_4_6;
import com.lishid.openinv.internal.IInventoryAccess;
import com.lishid.openinv.internal.ISpecialEnderChest;
import com.lishid.openinv.internal.ISpecialPlayerInventory;
import com.lishid.openinv.internal.InternalAccessor;
import org.bukkit.inventory.Inventory;
// Volatile
import net.minecraft.server.v1_4_6.IInventory;
import org.bukkit.craftbukkit.v1_4_6.inventory.CraftInventory;
public class InventoryAccess implements IInventoryAccess {
@Override
public boolean isSpecialPlayerInventory(Inventory inventory) {
if (inventory instanceof CraftInventory) {
return ((CraftInventory) inventory).getInventory() instanceof ISpecialPlayerInventory;
}
return InternalAccessor.grabFieldOfTypeFromObject(IInventory.class, inventory) instanceof ISpecialPlayerInventory;
}
@Override
public ISpecialPlayerInventory getSpecialPlayerInventory(Inventory inventory) {
IInventory inv;
if (inventory instanceof CraftInventory) {
inv = ((CraftInventory) inventory).getInventory();
} else {
inv = InternalAccessor.grabFieldOfTypeFromObject(IInventory.class, inventory);
}
if (inv instanceof SpecialPlayerInventory) {
return (SpecialPlayerInventory) inv;
}
return null;
}
@Override
public boolean isSpecialEnderChest(Inventory inventory) {
if (inventory instanceof CraftInventory) {
return ((CraftInventory) inventory).getInventory() instanceof ISpecialEnderChest;
}
return InternalAccessor.grabFieldOfTypeFromObject(IInventory.class, inventory) instanceof ISpecialEnderChest;
}
@Override
public ISpecialEnderChest getSpecialEnderChest(Inventory inventory) {
IInventory inv;
if (inventory instanceof CraftInventory) {
inv = ((CraftInventory) inventory).getInventory();
} else {
inv = InternalAccessor.grabFieldOfTypeFromObject(IInventory.class, inventory);
}
if (inv instanceof SpecialEnderChest) {
return (SpecialEnderChest) inv;
}
return null;
}
}
@@ -1,62 +0,0 @@
/*
* Copyright (C) 2011-2014 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_4_6;
import com.lishid.openinv.internal.IPlayerDataManager;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
// Volatile
import net.minecraft.server.v1_4_6.EntityPlayer;
import net.minecraft.server.v1_4_6.MinecraftServer;
import net.minecraft.server.v1_4_6.PlayerInteractManager;
import org.bukkit.craftbukkit.v1_4_6.CraftServer;
public class PlayerDataManager implements IPlayerDataManager {
@Override
public Player loadPlayer(OfflinePlayer offline) {
// Ensure the player has data
if (offline == null || !offline.hasPlayedBefore()) {
return null;
}
MinecraftServer server = ((CraftServer) Bukkit.getServer()).getServer();
// Create an entity to load the player data
EntityPlayer entity = new EntityPlayer(server, server.getWorldServer(0), offline.getName(),
new PlayerInteractManager(server.getWorldServer(0)));
// Get the bukkit entity
Player target = (entity == null) ? null : entity.getBukkitEntity();
if (target != null) {
// Load data
target.loadData();
}
// Return the entity
return target;
}
@Override
public String getPlayerDataID(OfflinePlayer player) {
return player.getName();
}
}
@@ -1,46 +0,0 @@
/*
* Copyright (C) 2011-2014 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_4_6;
// Volatile
import net.minecraft.server.v1_4_6.ContainerChest;
import net.minecraft.server.v1_4_6.EntityHuman;
import net.minecraft.server.v1_4_6.IInventory;
import net.minecraft.server.v1_4_6.ItemStack;
import net.minecraft.server.v1_4_6.PlayerInventory;
public class SilentContainerChest extends ContainerChest {
public SilentContainerChest(IInventory i1, IInventory i2) {
super(i1, i2);
// Send close signal
i2.f();
}
@Override
public void b(EntityHuman entityHuman) {
// Don't send close signal twice, might screw up
PlayerInventory playerinventory = entityHuman.inventory;
if (playerinventory.getCarried() != null) {
ItemStack carried = playerinventory.getCarried();
playerinventory.setCarried(null);
entityHuman.drop(carried);
}
}
}
@@ -1,128 +0,0 @@
/*
* Copyright (C) 2011-2014 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_4_6;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import com.lishid.openinv.internal.ISpecialEnderChest;
import org.bukkit.entity.HumanEntity;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryHolder;
// Volatile
import net.minecraft.server.v1_4_6.IInventory;
import net.minecraft.server.v1_4_6.InventoryEnderChest;
import net.minecraft.server.v1_4_6.InventorySubcontainer;
import net.minecraft.server.v1_4_6.ItemStack;
import org.bukkit.craftbukkit.v1_4_6.entity.CraftHumanEntity;
import org.bukkit.craftbukkit.v1_4_6.entity.CraftPlayer;
import org.bukkit.craftbukkit.v1_4_6.inventory.CraftInventory;
public class SpecialEnderChest extends InventorySubcontainer implements IInventory, ISpecialEnderChest {
private final InventoryEnderChest enderChest;
private final CraftInventory inventory = new CraftInventory(this);
private final List<HumanEntity> transaction = new ArrayList<HumanEntity>();
private boolean playerOnline = false;
private CraftPlayer owner;
private int maxStack = MAX_STACK;
public SpecialEnderChest(Player p, Boolean online) {
super(((CraftPlayer) p).getHandle().getEnderChest().getName(),
((CraftPlayer) p).getHandle().getEnderChest().getSize());
CraftPlayer player = (CraftPlayer) p;
this.enderChest = player.getHandle().getEnderChest();
this.owner = player;
this.items = enderChest.getContents();
}
@Override
public Inventory getBukkitInventory() {
return inventory;
}
@Override
public void setPlayerOnline(Player player) {
if (!playerOnline) {
try {
owner = (CraftPlayer) player;
InventoryEnderChest playerEnderChest = owner.getHandle().getEnderChest();
Field field = playerEnderChest.getClass().getField("items");
field.setAccessible(true);
field.set(playerEnderChest, this.items);
} catch (Exception e) {}
playerOnline = true;
}
}
@Override
public void setPlayerOffline() {
playerOnline = false;
}
@Override
public boolean isInUse() {
return !this.getViewers().isEmpty();
}
@Override
public ItemStack[] getContents() {
return this.items;
}
@Override
public void onOpen(CraftHumanEntity who) {
transaction.add(who);
}
@Override
public void onClose(CraftHumanEntity who) {
transaction.remove(who);
}
@Override
public List<HumanEntity> getViewers() {
return transaction;
}
@Override
public InventoryHolder getOwner() {
return this.owner;
}
@Override
public void setMaxStackSize(int size) {
maxStack = size;
}
@Override
public int getMaxStackSize() {
return maxStack;
}
@Override
public void update() {
super.update();
enderChest.update();
}
}
@@ -1,229 +0,0 @@
/*
* Copyright (C) 2011-2014 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_4_6;
import com.lishid.openinv.internal.ISpecialPlayerInventory;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
// Volatile
import net.minecraft.server.v1_4_6.ItemStack;
import net.minecraft.server.v1_4_6.PlayerInventory;
import org.bukkit.craftbukkit.v1_4_6.entity.CraftPlayer;
import org.bukkit.craftbukkit.v1_4_6.inventory.CraftInventory;
public class SpecialPlayerInventory extends PlayerInventory implements ISpecialPlayerInventory {
private final ItemStack[] extra = new ItemStack[5];
private final CraftInventory inventory = new CraftInventory(this);
private boolean playerOnline = false;
public SpecialPlayerInventory(Player bukkitPlayer, Boolean online) {
super(((CraftPlayer) bukkitPlayer).getHandle());
this.playerOnline = online;
this.items = player.inventory.items;
this.armor = player.inventory.armor;
}
@Override
public Inventory getBukkitInventory() {
return inventory;
}
@Override
public void setPlayerOnline(Player player) {
if (!playerOnline) {
this.player = ((CraftPlayer) player).getHandle();
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].a(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);
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();
}
}
+67 -61
View File
@@ -7,51 +7,15 @@ OpenInv is a [Bukkit plugin](https://dev.bukkit.org/bukkit-plugins/openinv/) whi
- Cross-world support! Don't grant `OpenInv.crossworld`
- No self-opening! Don't grant `OpenInv.openself`
- Drop items as the player! Place items in the unused slots to the right of the armor to drop them
- **OpenEnder**: Open anyone's inventory, even if they're offline.
- **OpenEnder**: Open anyone's ender chest, even if they're offline.
- Read-only mode! No edits allowed! Don't grant `OpenInv.editender`
- Cross-world support! Don't grant `OpenInv.crossworld`
- No self-opening! Don't grant `OpenInv.openself`
- **SilentChest**: Open containers without displaying an animation or making sound.
- **AnyChest**: Open containers, even if blocked by ocelots or blocks.
- No opening others! Don't grant `OpenInv.openenderall`
- **SilentContainer**: Open containers without displaying an animation or making sound.
- **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>/anychest [check]</td>
<td>ac</td>
<td>Check or toggle the AnyChest function, allowing opening blocked containers.</td>
</tr>
<tr>
<td>/silentchest [check]</td>
<td>sc</td>
<td>Check or toggle the SilentChest function, allowing opening containers silently.</td>
</tr>
</table>
See [the wiki](https://github.com/Jikoo/OpenInv/wiki/Commands).
## Permissions
<table>
@@ -75,12 +39,20 @@ 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>
</tr>
<tr>
<td>OpenInv.editinv</td>
<td>OpenInv.editender</td>
<td>Required to make changes to open ender chests.</td>
</tr>
<tr>
@@ -103,37 +75,71 @@ OpenInv is a [Bukkit plugin](https://dev.bukkit.org/bukkit-plugins/openinv/) whi
<td>OpenInv.search</td>
<td>Required to use /searchinv and /searchender.</td>
</tr>
<tr>
<td>OpenInv.searchenchant</td>
<td>Required to use /searchenchant.</td>
</tr>
<tr>
<td>OpenInv.anychest</td>
<td>Required to use /anychest.</td>
</tr>
<tr>
<td>OpenInv.any.default</td>
<td>Cause AnyContainer to be enabled by default.</td>
</tr>
<tr>
<td>OpenInv.silent</td>
<td>Required to use /silentchest.</td>
<td>Required to use /silentcontainer.</td>
</tr>
<tr>
<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`
Compiling OpenInv for a specific version is very easy - just compile the correct module.
Compiling for a set of versions is slightly more complex. You'll need to use a profile for the versions you want to compile. Provided profiles are latest, modern (versions 1.8+), and all. For more information, check out the [official guide](http://maven.apache.org/guides/introduction/introduction-to-profiles.html).
## 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>
```
```xml
<dependencies>
<dependency>
<groupId>com.github.jikoo.OpenInv</groupId>
<artifactId>openinvapi</artifactId>
<version>${openinv.version}</version>
</dependency>
</dependencies>
```
Copyright (C) 2011-2014 lishid. All rights reserved.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, version 3.
### Compilation
To compile, the relevant Spigot jars must be installed in your local repository using the `install` plugin:
```shell
mvn install:install-file -Dpackaging=jar -Dfile=spigot-1.8-R0.1-SNAPSHOT.jar \
-DgroupId=org.spigotmc -DartifactId=spigot -Dversion=1.8-R0.1-SNAPSHOT
```
Note that BuildTools automatically installs produced files. If you use BuildTools to compile Spigot locally, you don't need to install it manually.
If you want to use Paper as a dependency, you can install it by executing PaperClip with the property `paperclip.install` set to true:
```shell
wget -O paperclip.jar https://papermc.io/api/v1/paper/1.16.5/latest/download
java -jar -Dpaperclip.install=true paperclip.jar
```
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
To compile for a single version, specify the NMS revision you are targeting: `mvn -pl <NMS module> -am clean install`
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
```
To compile for a set of versions, you'll need to use a profile. The only provided profile is `all`. Select a profile using the `-P` argument: `mvn clean package -am -P all`
For more information, check out the [official Maven guide](http://maven.apache.org/guides/introduction/introduction-to-profiles.html).
The final file is `target/OpenInv.jar`
+49
View File
@@ -0,0 +1,49 @@
<!--
~ 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/>.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>openinvparent</artifactId>
<groupId>com.lishid</groupId>
<version>4.1.9-SNAPSHOT</version>
</parent>
<artifactId>openinvapi</artifactId>
<name>OpenInvAPI</name>
<dependencies>
<dependency>
<artifactId>annotations</artifactId>
<groupId>org.jetbrains</groupId>
</dependency>
<dependency>
<artifactId>spigot-api</artifactId>
<groupId>org.spigotmc</groupId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
@@ -0,0 +1,310 @@
/*
* 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;
import com.lishid.openinv.internal.IAnySilentContainer;
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 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;
import org.bukkit.entity.Player;
import org.bukkit.inventory.InventoryView;
import org.bukkit.plugin.Plugin;
import org.jetbrains.annotations.NotNull;
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.
*
* @return false unless configured otherwise
*/
boolean disableSaving();
/**
* Gets the active ISilentContainer implementation.
*
* @return the ISilentContainer
* @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
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
*/
boolean getPlayerAnyChestStatus(@NotNull OfflinePlayer player);
/**
* 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.
*
* @param offline the OfflinePlayer
* @return the identifier
* @throws IllegalStateException if the server version is unsupported
*/
default @NotNull String getPlayerID(@NotNull OfflinePlayer offline) {
return offline.getUniqueId().toString();
}
/**
* Gets a player's SilentChest setting.
*
* @param offline the OfflinePlayer
* @return true if SilentChest is enabled
* @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
*/
@NotNull ISpecialEnderChest getSpecialEnderChest(@NotNull Player player, boolean online) throws InstantiationException;
/**
* Gets an ISpecialPlayerInventory for the given Player.
*
* @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
*/
@NotNull ISpecialPlayerInventory getSpecialInventory(@NotNull Player player, boolean online) throws InstantiationException;
/**
* Checks if the server version is supported by OpenInv.
*
* @return true if the server version is supported
*/
boolean isSupportedVersion();
/**
* Load a Player from an OfflinePlayer. May return null under some circumstances.
*
* @param offline the OfflinePlayer to load a Player for
* @return the Player, or null
* @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
* 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
*/
default @Nullable OfflinePlayer matchPlayer(@NotNull String name) {
// Warn if called on the main thread - if we resort to searching offline players, this may take several seconds.
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().log(Level.WARNING, new Throwable("Current stack trace"), () -> "Current stack trace");
}
OfflinePlayer player;
try {
UUID uuid = UUID.fromString(name);
player = Bukkit.getOfflinePlayer(uuid);
// Ensure player is a real player, otherwise return null
if (player.hasPlayedBefore() || player.isOnline()) {
return player;
}
} catch (IllegalArgumentException ignored) {
// Not a UUID
}
player = Bukkit.getServer().getPlayerExact(name);
if (player != null) {
return player;
}
player = Bukkit.getServer().getOfflinePlayer(name);
if (player.hasPlayedBefore()) {
return player;
}
player = Bukkit.getServer().getPlayer(name);
if (player != null) {
return player;
}
float bestMatch = 0;
for (OfflinePlayer offline : Bukkit.getServer().getOfflinePlayers()) {
if (offline.getName() == null) {
// Loaded by UUID only, name has never been looked up.
continue;
}
float currentMatch = StringMetric.compareJaroWinkler(name, offline.getName());
if (currentMatch == 1.0F) {
return offline;
}
if (currentMatch > bestMatch) {
bestMatch = currentMatch;
player = offline;
}
}
// Only null if no players have played ever, otherwise even the worst match will do.
return player;
}
/**
* Open an ISpecialInventory for a Player.
*
* @param player the Player
* @param inventory the ISpecialInventory
* @return the InventoryView for the opened ISpecialInventory
*/
@Nullable InventoryView openInventory(@NotNull Player player, @NotNull ISpecialInventory inventory);
/**
* 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 or not they show is based on language settings.
*/
@Deprecated
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 SilentChest.
*
* @return true unless configured otherwise
* @deprecated OpenInv uses action bar chat for notifications. Whether or not they show is based on language settings.
*/
@Deprecated
default boolean notifySilentChest() {
return true;
}
/**
* Mark a Player as no longer in use by a Plugin to allow OpenInv to remove it from the cache
* when eligible.
*
* @param player the Player
* @param plugin the Plugin no longer holding a reference to the Player
* @throws IllegalStateException if the server version is unsupported
*/
void releasePlayer(@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.
*
* @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
*/
void unload(@NotNull OfflinePlayer offline);
Logger getLogger();
}
@@ -0,0 +1,157 @@
/*
* 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.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.
*
* @param player the Player opening the container
* @param silent whether the container's noise is to be silenced
* @param block the Block
* @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.
*
* @param player the Player closing a container
*/
void deactivateContainer(@NotNull Player player);
/**
* @deprecated use {@link #isAnyContainerNeeded(Block)}
* @param player the player opening the container
* @param block the block
* @return true if the container is blocked
*/
@Deprecated
default boolean isAnyContainerNeeded(@NotNull Player player, @NotNull Block block) {
return isAnyContainerNeeded(block);
}
/**
* Checks if the container at the given coordinates is blocked.
*
* @param block the Block
* @return true if the container is blocked
*/
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) {
if (isShulkerBlocked((ShulkerBox) blockState)) {
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) blockData).getType() == Chest.Type.SINGLE) {
return false;
}
Chest chest = (Chest) blockData;
int ordinal = (chest.getFacing().ordinal() + 4 + (chest.getType() == Chest.Type.RIGHT ? -1 : 1)) % 4;
BlockFace relativeFace = BlockFace.values()[ordinal];
org.bukkit.block.Block relative = block.getRelative(relativeFace);
if (relative.getType() != block.getType()) {
return false;
}
BlockData relativeData = relative.getBlockData();
if (!(relativeData instanceof Chest)) {
return false;
}
Chest relativeChest = (Chest) relativeData;
if (relativeChest.getFacing() != chest.getFacing()
|| relativeChest.getType() != (chest.getType() == Chest.Type.RIGHT ? Chest.Type.LEFT : Chest.Type.RIGHT)) {
return false;
}
return isChestBlocked(relative);
}
boolean isShulkerBlocked(ShulkerBox block);
/**
* Determine whether or not a chest is blocked.
*
* @param chest the chest block
* @return true if the chest block cannot be opened under ordinary circumstances
*/
default boolean isChestBlocked(Block chest) {
org.bukkit.block.Block relative = chest.getRelative(0, 1, 0);
return relative.getType().isOccluding()
|| chest.getWorld().getNearbyEntities(BoundingBox.of(relative), entity -> entity instanceof Cat).size() > 0;
}
/**
* Checks if the given block is a container which can be unblocked or silenced.
*
* @param block the BlockState
* @return true if the Block 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,15 +1,15 @@
/*
* Copyright (C) 2011-2014 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
* the Free Software Foundation, version 3.
*
* 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
* 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/>.
*/
@@ -17,41 +17,48 @@
package com.lishid.openinv.internal;
import org.bukkit.inventory.Inventory;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@Deprecated
public interface IInventoryAccess {
/**
* Check if an Inventory is an ISpecialPlayerInventory implementation.
*
* @param inventory the Inventory
* @return true if the Inventory is backed by an ISpecialPlayerInventory
*/
public boolean isSpecialPlayerInventory(Inventory 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
*/
public ISpecialPlayerInventory getSpecialPlayerInventory(Inventory inventory);
/**
* Check if an Inventory is an ISpecialEnderChest implementation.
*
* @param inventory the Inventory
* @return true if the Inventory is backed by an ISpecialEnderChest
*/
public boolean isSpecialEnderChest(Inventory inventory);
/**
* 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
*/
public ISpecialEnderChest getSpecialEnderChest(Inventory inventory);
@Deprecated
@Nullable ISpecialEnderChest getSpecialEnderChest(@NotNull Inventory 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
@Nullable ISpecialPlayerInventory getSpecialPlayerInventory(@NotNull Inventory inventory);
/**
* Check if an Inventory is an ISpecialEnderChest implementation.
*
* @param inventory the Inventory
* @return true if the Inventory is backed by an ISpecialEnderChest
*/
@Deprecated
boolean isSpecialEnderChest(@NotNull Inventory inventory);
/**
* Check if an Inventory is an ISpecialPlayerInventory implementation.
*
* @param inventory the Inventory
* @return true if the Inventory is backed by an ISpecialPlayerInventory
*/
@Deprecated
boolean isSpecialPlayerInventory(@NotNull Inventory inventory);
}
@@ -0,0 +1,21 @@
/*
* 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.internal;
public interface ISpecialEnderChest extends ISpecialInventory {
}
@@ -0,0 +1,59 @@
/*
* 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.internal;
import org.bukkit.entity.HumanEntity;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import org.jetbrains.annotations.NotNull;
public interface ISpecialInventory {
/**
* Gets the Inventory associated with this ISpecialInventory.
*
* @return the Inventory
*/
@NotNull Inventory getBukkitInventory();
/**
* Sets the Player associated with this ISpecialInventory online.
*
* @param player the Player coming online
*/
void setPlayerOnline(@NotNull Player player);
/**
* Sets the Player associated with this ISpecialInventory offline.
*/
void setPlayerOffline();
/**
* Gets whether or not this ISpecialInventory is in use.
*
* @return true if the ISpecialInventory is in use
*/
boolean isInUse();
/**
* Gets the Player associated with this ISpecialInventory.
*
* @return the HumanEntity
*/
@NotNull HumanEntity getPlayer();
}
@@ -0,0 +1,21 @@
/*
* 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.internal;
public interface ISpecialPlayerInventory extends ISpecialInventory {
}
@@ -0,0 +1,117 @@
/*
* 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 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.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 {
private static Class<?> craftInventory = null;
private static Method getInventory = null;
static {
String packageName = Bukkit.getServer().getClass().getPackage().getName();
try {
craftInventory = Class.forName(packageName + ".inventory.CraftInventory");
} catch (ClassNotFoundException ignored) {}
try {
getInventory = craftInventory.getDeclaredMethod("getInventory");
} catch (NoSuchMethodException ignored) {}
}
/**
* @deprecated use {@link #isUsable()}
*/
@Deprecated
public static boolean isUseable() {
return isUsable();
}
public static boolean isUsable() {
return craftInventory != null && getInventory != null;
}
public static boolean isPlayerInventory(@NotNull Inventory inventory) {
return getPlayerInventory(inventory) != null;
}
public static @Nullable ISpecialPlayerInventory getPlayerInventory(@NotNull Inventory inventory) {
return getSpecialInventory(ISpecialPlayerInventory.class, inventory);
}
public static boolean isEnderChest(@NotNull Inventory inventory) {
return getEnderChest(inventory) != null;
}
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 (craftInventory != null && getInventory != null && craftInventory.isAssignableFrom(inventory.getClass())) {
try {
inv = getInventory.invoke(inventory);
if (expected.isInstance(inv)) {
return expected.cast(inv);
}
} catch (ReflectiveOperationException ignored) {}
}
// Use reflection to find the IInventory
inv = ReflectionHelper.grabObjectByType(inventory, expected);
if (expected.isInstance(inv)) {
return expected.cast(inv);
}
return null;
}
@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;
}
}
@@ -0,0 +1,150 @@
/*
* 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;
public class StringMetric {
public static float compareJaroWinkler(String a, String b) {
final float jaroScore = compareJaro(a, b);
if (jaroScore < (float) 0.7) {
return jaroScore;
}
String prefix = commonPrefix(a, b);
int prefixLength = Math.min(prefix.codePointCount(0, prefix.length()), 4);
return jaroScore + (prefixLength * (float) 0.1 * (1.0f - jaroScore));
}
private static float compareJaro(String a, String b) {
if (a.isEmpty() && b.isEmpty()) {
return 1.0f;
}
if (a.isEmpty() || b.isEmpty()) {
return 0.0f;
}
final int[] charsA = a.codePoints().toArray();
final int[] charsB = b.codePoints().toArray();
// Intentional integer division to round down.
final int halfLength = Math.max(0, Math.max(charsA.length, charsB.length) / 2 - 1);
final int[] commonA = getCommonCodePoints(charsA, charsB, halfLength);
final int[] commonB = getCommonCodePoints(charsB, charsA, halfLength);
// commonA and commonB will always contain the same multi-set of
// characters. Because getCommonCharacters has been optimized, commonA
// and commonB are -1-padded. So in this loop we count transposition
// and use commonCharacters to determine the length of the multi-set.
float transpositions = 0;
int commonCharacters = 0;
for (int length = commonA.length; commonCharacters < length
&& commonA[commonCharacters] > -1; commonCharacters++) {
if (commonA[commonCharacters] != commonB[commonCharacters]) {
transpositions++;
}
}
if (commonCharacters == 0) {
return 0.0f;
}
float aCommonRatio = commonCharacters / (float) charsA.length;
float bCommonRatio = commonCharacters / (float) charsB.length;
float transpositionRatio = (commonCharacters - transpositions / 2.0f) / commonCharacters;
return (aCommonRatio + bCommonRatio + transpositionRatio) / 3.0f;
}
/*
* Returns an array of code points from a within b. A character in b is
* counted as common when it is within separation distance from the position
* in a.
*/
private static int[] getCommonCodePoints(final int[] charsA, final int[] charsB, final int separation) {
final int[] common = new int[Math.min(charsA.length, charsB.length)];
final boolean[] matched = new boolean[charsB.length];
// Iterate of string a and find all characters that occur in b within
// the separation distance. Mark any matches found to avoid
// duplicate matchings.
int commonIndex = 0;
for (int i = 0, length = charsA.length; i < length; i++) {
final int character = charsA[i];
final int index = indexOf(character, charsB, i - separation, i
+ separation + 1, matched);
if (index > -1) {
common[commonIndex++] = character;
matched[index] = true;
}
}
if (commonIndex < common.length) {
common[commonIndex] = -1;
}
// Both invocations will yield the same multi-set terminated by -1, so
// they can be compared for transposition without making a copy.
return common;
}
/*
* Search for code point in buffer starting at fromIndex to toIndex - 1.
*
* Returns -1 when not found.
*/
private static int indexOf(int character, int[] buffer, int fromIndex, int toIndex, boolean[] matched) {
// compare char with range of characters to either side
for (int j = Math.max(0, fromIndex), length = Math.min(toIndex, buffer.length); j < length; j++) {
// check if found
if (buffer[j] == character && !matched[j]) {
return j;
}
}
return -1;
}
private static String commonPrefix(CharSequence a, CharSequence b) {
int maxPrefixLength = Math.min(a.length(), b.length());
int p;
p = 0;
while (p < maxPrefixLength && a.charAt(p) == b.charAt(p)) {
++p;
}
if (validSurrogatePairAt(a, p - 1) || validSurrogatePairAt(b, p - 1)) {
--p;
}
return a.subSequence(0, p).toString();
}
private static boolean validSurrogatePairAt(CharSequence string, int index) {
return index >= 0 && index <= string.length() - 2 && Character.isHighSurrogate(string.charAt(index)) && Character.isLowSurrogate(string.charAt(index + 1));
}
private StringMetric(){}
}
+30 -180
View File
@@ -1,172 +1,32 @@
<!--
~ 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/>.
-->
<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>openinv</artifactId>
<version>1.0-SNAPSHOT</version>
<artifactId>openinvparent</artifactId>
<version>4.1.9-SNAPSHOT</version>
</parent>
<artifactId>openinvassembly</artifactId>
<name>OpenInvAssembly</name>
<profiles>
<profile>
<id>latest</id>
<dependencies>
<dependency>
<groupId>com.lishid</groupId>
<artifactId>openinvadapter1_11_R1</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</profile>
<profile>
<id>modern</id>
<dependencies>
<dependency>
<groupId>com.lishid</groupId>
<artifactId>openinvadapter1_8_R1</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.lishid</groupId>
<artifactId>openinvadapter1_8_R2</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.lishid</groupId>
<artifactId>openinvadapter1_8_R3</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.lishid</groupId>
<artifactId>openinvadapter1_9_R1</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.lishid</groupId>
<artifactId>openinvadapter1_9_R2</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.lishid</groupId>
<artifactId>openinvadapter1_10_R1</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.lishid</groupId>
<artifactId>openinvadapter1_11_R1</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</profile>
<profile>
<id>all</id>
<dependencies>
<dependency>
<groupId>com.lishid</groupId>
<artifactId>openinvadapter1_4_5</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.lishid</groupId>
<artifactId>openinvadapter1_4_6</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.lishid</groupId>
<artifactId>openinvadapter1_4_R1</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.lishid</groupId>
<artifactId>openinvadapter1_5_R2</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.lishid</groupId>
<artifactId>openinvadapter1_5_R3</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.lishid</groupId>
<artifactId>openinvadapter1_6_R1</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.lishid</groupId>
<artifactId>openinvadapter1_6_R2</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.lishid</groupId>
<artifactId>openinvadapter1_6_R3</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.lishid</groupId>
<artifactId>openinvadapter1_7_R1</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.lishid</groupId>
<artifactId>openinvadapter1_7_R2</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.lishid</groupId>
<artifactId>openinvadapter1_7_R3</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.lishid</groupId>
<artifactId>openinvadapter1_7_R4</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.lishid</groupId>
<artifactId>openinvadapter1_8_R1</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.lishid</groupId>
<artifactId>openinvadapter1_8_R2</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.lishid</groupId>
<artifactId>openinvadapter1_8_R3</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.lishid</groupId>
<artifactId>openinvadapter1_9_R1</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.lishid</groupId>
<artifactId>openinvadapter1_9_R2</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.lishid</groupId>
<artifactId>openinvadapter1_10_R1</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.lishid</groupId>
<artifactId>openinvadapter1_11_R1</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</profile>
</profiles>
<packaging>pom</packaging>
<build>
<directory>../target</directory>
@@ -174,35 +34,25 @@
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.4.3</version>
<configuration>
<!--
~ This keeps the final file clean, but may cause issues for people not familiar with the setup.
~ If you're having trouble with the final product missing files, remove or tweak this configuration.
-->
<minimizeJar>true</minimizeJar>
<filters>
<filter>
<artifact>com.lishid:*</artifact>
<includes>
<include>com/lishid/openinv/**/*</include>
<include>plugin.yml</include>
</includes>
</filter>
</filters>
</configuration>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<id>reactor-uberjar</id>
<phase>package</phase>
<goals>
<goal>shade</goal>
<goal>single</goal>
</goals>
<configuration>
<appendAssemblyId>false</appendAssemblyId>
<descriptors>
<descriptor>src/assembly/reactor-uberjar.xml</descriptor>
</descriptors>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
</project>
+54
View File
@@ -0,0 +1,54 @@
<!--
~ 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/>.
-->
<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>
<formats>
<format>jar</format>
</formats>
<includeBaseDirectory>false</includeBaseDirectory>
<moduleSets>
<moduleSet>
<useAllReactorProjects>true</useAllReactorProjects>
<binaries>
<outputDirectory>/</outputDirectory>
<unpack>true</unpack>
<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>
</moduleSets>
</assembly>
-23
View File
@@ -1,23 +0,0 @@
<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>openinv</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>openinvinternal</artifactId>
<name>OpenInvInternal</name>
<dependencies>
<dependency>
<groupId>org.bukkit</groupId>
<artifactId>bukkit</artifactId>
<version>1.4.5-R1.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
@@ -1,37 +0,0 @@
package com.lishid.openinv.internal;
import org.bukkit.block.Block;
import org.bukkit.entity.Player;
public interface IAnySilentContainer {
/**
* Checks if the given block is a container which can be unblocked or silenced.
*
* @param block the BlockState
* @return true if the Block is a supported container
*/
public boolean isAnySilentContainer(Block block);
/**
* Checks if the container at the given coordinates is blocked.
*
* @param player the Player opening the container
* @param block the Block
* @return true if the container is blocked
*/
public boolean isAnyContainerNeeded(Player player, Block block);
/**
* 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.
*
* @param player
* @param silentchest whether the container's noise is to be silenced
* @param block the Block
* @return true if the container can be opened
*/
public boolean activateContainer(Player player, boolean silentchest, Block block);
}
@@ -1,42 +0,0 @@
/*
* Copyright (C) 2011-2014 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 org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
public interface IPlayerDataManager {
/**
* Loads a Player for an OfflinePlayer.
* </p>
* This method is potentially blocking, and should not be called on the main thread.
*
* @param offline
* @return
*/
public Player loadPlayer(OfflinePlayer offline);
/**
* Gets a unique identifying string for an OfflinePlayer.
*
* @param player
* @return
*/
public String getPlayerDataID(OfflinePlayer player);
}
@@ -1,50 +0,0 @@
/*
* Copyright (C) 2011-2014 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 org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
public interface ISpecialEnderChest {
/**
* Gets the Inventory associated with this ISpecialEnderChest.
*
* @return the Inventory
*/
public Inventory getBukkitInventory();
/**
* Sets the Player associated with this ISpecialEnderChest online.
*
* @param player the Player coming online
*/
public void setPlayerOnline(Player player);
/**
* Sets the Player associated with this ISpecialEnderChest offline.
*/
public void setPlayerOffline();
/**
* Gets whether or not this ISpecialEnderChest is in use.
*
* @return true if the ISpecialEnderChest is in use
*/
public boolean isInUse();
}
@@ -1,50 +0,0 @@
/*
* Copyright (C) 2011-2014 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 org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
public interface ISpecialPlayerInventory {
/**
* Gets the Inventory associated with this ISpecialPlayerInventory.
*
* @return the Inventory
*/
public Inventory getBukkitInventory();
/**
* Sets the Player associated with this ISpecialPlayerInventory online.
*
* @param player the Player coming online
*/
public void setPlayerOnline(Player player);
/**
* Sets the Player associated with this ISpecialPlayerInventory offline.
*/
public void setPlayerOffline();
/**
* Gets whether or not this ISpecialPlayerInventory is in use.
*
* @return true if the ISpecialPlayerInventory is in use
*/
public boolean isInUse();
}
@@ -1,180 +0,0 @@
/*
* Copyright (C) 2011-2014 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 java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
public class InternalAccessor {
private final Plugin plugin;
private final String version;
private boolean supported = false;
public InternalAccessor(Plugin plugin) {
this.plugin = plugin;
String packageName = plugin.getServer().getClass().getPackage().getName();
version = packageName.substring(packageName.lastIndexOf('.') + 1);
try {
Class.forName("com.lishid.openinv.internal." + version + ".PlayerDataManager");
supported = true;
} catch (Exception e) {}
}
/**
* Gets the server implementation version. If not initialized, returns the string "null"
* instead.
*
* @return the version, or "null"
*/
public String getVersion() {
return this.version != null ? this.version : "null";
}
/**
* Checks if the server implementation is supported.
*
* @return true if initialized for a supported server version
*/
public boolean isSupported() {
return this.supported;
}
/**
* Creates an instance of the IPlayerDataManager implementation for the current server version,
* or null if unsupported.
*
* @return the IPlayerDataManager
*/
public IPlayerDataManager newPlayerDataManager() {
return createObject(IPlayerDataManager.class, "PlayerDataManager");
}
/**
* Creates an instance of the IInventoryAccess implementation for the current server version, or
* null if unsupported.
*
* @return the IInventoryAccess
*/
public IInventoryAccess newInventoryAccess() {
return createObject(IInventoryAccess.class, "InventoryAccess");
}
/**
* Creates an instance of the IAnySilentContainer implementation for the current server version,
* or null if unsupported.
*
* @return the IAnySilentContainer
*/
public IAnySilentContainer newAnySilentContainer() {
return createObject(IAnySilentContainer.class, "AnySilentContainer");
}
/**
* Creates an instance of the ISpecialPlayerInventory implementation for the given Player, or
* null if the current version is unsupported.
*
* @param player the Player
* @param online true if the Player is online
* @return the ISpecialPlayerInventory created
*/
public ISpecialPlayerInventory newSpecialPlayerInventory(Player player, boolean online) {
return createObject(ISpecialPlayerInventory.class, "SpecialPlayerInventory", player, online);
}
/**
* Creates an instance of the ISpecialEnderChest implementation for the given Player, or
* null if the current version is unsupported.
*
* @param player the Player
* @param online true if the Player is online
* @return the ISpecialEnderChest created
*/
public ISpecialEnderChest newSpecialEnderChest(Player player, boolean online) {
return createObject(ISpecialEnderChest.class, "SpecialEnderChest", player, online);
}
private <T> T createObject(Class<? extends T> assignableClass, String className, Object... params) {
try {
// Check if internal versioned class exists
Class<?> internalClass = Class.forName("com.lishid.openinv.internal." + version + "." + className);
if (!assignableClass.isAssignableFrom(internalClass)) {
plugin.getLogger().warning("Found class " + internalClass.getName() + " but cannot cast to " + assignableClass.getName());
return null;
}
// Quick return: no parameters, no need to fiddle about finding the correct constructor.
if (params.length == 0) {
return assignableClass.cast(internalClass.getConstructor().newInstance());
}
// Search constructors for one matching the given parameters
nextConstructor: for (Constructor<?> constructor : internalClass.getConstructors()) {
Class<?>[] requiredClasses = constructor.getParameterTypes();
if (requiredClasses.length != params.length) {
continue;
}
for (int i = 0; i < params.length; ++i) {
if (!requiredClasses[i].isAssignableFrom(params[i].getClass())) {
continue nextConstructor;
}
}
return assignableClass.cast(constructor.newInstance(params));
}
StringBuilder message = new StringBuilder("Found class ").append(internalClass.getName())
.append(" but cannot find any matching constructors for [");
for (Object object : params) {
message.append(object.getClass().getName()).append(", ");
}
if (params.length > 0) {
message.delete(message.length() - 2, message.length());
}
plugin.getLogger().warning(message.append(']').toString());
} catch (Exception e) {
plugin.getLogger().warning("OpenInv encountered an error with the CraftBukkit version \"" + version + "\". Please look for an updated version of OpenInv.");
e.printStackTrace();
}
return null;
}
public static <T> T grabFieldOfTypeFromObject(Class<T> type, 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;
}
}
+105
View File
@@ -0,0 +1,105 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ 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/>.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>openinvparent</artifactId>
<groupId>com.lishid</groupId>
<relativePath>../../pom.xml</relativePath>
<version>4.1.9-SNAPSHOT</version>
</parent>
<artifactId>openinvadapter1_17_R1</artifactId>
<name>OpenInvAdapter1_17_R1</name>
<properties>
<maven.compiler.source>16</maven.compiler.source>
<maven.compiler.target>16</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<artifactId>spigot</artifactId>
<groupId>org.spigotmc</groupId>
<scope>provided</scope>
<version>1.17.1-R0.1-SNAPSHOT</version>
<classifier>remapped-mojang</classifier>
</dependency>
<dependency>
<artifactId>openinvapi</artifactId>
<groupId>com.lishid</groupId>
</dependency>
<dependency>
<artifactId>openinvplugincore</artifactId>
<groupId>com.lishid</groupId>
</dependency>
<dependency>
<artifactId>annotations</artifactId>
<groupId>org.jetbrains</groupId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-shade-plugin</artifactId>
<configuration>
<minimizeJar>true</minimizeJar>
</configuration>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
</plugin>
<plugin>
<groupId>net.md-5</groupId>
<artifactId>specialsource-maven-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>remap</goal>
</goals>
<id>remap-obf</id>
<configuration>
<srgIn>org.spigotmc:minecraft-server:1.17.1-R0.1-SNAPSHOT:txt:maps-mojang</srgIn>
<reverse>true</reverse>
<remappedDependencies>org.spigotmc:spigot:1.17.1-R0.1-SNAPSHOT: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:1.17.1-R0.1-SNAPSHOT:csrg:maps-spigot</srgIn>
<remappedDependencies>org.spigotmc:spigot:1.17.1-R0.1-SNAPSHOT:jar:remapped-obf</remappedDependencies>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
@@ -0,0 +1,277 @@
/*
* 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.internal.v1_17_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.TextComponent;
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_17_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(ShulkerBox box) {
org.bukkit.World bukkitWorld = box.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(box.getX(), box.getY(), box.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.BlockEnderChest
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 TextComponent("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: check if chest is blocked
Optional<MenuProvider> menuOptional = chestBlock.combine(blockState, level, blockPos, false).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);
// Close container - note that this is very different from ServerPlayer#closeContainer!
// Triggering event must not be re-called or we'll enter an infinite loop.
player.doCloseContainer();
// 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-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.internal.v1_17_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_17_R1.CraftServer;
import org.bukkit.craftbukkit.v1_17_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());
}
}
}
@@ -0,0 +1,230 @@
/*
* 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.internal.v1_17_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.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_17_R1.CraftServer;
import org.bukkit.craftbukkit.v1_17_R1.entity.CraftPlayer;
import org.bukkit.craftbukkit.v1_17_R1.event.CraftEventFactory;
import org.bukkit.craftbukkit.v1_17_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.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);
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,344 @@
/*
* 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.internal.v1_17_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_17_R1.entity.CraftHumanEntity;
import org.bukkit.craftbukkit.v1_17_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 boolean isInUse() {
return !this.getViewers().isEmpty();
}
@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,769 @@
/*
* 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.internal.v1_17_R1;
import com.google.common.collect.ImmutableList;
import com.lishid.openinv.internal.ISpecialPlayerInventory;
import java.util.Collection;
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.Tag;
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_17_R1.entity.CraftHumanEntity;
import org.bukkit.craftbukkit.v1_17_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 boolean isInUse() {
return !this.getViewers().isEmpty();
}
@Override
public @NotNull HumanEntity getPlayer() {
return this.player.getBukkitEntity();
}
private @NotNull ItemStack getRawItem(int i) {
return super.getItem(i);
}
private void setRawItem(int i, @NotNull ItemStack itemStack) {
super.setItem(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(Tag<Item> tag) {
return contains(itemStack -> !itemStack.isEmpty() && itemStack.is(tag));
}
@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);
}
}
+111
View File
@@ -0,0 +1,111 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ 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/>.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>openinvparent</artifactId>
<groupId>com.lishid</groupId>
<relativePath>../../pom.xml</relativePath>
<version>4.1.9-SNAPSHOT</version>
</parent>
<artifactId>openinvadapter1_18_R1</artifactId>
<name>OpenInvAdapter1_18_R1</name>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<spigot.version>1.18-pre5-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>
<groupId>org.spigotmc</groupId>
<scope>provided</scope>
<version>${spigot.version}</version>
<classifier>remapped-mojang</classifier>
</dependency>
<dependency>
<artifactId>openinvapi</artifactId>
<groupId>com.lishid</groupId>
</dependency>
<dependency>
<artifactId>openinvplugincore</artifactId>
<groupId>com.lishid</groupId>
</dependency>
<dependency>
<artifactId>annotations</artifactId>
<groupId>org.jetbrains</groupId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-shade-plugin</artifactId>
<configuration>
<minimizeJar>true</minimizeJar>
</configuration>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
</plugin>
<plugin>
<groupId>net.md-5</groupId>
<artifactId>specialsource-maven-plugin</artifactId>
<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>
</build>
</project>
@@ -0,0 +1,277 @@
/*
* 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.internal.v1_18_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.TextComponent;
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_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(ShulkerBox box) {
org.bukkit.World bukkitWorld = box.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(box.getX(), box.getY(), box.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.BlockEnderChest
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 TextComponent("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: check if chest is blocked
Optional<MenuProvider> menuOptional = chestBlock.combine(blockState, level, blockPos, false).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);
// Close container - note that this is very different from ServerPlayer#closeContainer!
// Triggering event must not be re-called or we'll enter an infinite loop.
player.doCloseContainer();
// 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-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.internal.v1_18_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_18_R1.CraftServer;
import org.bukkit.craftbukkit.v1_18_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());
}
}
}
@@ -0,0 +1,230 @@
/*
* 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.internal.v1_18_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.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_R1.CraftServer;
import org.bukkit.craftbukkit.v1_18_R1.entity.CraftPlayer;
import org.bukkit.craftbukkit.v1_18_R1.event.CraftEventFactory;
import org.bukkit.craftbukkit.v1_18_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.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);
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,344 @@
/*
* 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.internal.v1_18_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_18_R1.entity.CraftHumanEntity;
import org.bukkit.craftbukkit.v1_18_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 boolean isInUse() {
return !this.getViewers().isEmpty();
}
@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,769 @@
/*
* 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.internal.v1_18_R1;
import com.google.common.collect.ImmutableList;
import com.lishid.openinv.internal.ISpecialPlayerInventory;
import java.util.Collection;
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.Tag;
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_R1.entity.CraftHumanEntity;
import org.bukkit.craftbukkit.v1_18_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 boolean isInUse() {
return !this.getViewers().isEmpty();
}
@Override
public @NotNull HumanEntity getPlayer() {
return this.player.getBukkitEntity();
}
private @NotNull ItemStack getRawItem(int i) {
return super.getItem(i);
}
private void setRawItem(int i, @NotNull ItemStack itemStack) {
super.setItem(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(Tag<Item> tag) {
return contains(itemStack -> !itemStack.isEmpty() && itemStack.is(tag));
}
@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);
}
}
+41 -12
View File
@@ -1,28 +1,48 @@
<!--
~ 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/>.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>openinvparent</artifactId>
<groupId>com.lishid</groupId>
<artifactId>openinv</artifactId>
<version>1.0-SNAPSHOT</version>
<version>4.1.9-SNAPSHOT</version>
</parent>
<artifactId>openinvplugin</artifactId>
<artifactId>openinvplugincore</artifactId>
<name>OpenInvPlugin</name>
<dependencies>
<dependency>
<groupId>org.bukkit</groupId>
<artifactId>bukkit</artifactId>
<!-- Really should use 1.4.5-R1.0, but the InventoryDragEvent doesn't exist. TODO separate module? -->
<version>1.11-R0.1-SNAPSHOT</version>
<scope>provided</scope>
<artifactId>openinvapi</artifactId>
<groupId>com.lishid</groupId>
</dependency>
<dependency>
<groupId>com.lishid</groupId>
<artifactId>openinvinternal</artifactId>
<version>1.0-SNAPSHOT</version>
<artifactId>annotations</artifactId>
<groupId>org.jetbrains</groupId>
</dependency>
<dependency>
<artifactId>paper-api</artifactId>
<groupId>io.papermc.paper</groupId>
</dependency>
<dependency>
<artifactId>planarwrappers</artifactId>
<groupId>com.github.jikoo</groupId>
</dependency>
</dependencies>
@@ -33,6 +53,15 @@
<filtering>true</filtering>
</resource>
</resources>
<plugins>
<plugin>
<artifactId>maven-shade-plugin</artifactId>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
</project>
@@ -1,156 +1,343 @@
/*
* Copyright (C) 2011-2014 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
* the Free Software Foundation, version 3.
*
* 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
* 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.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import com.lishid.openinv.commands.ContainerSettingCommand;
import com.lishid.openinv.commands.OpenInvCommand;
import com.lishid.openinv.commands.SearchCommand;
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.InventoryListener;
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.InternalAccessor;
import com.lishid.openinv.util.LanguageManager;
import com.lishid.openinv.util.Permissions;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.Objects;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import com.lishid.openinv.commands.AnyChestPluginCommand;
import com.lishid.openinv.commands.OpenEnderPluginCommand;
import com.lishid.openinv.commands.OpenInvPluginCommand;
import com.lishid.openinv.commands.SearchInvPluginCommand;
import com.lishid.openinv.commands.SilentChestPluginCommand;
import com.lishid.openinv.internal.IAnySilentContainer;
import com.lishid.openinv.internal.IInventoryAccess;
import com.lishid.openinv.internal.IPlayerDataManager;
import com.lishid.openinv.internal.ISpecialEnderChest;
import com.lishid.openinv.internal.ISpecialPlayerInventory;
import com.lishid.openinv.internal.InternalAccessor;
import com.lishid.openinv.util.Cache;
import com.lishid.openinv.util.Function;
import java.util.function.Consumer;
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.configuration.file.FileConfiguration;
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;
/**
* Open other player's inventory
*
*
* @author lishid
*/
public class OpenInv extends JavaPlugin {
public class OpenInv extends JavaPlugin implements IOpenInv {
private final Map<String, ISpecialPlayerInventory> inventories = new HashMap<String, ISpecialPlayerInventory>();
private final Map<String, ISpecialEnderChest> enderChests = new HashMap<String, ISpecialEnderChest>();
private final Cache<String, Player> playerCache = new Cache<String, Player>(300000L,
new Function<Player>() {
@Override
public boolean run(Player value) {
String key = playerLoader.getPlayerDataID(value);
return inventories.containsKey(key) && inventories.get(key).isInUse()
|| enderChests.containsKey(key) && enderChests.get(key).isInUse();
}
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,
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(Player value) {
String key = playerLoader.getPlayerDataID(value);
value -> {
String key = OpenInv.this.getPlayerID(value);
// Check if inventory is stored, and if it is, remove it and eject all viewers
if (inventories.containsKey(key)) {
Inventory inv = inventories.remove(key).getBukkitInventory();
for (HumanEntity entity : inv.getViewers()) {
entity.closeInventory();
}
// 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 = new ArrayList<>(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 (enderChests.containsKey(key)) {
Inventory inv = enderChests.remove(key).getBukkitInventory();
for (HumanEntity entity : inv.getViewers()) {
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 = new ArrayList<>(inv.getViewers());
for (HumanEntity entity : viewers.toArray(new HumanEntity[0])) {
entity.closeInventory();
}
}
if (!OpenInv.this.disableSaving() && !value.isOnline()) {
value.saveData();
}
return true;
if (!OpenInv.this.disableSaving() && !value.isOnline()) {
value.saveData();
}
});
private InternalAccessor accessor;
private IPlayerDataManager playerLoader;
private IInventoryAccess inventoryAccess;
private IAnySilentContainer anySilentContainer;
private LanguageManager languageManager;
@Override
public void onEnable() {
// Get plugin manager
PluginManager pm = getServer().getPluginManager();
/**
* Evicts all viewers lacking cross-world permissions from a Player's inventory.
*
* @param player the Player
*/
public void changeWorld(final Player player) {
accessor = new InternalAccessor(this);
// Version check
if (!accessor.isSupported()) {
getLogger().info("Your version of CraftBukkit (" + accessor.getVersion() + ") is not supported.");
getLogger().info("Please look for an updated version of OpenInv.");
pm.disablePlugin(this);
String key = this.getPlayerID(player);
// Check if the player is cached. If not, neither of their inventories is open.
if (!this.playerCache.containsKey(key)) {
return;
}
playerLoader = accessor.newPlayerDataManager();
inventoryAccess = accessor.newInventoryAccess();
anySilentContainer = accessor.newAnySilentContainer();
FileConfiguration config = getConfig();
boolean dirtyConfig = false;
if (!config.isBoolean("NotifySilentChest")) {
config.set("NotifySilentChest", true);
dirtyConfig = true;
}
if (!config.isBoolean("NotifyAnyChest")) {
config.set("NotifyAnyChest", true);
dirtyConfig = true;
}
if (!config.isBoolean("DisableSaving")) {
config.set("DisableSaving", false);
dirtyConfig = true;
}
config.addDefault("NotifySilentChest", true);
config.addDefault("NotifyAnyChest", true);
config.addDefault("DisableSaving", false);
config.options().copyDefaults(true);
if (dirtyConfig) {
saveConfig();
if (this.inventories.containsKey(key)) {
kickCrossWorldViewers(player, this.inventories.get(key));
}
pm.registerEvents(new OpenInvPlayerListener(this), this);
pm.registerEvents(new OpenInvInventoryListener(this), this);
if (this.enderChests.containsKey(key)) {
kickCrossWorldViewers(player, this.enderChests.get(key));
}
}
getCommand("openinv").setExecutor(new OpenInvPluginCommand(this));
SearchInvPluginCommand searchInv = new SearchInvPluginCommand();
getCommand("searchinv").setExecutor(searchInv);
getCommand("searchender").setExecutor(searchInv);
getCommand("silentchest").setExecutor(new SilentChestPluginCommand(this));
getCommand("anychest").setExecutor(new AnyChestPluginCommand(this));
getCommand("openender").setExecutor(new OpenEnderPluginCommand(this));
private void kickCrossWorldViewers(Player player, ISpecialInventory inventory) {
List<HumanEntity> viewers = new ArrayList<>(inventory.getBukkitInventory().getViewers());
for (HumanEntity human : viewers) {
// 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) || Objects.equals(human.getWorld(), player.getWorld())) {
continue;
}
human.closeInventory();
}
}
/**
* 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
*/
public int convertToPlayerSlot(InventoryView view, int rawSlot) {
return this.accessor.getPlayerDataManager().convertToPlayerSlot(view, rawSlot);
}
@Override
public boolean disableSaving() {
return this.getConfig().getBoolean("settings.disable-saving", false);
}
@NotNull
@Override
public IAnySilentContainer getAnySilentContainer() {
return this.accessor.getAnySilentContainer();
}
@Override
public boolean getPlayerAnyChestStatus(@NotNull final OfflinePlayer player) {
boolean defaultState = false;
if (player.isOnline()) {
Player onlinePlayer = player.getPlayer();
if (onlinePlayer != null) {
defaultState = Permissions.ANY_DEFAULT.hasPermission(onlinePlayer);
}
}
return this.getConfig().getBoolean("toggles.any-chest." + this.getPlayerID(player), defaultState);
}
@Override
public boolean getPlayerSilentChestStatus(@NotNull final OfflinePlayer offline) {
boolean defaultState = false;
if (offline.isOnline()) {
Player onlinePlayer = offline.getPlayer();
if (onlinePlayer != null) {
defaultState = Permissions.SILENT_DEFAULT.hasPermission(onlinePlayer);
}
}
return this.getConfig().getBoolean("toggles.silent-chest." + this.getPlayerID(offline), defaultState);
}
@NotNull
@Override
public 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);
}
ISpecialEnderChest inv = this.accessor.newSpecialEnderChest(player, online);
this.enderChests.put(id, inv);
this.playerCache.put(id, player);
return inv;
}
@NotNull
@Override
public 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);
}
ISpecialPlayerInventory inv = this.accessor.newSpecialPlayerInventory(player, online);
this.inventories.put(id, inv);
this.playerCache.put(id, player);
return inv;
}
@Override
public boolean isSupportedVersion() {
return this.accessor != null && this.accessor.isSupported();
}
@Override
public @Nullable Player loadPlayer(@NotNull final OfflinePlayer offline) {
String key = this.getPlayerID(offline);
if (this.playerCache.containsKey(key)) {
return this.playerCache.get(key);
}
Player player = offline.getPlayer();
if (player != null) {
this.playerCache.put(key, player);
return player;
}
if (!this.isSupportedVersion()) {
return null;
}
if (Bukkit.isPrimaryThread()) {
return this.accessor.getPlayerDataManager().loadPlayer(offline);
}
Future<Player> future = Bukkit.getScheduler().callSyncMethod(this,
() -> OpenInv.this.accessor.getPlayerDataManager().loadPlayer(offline));
try {
player = future.get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
return null;
}
if (player != null) {
this.playerCache.put(key, player);
}
return player;
}
@Override
public @Nullable InventoryView openInventory(@NotNull Player player, @NotNull ISpecialInventory inventory) {
return this.accessor.getPlayerDataManager().openInventory(player, inventory);
}
public void sendMessage(@NotNull CommandSender sender, @NotNull String key) {
String message = this.languageManager.getValue(key, getLocale(sender));
if (message != null && !message.isEmpty()) {
sender.sendMessage(message);
}
}
public void sendMessage(@NotNull CommandSender sender, @NotNull String key, String... replacements) {
String message = this.languageManager.getValue(key, getLocale(sender), replacements);
if (message != null && !message.isEmpty()) {
sender.sendMessage(message);
}
}
public void sendSystemMessage(@NotNull Player player, @NotNull String key) {
this.sendSystemMessage(player, key, new String[0]);
}
public void sendSystemMessage(@NotNull Player player, @NotNull String key, String... replacements) {
String message = this.languageManager.getValue(key, getLocale(player));
if (message == null) {
return;
}
int newline = message.indexOf('\n');
if (newline != -1) {
// No newlines in action bar chat.
message = message.substring(0, newline);
}
if (message.isEmpty()) {
return;
}
player.spigot().sendMessage(ChatMessageType.ACTION_BAR, TextComponent.fromLegacyText(message));
}
public @Nullable String getLocalizedMessage(@NotNull CommandSender sender, @NotNull String key) {
return this.languageManager.getValue(key, getLocale(sender));
}
public @Nullable String getLocalizedMessage(@NotNull CommandSender sender, @NotNull String key, String... replacements) {
return this.languageManager.getValue(key, getLocale(sender), replacements);
}
private @NotNull String getLocale(@NotNull CommandSender sender) {
if (sender instanceof Player) {
return ((Player) sender).getLocale();
} else {
return this.getConfig().getString("settings.locale", "en_us");
}
}
public int getSearchPollsPerTick() {
return this.getConfig().getInt("settings.search.polls-per-tick", 5);
}
public int getSearchResultsMax() {
return this.getConfig().getInt("settings.search.max-results", 40);
}
@Override
@@ -160,309 +347,146 @@ public class OpenInv extends JavaPlugin {
return;
}
this.playerCache.invalidateAll();
if (this.isSupportedVersion()) {
this.playerCache.invalidateAll();
}
}
@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");
// 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 InventoryListener(this), this);
// Register commands to their executors
this.setCommandExecutor(new OpenInvCommand(this), "openinv", "openender");
this.setCommandExecutor(new SearchCommand(this), "search");
this.setCommandExecutor(new ContainerSettingCommand(this), "silentcontainer", "anycontainer");
} else {
this.sendVersionError(this.getLogger()::warning);
}
}
private void sendVersionError(Consumer<String> messageMethod) {
messageMethod.accept("Your server version (" + this.accessor.getVersion() + ") is not supported.");
messageMethod.accept("Please obtain an appropriate version here: " + accessor.getReleasesLink());
}
private void setCommandExecutor(CommandExecutor executor, String... commands) {
for (String commandName : commands) {
PluginCommand command = this.getCommand(commandName);
if (command != null) {
command.setExecutor(executor);
}
}
}
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (!this.accessor.isSupported()) {
this.sendVersionError(sender::sendMessage);
return true;
}
return false;
}
public void releaseAllPlayers(final Plugin plugin) {
Iterator<Map.Entry<String, Class<? extends Plugin>>> iterator = this.pluginUsage.entries().iterator();
if (!iterator.hasNext()) {
return;
}
for (Map.Entry<String, Class<? extends Plugin>> entry = iterator.next(); iterator.hasNext(); entry = iterator.next()) {
if (entry.getValue().equals(plugin.getClass())) {
iterator.remove();
}
}
}
@Override
public void releasePlayer(@NotNull final Player player, @NotNull final Plugin plugin) {
String key = this.getPlayerID(player);
if (!this.pluginUsage.containsEntry(key, plugin.getClass())) {
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();
}
/**
* Checks if the server version is supported by OpenInv.
*
* @return true if the server version is supported
*/
public boolean isSupportedVersion() {
return accessor.isSupported();
}
/**
* Gets the active IInventoryAccess implementation. May return null if the server version is
* unsupported.
*
* @return the IInventoryAccess
*/
public IInventoryAccess getInventoryAccess() {
return this.inventoryAccess;
}
/**
* Gets the active ISilentContainer implementation. May return null if the server version is
* unsupported.
*
* @return the ISilentContainer
*/
public IAnySilentContainer getAnySilentContainer() {
return this.anySilentContainer;
}
/**
* Gets an ISpecialPlayerInventory for the given Player.
*
* Method for handling a Player going offline.
*
* @param player the Player
* @param online true if the Player is currently online
* @return the ISpecialPlayerInventory
* @throws IllegalStateException if the server version is unsupported
*/
public ISpecialPlayerInventory getInventory(Player player, boolean online) {
String id = playerLoader.getPlayerDataID(player);
if (inventories.containsKey(id)) {
return inventories.get(id);
}
ISpecialPlayerInventory inv = accessor.newSpecialPlayerInventory(player, online);
inventories.put(id, inv);
playerCache.put(id, player);
return inv;
}
public void setPlayerOffline(final Player player) {
/**
* Gets an ISpecialEnderChest for the given Player.
*
* @param player the Player
* @param online true if the Player is currently online
* @return the ISpecialEnderChest
*/
public ISpecialEnderChest getEnderChest(Player player, boolean online) {
String id = playerLoader.getPlayerDataID(player);
if (enderChests.containsKey(id)) {
return enderChests.get(id);
}
ISpecialEnderChest inv = accessor.newSpecialEnderChest(player, online);
enderChests.put(id, inv);
playerCache.put(id, player);
return inv;
}
String key = this.getPlayerID(player);
/**
* Forcibly unload a cached Player's data.
*
* @param player the OfflinePlayer to unload
*/
public void unload(OfflinePlayer player) {
this.playerCache.invalidate(this.playerLoader.getPlayerDataID(player));
}
/**
* 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.
*
* @return false unless configured otherwise
*/
public boolean disableSaving() {
return getConfig().getBoolean("DisableSaving", false);
}
/**
* 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
*/
public boolean notifySilentChest() {
return getConfig().getBoolean("NotifySilentChest", 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
*/
public boolean notifyAnyChest() {
return getConfig().getBoolean("NotifyAnyChest", true);
}
/**
* Gets a player's SilentChest setting.
*
* @param player the OfflinePlayer
* @return true if SilentChest is enabled
*/
public boolean getPlayerSilentChestStatus(OfflinePlayer player) {
return getConfig().getBoolean("SilentChest." + playerLoader.getPlayerDataID(player) + ".toggle", false);
}
/**
* Sets a player's SilentChest setting.
*
* @param player the OfflinePlayer
* @param status the status
*/
public void setPlayerSilentChestStatus(OfflinePlayer player, boolean status) {
getConfig().set("SilentChest." + playerLoader.getPlayerDataID(player) + ".toggle", status);
saveConfig();
}
/**
* Gets the provided player's AnyChest setting.
*
* @param player the OfflinePlayer
* @return true if AnyChest is enabled
*/
public boolean getPlayerAnyChestStatus(OfflinePlayer player) {
return getConfig().getBoolean("AnyChest." + playerLoader.getPlayerDataID(player) + ".toggle", true);
}
/**
* Sets a player's AnyChest setting.
*
* @param player the OfflinePlayer
* @param status the status
*/
public void setPlayerAnyChestStatus(OfflinePlayer player, boolean status) {
getConfig().set("AnyChest." + playerLoader.getPlayerDataID(player) + ".toggle", status);
saveConfig();
}
/**
* Get an OfflinePlayer by name.
* <p>
* Note: 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
*/
public OfflinePlayer matchPlayer(String name) {
// Warn if called on the main thread - if we resort to searching offline players, this may take several seconds.
if (getServer().isPrimaryThread()) {
getLogger().warning("Call to OpenInv#matchPlayer made on the main thread!");
getLogger().warning("This can cause the server to hang, potentially severely.");
getLogger().warning("Trace:");
for (StackTraceElement element : new Throwable().fillInStackTrace().getStackTrace()) {
getLogger().warning(element.toString());
}
// Check if the player is cached. If not, neither of their inventories is open.
if (!this.playerCache.containsKey(key)) {
return;
}
// Ensure name is valid if server is in online mode to avoid unnecessary searching
if (getServer().getOnlineMode() && !name.matches("[a-zA-Z0-9_]{3,16}")) {
return null;
// Replace stored player with our own version
this.playerCache.put(key, this.accessor.getPlayerDataManager().inject(player));
if (this.inventories.containsKey(key)) {
this.inventories.get(key).setPlayerOffline();
}
OfflinePlayer player = getServer().getPlayerExact(name);
if (player != null) {
return player;
if (this.enderChests.containsKey(key)) {
this.enderChests.get(key).setPlayerOffline();
}
player = getServer().getOfflinePlayer(name);
/*
* Compatibility: Pre-UUID, getOfflinePlayer always returns an OfflinePlayer. Post-UUID,
* getOfflinePlayer will return null if no matching player is found. To preserve
* compatibility, only return the player if they have played before. Ignoring current online
* status is fine, they'd have been found by getPlayerExact otherwise.
*/
if (player != null && player.hasPlayedBefore()) {
return player;
}
player = getServer().getPlayer(name);
if (player != null) {
return player;
}
int bestMatch = Integer.MAX_VALUE;
for (OfflinePlayer offline : getServer().getOfflinePlayers()) {
if (offline.getName() == null) {
// Loaded by UUID only, name has never been looked up.
continue;
}
// Compatibility: Lang3 is only bundled with 1.8+
int currentMatch = org.apache.commons.lang.StringUtils.getLevenshteinDistance(name, offline.getName());
if (currentMatch == 0) {
return offline;
}
if (currentMatch < bestMatch) {
bestMatch = currentMatch;
player = offline;
}
}
// Only null if no players have played ever, otherwise even the worst match will do.
return player;
}
/**
* Load a Player from an OfflinePlayer. May return null under some circumstances.
*
* @param offline the OfflinePlayer to load a Player for
* @return the Player
*/
public Player loadPlayer(final OfflinePlayer offline) {
if (offline == null) {
return null;
}
String key = this.playerLoader.getPlayerDataID(offline);
if (this.playerCache.containsKey(key)) {
return this.playerCache.get(key);
}
Player loaded;
if (offline.isOnline()) {
loaded = offline.getPlayer();
this.playerCache.put(key, loaded);
return loaded;
}
if (Bukkit.isPrimaryThread()) {
return this.playerLoader.loadPlayer(offline);
}
Future<Player> future = Bukkit.getScheduler().callSyncMethod(this,
new Callable<Player>() {
@Override
public Player call() throws Exception {
return playerLoader.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();
} catch (InterruptedException e) {
e.printStackTrace();
return null;
} catch (ExecutionException e) {
e.printStackTrace();
return null;
}
if (loaded != null) {
this.playerCache.put(key, loaded);
}
return loaded;
}
/**
* Method for handling a Player coming online.
*
*
* @param player the Player
* @throws IllegalStateException if the server version is unsupported
*/
public void setPlayerOnline(final Player player) {
String key = this.playerLoader.getPlayerDataID(player);
String key = this.getPlayerID(player);
// Check if the player is cached. If not, neither of their inventories is open.
if (!this.playerCache.containsKey(key)) {
@@ -488,76 +512,18 @@ public class OpenInv extends JavaPlugin {
}
}
/**
* Method for handling a Player going offline.
*
* @param player the Player
*/
public void setPlayerOffline(final Player player) {
String key = this.playerLoader.getPlayerDataID(player);
// Check if the player is cached. If not, neither of their inventories is open.
if (!this.playerCache.containsKey(key)) {
return;
}
if (this.inventories.containsKey(key)) {
this.inventories.get(key).setPlayerOffline();
}
if (this.enderChests.containsKey(key)) {
this.enderChests.get(key).setPlayerOffline();
}
}
/**
* Evicts all viewers lacking cross-world permissions from a Player's inventory.
*
* @param player the Player
*/
public void changeWorld(final Player player) {
String key = this.playerLoader.getPlayerDataID(player);
// Check if the player is cached. If not, neither of their inventories is open.
if (!this.playerCache.containsKey(key)) {
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();
}
}
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();
}
}
@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(Player player) {
public void showHelp(final Player player) {
// Get registered commands
for (String commandName : this.getDescription().getCommands().keySet()) {
PluginCommand command = this.getCommand(commandName);
@@ -587,4 +553,9 @@ public class OpenInv extends JavaPlugin {
}
}
@Override
public void unload(@NotNull final OfflinePlayer offline) {
this.playerCache.invalidate(this.getPlayerID(offline));
}
}
@@ -1,62 +0,0 @@
/*
* Copyright (C) 2011-2014 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 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.event.inventory.InventoryDragEvent;
import org.bukkit.event.player.PlayerChangedWorldEvent;
import org.bukkit.inventory.Inventory;
public class OpenInvInventoryListener implements Listener {
private final OpenInv plugin;
public OpenInvInventoryListener(OpenInv plugin) {
this.plugin = plugin;
}
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void onInventoryClick(InventoryClickEvent event) {
if (cancelInteract(event.getWhoClicked(), event.getInventory())) {
event.setCancelled(true);
}
}
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void onInventoryDrag(InventoryDragEvent event) {
if (cancelInteract(event.getWhoClicked(), event.getInventory())) {
event.setCancelled(true);
}
}
private boolean cancelInteract(HumanEntity entity, Inventory inventory) {
return plugin.getInventoryAccess().isSpecialPlayerInventory(inventory)
&& !Permissions.EDITINV.hasPermission(entity)
|| plugin.getInventoryAccess().isSpecialEnderChest(inventory)
&& !Permissions.EDITENDER.hasPermission(entity);
}
@EventHandler
public void onWorldChange(PlayerChangedWorldEvent event) {
plugin.changeWorld(event.getPlayer());
}
}
@@ -1,78 +0,0 @@
/*
* Copyright (C) 2011-2014 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 org.bukkit.entity.Player;
import org.bukkit.event.Event.Result;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.Action;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
public class OpenInvPlayerListener implements Listener {
private final OpenInv plugin;
public OpenInvPlayerListener(OpenInv plugin) {
this.plugin = plugin;
}
@EventHandler(priority = EventPriority.MONITOR)
public void onPlayerJoin(final PlayerJoinEvent event) {
plugin.setPlayerOnline(event.getPlayer());
}
@EventHandler(priority = EventPriority.MONITOR)
public void onPlayerQuit(PlayerQuitEvent event) {
plugin.setPlayerOffline(event.getPlayer());
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onPlayerInteract(PlayerInteractEvent event) {
if (event.getAction() != Action.RIGHT_CLICK_BLOCK || event.getPlayer().isSneaking()
|| event.useInteractedBlock() == Result.DENY
|| !plugin.getAnySilentContainer().isAnySilentContainer(event.getClickedBlock())) {
return;
}
Player player = event.getPlayer();
boolean anychest = Permissions.ANYCHEST.hasPermission(player) && plugin.getPlayerAnyChestStatus(player);
boolean needsAnyChest = plugin.getAnySilentContainer().isAnyContainerNeeded(player, event.getClickedBlock());
if (!anychest && needsAnyChest) {
return;
}
boolean silentchest = Permissions.SILENT.hasPermission(player) && plugin.getPlayerSilentChestStatus(player);
// If anychest or silentchest is active
if ((anychest || silentchest) && plugin.getAnySilentContainer().activateContainer(player, silentchest, event.getClickedBlock())) {
if (silentchest && plugin.notifySilentChest() && needsAnyChest && plugin.notifyAnyChest()) {
player.sendMessage("You are opening a blocked container silently.");
} else if (silentchest && plugin.notifySilentChest()) {
player.sendMessage("You are opening a container silently.");
} else if (needsAnyChest && plugin.notifyAnyChest()) {
player.sendMessage("You are opening a blocked container.");
}
event.setCancelled(true);
}
}
}
@@ -1,38 +0,0 @@
package com.lishid.openinv;
import org.bukkit.permissions.Permissible;
public enum Permissions {
OPENINV("OpenInv.openinv"),
OVERRIDE("OpenInv.override"),
EXEMPT("OpenInv.exempt"),
CROSSWORLD("OpenInv.crossworld"),
SILENT("OpenInv.silent"),
ANYCHEST("OpenInv.anychest"),
ENDERCHEST("OpenInv.openender"),
ENDERCHEST_ALL("OpenInv.openenderall"),
SEARCH("OpenInv.search"),
EDITINV("OpenInv.editinv"),
EDITENDER("OpenInv.editender"),
OPENSELF("OpenInv.openself");
private final String permission;
private Permissions(String permission) {
this.permission = permission;
}
public boolean hasPermission(Permissible permissible) {
String[] parts = permission.split("\\.");
String perm = "";
for (int i = 0; i < parts.length; i++) {
if (permissible.hasPermission(perm + "*")) {
return true;
}
perm += parts[i] + ".";
}
return permissible.hasPermission(permission);
}
}
@@ -1,54 +0,0 @@
/*
* Copyright (C) 2011-2014 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 org.bukkit.ChatColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import com.lishid.openinv.OpenInv;
public class AnyChestPluginCommand implements CommandExecutor {
private final OpenInv plugin;
public AnyChestPluginCommand(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("AnyChest is " + (plugin.getPlayerAnyChestStatus(player) ? "ON" : "OFF") + ".");
return true;
}
plugin.setPlayerAnyChestStatus(player, !plugin.getPlayerAnyChestStatus(player));
sender.sendMessage("AnyChest is now " + (plugin.getPlayerAnyChestStatus(player) ? "ON" : "OFF") + ".");
return true;
}
}
@@ -0,0 +1,87 @@
/*
* 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.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)) {
plugin.sendMessage(sender, "messages.error.consoleUnsupported");
return true;
}
Player player = (Player) sender;
boolean any = command.getName().startsWith("any");
Predicate<Player> getSetting = any ? plugin::getPlayerAnyChestStatus : plugin::getPlayerSilentChestStatus;
BiConsumer<OfflinePlayer, Boolean> setSetting = any ? plugin::setPlayerAnyChestStatus : plugin::setPlayerSilentChestStatus;
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"});
}
}
@@ -1,141 +0,0 @@
/*
* Copyright (C) 2011-2014 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 java.util.HashMap;
import com.lishid.openinv.OpenInv;
import com.lishid.openinv.Permissions;
import com.lishid.openinv.internal.ISpecialEnderChest;
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 OpenEnderPluginCommand implements CommandExecutor {
private final OpenInv plugin;
private final HashMap<Player, String> openEnderHistory = new HashMap<Player, String>();
public OpenEnderPluginCommand(OpenInv plugin) {
this.plugin = plugin;
}
@Override
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;
}
if (args.length > 0 && args[0].equalsIgnoreCase("?")) {
plugin.showHelp((Player) sender);
return true;
}
final Player player = (Player) sender;
// History management
String history = openEnderHistory.get(player);
if (history == null || history == "") {
history = player.getName();
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 = 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;
}
openInventory(player, offlinePlayer);
}
}.runTask(plugin);
}
}.runTaskAsynchronously(plugin);
return true;
}
private void openInventory(Player player, OfflinePlayer target) {
Player onlineTarget;
boolean online = target.isOnline();
if (!online) {
// Try loading the player's data
onlineTarget = plugin.loadPlayer(target);
if (onlineTarget == null) {
player.sendMessage(ChatColor.RED + "Player not found!");
return;
}
} else {
onlineTarget = target.getPlayer();
}
if (!onlineTarget.equals(player)) {
if (!Permissions.ENDERCHEST_ALL.hasPermission(player)) {
player.sendMessage(ChatColor.RED + "You do not have permission to access other players' enderchests.");
return;
}
if (!Permissions.CROSSWORLD.hasPermission(player) && !player.getWorld().equals(onlineTarget.getWorld())) {
player.sendMessage(ChatColor.RED + onlineTarget.getDisplayName() + " is not in your world!");
return;
}
if (!Permissions.OVERRIDE.hasPermission(player) && Permissions.EXEMPT.hasPermission(onlineTarget)) {
player.sendMessage(ChatColor.RED + onlineTarget.getDisplayName() + "'s inventory is protected!");
return;
}
}
// Record the target
openEnderHistory.put(player, onlineTarget.getName());
// Create the inventory
ISpecialEnderChest chest = plugin.getEnderChest(onlineTarget, online);
// Open the inventory
player.openInventory(chest.getBukkitInventory());
}
}
@@ -0,0 +1,185 @@
/*
* 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.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 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.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) {
if (!(sender instanceof Player)) {
plugin.sendMessage(sender, "messages.error.consoleUnsupported");
return true;
}
if (args.length > 0 && (args[0].equalsIgnoreCase("help") || args[0].equals("?"))) {
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 = 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 openInventory(final Player player, final OfflinePlayer target, boolean openinv) {
Player onlineTarget;
boolean online = target.isOnline();
if (!online) {
if (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, 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) {
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,148 +0,0 @@
/*
* Copyright (C) 2011-2014 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 java.util.HashMap;
import com.lishid.openinv.OpenInv;
import com.lishid.openinv.Permissions;
import com.lishid.openinv.internal.ISpecialPlayerInventory;
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>();
public OpenInvPluginCommand(OpenInv plugin) {
this.plugin = plugin;
}
@Override
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;
}
if (args.length > 0 && args[0].equalsIgnoreCase("?")) {
plugin.showHelp((Player) sender);
return true;
}
final Player player = (Player) sender;
// History management
String history = openInvHistory.get(player);
if (history == null || history == "") {
history = player.getName();
openInvHistory.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 = 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;
}
openInventory(player, offlinePlayer);
}
}.runTask(plugin);
}
}.runTaskAsynchronously(plugin);
return true;
}
private void openInventory(Player player, OfflinePlayer target) {
Player onlineTarget;
boolean online = target.isOnline();
if (!online) {
// Try loading the player's data
onlineTarget = plugin.loadPlayer(target);
if (onlineTarget == null) {
player.sendMessage(ChatColor.RED + "Player not found!");
return;
}
} else {
onlineTarget = target.getPlayer();
}
// Permissions checks
if (onlineTarget.equals(player)) {
// Self-open check
if (!Permissions.OPENSELF.hasPermission(player)) {
player.sendMessage(ChatColor.RED + "You're not allowed to openinv yourself.");
return;
}
} else {
// 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) && !Permissions.OVERRIDE.hasPermission(player)) && onlineTarget.getWorld() != player.getWorld()) {
player.sendMessage(ChatColor.RED + onlineTarget.getDisplayName() + " is not in your world!");
return;
}
}
// Record the target
openInvHistory.put(player, onlineTarget.getName());
// Create the inventory
ISpecialPlayerInventory inv = plugin.getInventory(onlineTarget, online);
// Open the inventory
player.openInventory(inv.getBukkitInventory());
}
}
@@ -0,0 +1,249 @@
/*
* 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.commands;
import com.lishid.openinv.OpenInv;
import com.lishid.openinv.commands.search.ChunkSearchOption;
import com.lishid.openinv.commands.search.CompletableOption;
import com.lishid.openinv.commands.search.ItemAmountOption;
import com.lishid.openinv.commands.search.ItemEnchantOption;
import com.lishid.openinv.commands.search.ItemTypeOption;
import com.lishid.openinv.commands.search.PlayerSearchOption;
import com.lishid.openinv.commands.search.PseudoOption;
import com.lishid.openinv.search.Search;
import com.lishid.openinv.search.bucket.SearchBucket;
import com.lishid.openinv.search.match.ItemMatcher;
import com.lishid.openinv.search.match.MatchableItem;
import com.lishid.openinv.search.match.MatchableMeta;
import com.lishid.openinv.util.PseudoJson;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;
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;
import org.jetbrains.annotations.Nullable;
public class SearchCommand implements TabExecutor {
private final OpenInv plugin;
private final Collection<String> activeSearches = Collections.newSetFromMap(new ConcurrentHashMap<>());
private final Collection<CompletableOption<SearchBucket>> completableSearchBuckets = new HashSet<>();
private final Collection<CompletableOption<MatchableItem>> completableMatchOptions = new HashSet<>();
private final Collection<CompletableOption<MatchableMeta>> completableMatchMetaOptions = new HashSet<>();
public SearchCommand(OpenInv plugin) {
this.plugin = plugin;
this.completableSearchBuckets.add(new ChunkSearchOption());
this.completableSearchBuckets.add(new PlayerSearchOption(plugin));
this.completableMatchOptions.add(new ItemAmountOption());
this.completableMatchOptions.add(new ItemTypeOption());
this.completableMatchMetaOptions.add(new ItemEnchantOption());
}
@Override
public boolean onCommand(
@NotNull CommandSender sender,
@NotNull Command command,
@NotNull String label,
@NotNull String[] args) {
if (args.length < 2) {
return false;
}
String id = sender instanceof Player ? plugin.getPlayerID((Player) sender) : "CONSOLE";
if (activeSearches.contains(id)) {
plugin.sendMessage(sender, "messages.error.search.activeSearch");
return true;
}
Collection<PseudoJson> arguments = new HashSet<>();
for (String arg : args) {
arg = arg.toLowerCase(Locale.ROOT);
try {
arguments.add(PseudoJson.fromString(arg));
} catch (IllegalArgumentException e) {
plugin.sendMessage(sender, "messages.error.search.invalidSchema", "%value%", arg);
return true;
}
}
Collection<SearchBucket> searchBuckets = getSearchBuckets(sender, arguments);
if (searchBuckets.isEmpty()) {
plugin.sendMessage(sender, "messages.error.search.invalidInventory");
return true;
}
ItemMatcher matcher = getMatcher(sender, arguments);
if (matcher == null) {
plugin.sendMessage(sender, "messages.error.search.invalidMatcher");
return true;
}
activeSearches.add(id);
new Search(matcher, searchBuckets, () -> activeSearches.remove(id)).schedule(sender, plugin);
return true;
}
private @Nullable ItemMatcher getMatcher(@NotNull CommandSender sender, @NotNull Collection<PseudoJson> args) {
List<PseudoOption<MatchableItem>> matchItemOptions = new ArrayList<>();
List<PseudoOption<MatchableMeta>> matchMetaOptions = new ArrayList<>();
for (PseudoJson arg : args) {
addMatchOptions(completableMatchOptions, matchItemOptions, sender, arg);
addMatchOptions(completableMatchMetaOptions, matchMetaOptions, sender, arg);
}
// Must have a match option. Null entries indicate a parsing error.
if (matchItemOptions.isEmpty()
|| matchItemOptions.stream().anyMatch(Objects::isNull)
|| matchMetaOptions.stream().anyMatch(Objects::isNull)) {
return null;
}
return new ItemMatcher(
matchItemOptions.stream().map(PseudoOption::option).toList(),
matchMetaOptions.stream().map(PseudoOption::option).toList());
}
private <T> void addMatchOptions(
@NotNull Collection<CompletableOption<T>> completableOptions,
@NotNull Collection<PseudoOption<T>> options,
@NotNull CommandSender sender,
@NotNull PseudoJson arg) {
for (CompletableOption<T> option : completableOptions) {
if (option.matches(sender, arg)) {
PseudoOption<T> parsed = option.parse(sender, arg);
if (parsed == null) {
options.add(null);
continue;
}
if (option.isUnique()) {
String uniqueId = option.getName();
// Match existing options.
List<PseudoOption<T>> matches = options.stream()
.filter(Objects::nonNull)
.filter(bucket -> bucket.pseudoJson().getIdentifier().equals(uniqueId))
.collect(Collectors.toList());
// Remove all existing options.
options.removeAll(matches);
// Merge with existing options.
for (PseudoOption<T> match : matches) {
PseudoOption<T> newParsed = option.merge(sender, match, parsed);
if (newParsed != null) {
parsed = newParsed;
}
}
}
options.add(parsed);
}
}
}
private Collection<SearchBucket> getSearchBuckets(
@NotNull CommandSender sender,
@NotNull Collection<PseudoJson> args) {
List<PseudoOption<SearchBucket>> buckets = new ArrayList<>();
for (PseudoJson arg : args) {
addMatchOptions(completableSearchBuckets, buckets, sender, arg);
}
return buckets.stream().map(PseudoOption::option).collect(Collectors.toList());
}
@Override
public @Nullable List<String> onTabComplete(
@NotNull CommandSender sender,
@NotNull Command command,
@NotNull String label,
@NotNull String[] args) {
List<PseudoJson> arguments = new ArrayList<>();
for (int i = 0; i < args.length; i++) {
String arg = args[i].toLowerCase(Locale.ROOT);
try {
arguments.add(PseudoJson.fromString(arg));
} catch (IllegalArgumentException e) {
if (i == args.length - 1) {
// If last argument cannot be parsed, do not complete.
return List.of();
}
// Otherwise, ignore formatting issues during tab completion.
}
}
// Ensure an argument exists.
if (arguments.isEmpty()) {
arguments.add(new PseudoJson(""));
}
// Remove last indexed argument, it is being completed.
PseudoJson last = arguments.remove(arguments.size() - 1);
// Due to generics, can't just use generic list of options to start with.
Collection<PseudoOption<SearchBucket>> buckets = new HashSet<>();
Collection<PseudoOption<MatchableItem>> items = new HashSet<>();
Collection<PseudoOption<MatchableMeta>> metas = new HashSet<>();
for (PseudoJson arg : arguments) {
addMatchOptions(completableSearchBuckets, buckets, sender, arg);
addMatchOptions(completableMatchOptions, items, sender, arg);
addMatchOptions(completableMatchMetaOptions, metas, sender, arg);
}
Collection<PseudoOption<?>> options = new HashSet<>();
options.addAll(buckets);
options.addAll(items);
options.addAll(metas);
// Suggest all completable options' completions.
return Stream.concat(completableSearchBuckets.stream(),
Stream.concat(completableMatchOptions.stream(),
completableMatchMetaOptions.stream()))
// Don't suggest unique options that are already present.
.filter(option ->
option.isUnique() && options.stream().anyMatch(pseudoOption ->
pseudoOption.pseudoJson().getIdentifier().equals(option.getName())))
.flatMap(option -> option.suggestOptions(sender, last).stream())
.distinct()
.toList();
}
}
Whitespace-only changes.
@@ -1,75 +0,0 @@
/*
* Copyright (C) 2011-2014 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 org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
public class SearchInvPluginCommand implements CommandExecutor {
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
Material material = null;
int count = 1;
if (args.length >= 1) {
String[] gData = null;
gData = args[0].split(":");
material = Material.matchMaterial(gData[0]);
}
if (args.length >= 2) {
try {
count = Integer.parseInt(args[1]);
} catch (NumberFormatException ex) {
sender.sendMessage(ChatColor.RED + "'" + args[1] + "' is not a number!");
return false;
}
}
if (material == null) {
sender.sendMessage(ChatColor.RED + "Unknown item");
return false;
}
StringBuilder players = new StringBuilder();
for (Player player : Bukkit.getServer().getOnlinePlayers()) {
Inventory inventory = command.getName().equals("searchinv") ? player.getInventory() : player.getEnderChest();
if (inventory.contains(material, count)) {
players.append(player.getName()).append(", ");
}
}
// Matches found, delete trailing comma and space
if (players.length() > 0) {
players.delete(players.length() - 2, players.length());
} else {
sender.sendMessage("No players found with " + material.toString());
}
sender.sendMessage("Players with the item " + material.toString() + ": " + players.toString());
return true;
}
}
@@ -1,53 +0,0 @@
/*
* Copyright (C) 2011-2014 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 org.bukkit.ChatColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import com.lishid.openinv.OpenInv;
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;
}
}
@@ -0,0 +1,125 @@
/*
* 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.commands.search;
import com.lishid.openinv.search.bucket.ChunkBucket;
import com.lishid.openinv.search.bucket.SearchBucket;
import com.lishid.openinv.util.Permissions;
import com.lishid.openinv.util.PseudoJson;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import org.bukkit.Bukkit;
import org.bukkit.Chunk;
import org.bukkit.World;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class ChunkSearchOption implements CompletableOption<SearchBucket> {
@Override
public @NotNull String getName() {
return "chunk";
}
@Override
public boolean matches(@NotNull CommandSender sender, @NotNull PseudoJson pseudoJson) {
return CompletableOption.super.matches(sender, pseudoJson)
&& Permissions.SEARCH_CHUNKS_LOADED.hasPermission(sender);
}
@Override
public @Nullable PseudoOption<SearchBucket> parse(@NotNull CommandSender sender, @NotNull PseudoJson pseudoJson) {
World world = null;
Integer chunkX = null;
Integer chunkZ = null;
int chunkRadius = 5;
boolean chunkLoad = false;
Map<String, String> mappings = pseudoJson.getMappings();
if (!(sender instanceof Player senderPlayer)) {
if (mappings.size() < 3) {
// Console must specify world, x center, z center.
return null;
}
} else {
world = senderPlayer.getWorld();
Chunk senderChunk = senderPlayer.getLocation().getChunk();
chunkX = senderChunk.getX();
chunkZ = senderChunk.getZ();
}
for (Map.Entry<String, String> entry : mappings.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
if (key.indexOf('w') >= 0) {
world = null;
// Name should always be lower case by this point. Ignore case when selecting world.
for (World loaded : Bukkit.getWorlds()) {
if (loaded.getName().equalsIgnoreCase(value)) {
world = loaded;
break;
}
}
} else if (key.indexOf('x') >= 0) {
try {
chunkX = Integer.parseInt(value);
} catch (NumberFormatException e) {
chunkX = null;
}
} else if (key.indexOf('z') >= 0) {
try {
chunkZ = Integer.parseInt(value);
} catch (NumberFormatException e) {
chunkZ = null;
}
} else if (key.indexOf('r') >= 0) {
// R is for radius. Needs to be checked later so that "world" doesn't result in a false positive.
try {
chunkRadius = Integer.parseInt(value);
} catch (NumberFormatException e) {
// Fall through to default radius.
}
} else if (key.indexOf('l') >= 0) {
// L is for loaded. Needs to be checked later so that "world" doesn't result in a false positive.
chunkLoad = Permissions.SEARCH_CHUNKS_UNLOADED.hasPermission(sender) && Boolean.parseBoolean(value);
}
}
if (world == null || chunkX == null || chunkZ == null || chunkRadius < 0) {
// Invalid data provided.
return null;
}
return new PseudoOption<>(pseudoJson, new ChunkBucket(world, chunkX, chunkZ, chunkRadius, chunkLoad));
}
@Override
public @NotNull Collection<String> suggestOptions(@NotNull CommandSender sender, @NotNull PseudoJson pseudoJson) {
// TODO
return Set.of();
}
@Override
public boolean isUnique() {
return false;
}
}
@@ -0,0 +1,110 @@
/*
* 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.commands.search;
import com.lishid.openinv.util.PseudoJson;
import java.util.Collection;
import java.util.Locale;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Interface defining behavior for an option.
*
* @param <T> the type of option represented
*/
public interface CompletableOption<T> {
/**
* Get unique identifier representing the option.
*
* @return the unique identifier
*/
@NotNull String getName();
/**
* Check if the <code>CompletableOption</code> matches the given <code>PseudoJson</code>.
*
* @param sender the individual using the option
* @param pseudoJson the pseudo-JSON element
* @return true if the CompletableOption matches
*/
default boolean matches(@NotNull CommandSender sender, @NotNull PseudoJson pseudoJson) {
return pseudoJson.getIdentifier().toLowerCase(Locale.ROOT).startsWith(getName());
}
/**
* Parse the option represented from the given <code>PseudoJson</code>.
*
* @param sender the individual using the option
* @param pseudoJson the pseudo-JSON element
* @return the parsed options
*/
@Nullable PseudoOption<T> parse(@NotNull CommandSender sender, @NotNull PseudoJson pseudoJson);
/**
* Form a collection of viable permutations to suggest for the given <code>PseudoJson</code>.
*
* @param sender the individual to suggest options for
* @param pseudoJson the parsed pseudo-JSON
* @return the suggested permutations
*/
@NotNull Collection<String> suggestOptions(
@NotNull CommandSender sender,
@NotNull PseudoJson pseudoJson);
/**
* Get whether or not multiple different copies of this <code>CompletableOption</code> are allowed.
*
* @return true if only a single instance is allowed
*/
boolean isUnique();
/**
* Merge similar elements. Used to enforce {@link #isUnique()} despite multiples being provided.
*
* @param sender the individual using the options
* @param first the first parsed option
* @param second the second parsed option
* @return a single element containing a best-effort merge of both
*/
default @Nullable PseudoOption<T> merge(
@NotNull CommandSender sender,
@NotNull PseudoOption<?> first,
@NotNull PseudoOption<?> second) {
PseudoJson merge = new PseudoJson(first.pseudoJson().getIdentifier());
// Use mappings of first for our base.
first.pseudoJson().getMappings().forEach(merge::put);
// Overwrite with second.
second.pseudoJson().getMappings().forEach(merge::put);
// Parse merged options.
PseudoOption<T> parsed = parse(sender, merge);
if (parsed != null) {
return parsed;
}
// On parsing failure, try second instead.
merge = new PseudoJson(second.pseudoJson().getIdentifier(), merge.getMappings());
return parse(sender, merge);
}
}
@@ -0,0 +1,84 @@
/*
* 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.commands.search;
import com.lishid.openinv.search.match.ItemMatchables;
import com.lishid.openinv.search.match.MatchableItem;
import com.lishid.openinv.util.PseudoJson;
import com.lishid.openinv.util.TabCompleter;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.bukkit.command.CommandSender;
import org.bukkit.util.StringUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class ItemAmountOption implements CompletableOption<MatchableItem> {
@Override
public @NotNull String getName() {
return "amount";
}
@Override
public @Nullable PseudoOption<MatchableItem> parse(@NotNull CommandSender sender, @NotNull PseudoJson pseudoJson) {
String minAmountString = pseudoJson.getMappings().entrySet().stream().findFirst().map(Map.Entry::getValue).orElse(null);
if (minAmountString == null) {
// Amount not correctly specified. Warn about spec.
return null;
}
int minAmount;
try {
minAmount = Integer.parseInt(minAmountString);
} catch (NumberFormatException e) {
// Amount can't be parsed. Warn about spec.
return null;
}
return new PseudoOption<>(pseudoJson, ItemMatchables.hasAmount(minAmount));
}
@Override
public @NotNull Collection<String> suggestOptions(@NotNull CommandSender sender, @NotNull PseudoJson pseudoJson) {
// TODO combine with type
if (!pseudoJson.getIdentifier().equals(getName())) {
// Suggest name while unformed.
if (StringUtil.startsWithIgnoreCase(getName(), pseudoJson.getIdentifier())) {
return Set.of(getName() + ":");
}
// If name is not a valid suggestion, suggest nothing.
return Set.of();
}
String minAmountString = pseudoJson.getMappings().entrySet().stream().findFirst().map(Map.Entry::getValue).orElse("");
// Find and suggest name in full pseudojson.
return TabCompleter.completeInteger(minAmountString).stream().map(match -> {
PseudoJson amount = new PseudoJson(getName());
amount.put(getName(), match);
return amount;
}).map(PseudoJson::asString).collect(Collectors.toSet());
}
@Override
public boolean isUnique() {
return true;
}
}
@@ -0,0 +1,137 @@
/*
* 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.commands.search;
import com.github.jikoo.planarwrappers.util.StringConverters;
import com.lishid.openinv.search.match.ItemMatchables;
import com.lishid.openinv.search.match.MatchableMeta;
import com.lishid.openinv.util.PseudoJson;
import com.lishid.openinv.util.TabCompleter;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.bukkit.command.CommandSender;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.util.StringUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class ItemEnchantOption implements CompletableOption<MatchableMeta> {
@Override
public @NotNull String getName() {
return "enchant";
}
@Override
public @Nullable PseudoOption<MatchableMeta> parse(@NotNull CommandSender sender, @NotNull PseudoJson pseudoJson) {
Enchantment enchantment = null;
Integer enchantLevel = null;
for (Map.Entry<String, String> entry : pseudoJson.getMappings().entrySet()) {
if (entry.getValue().isBlank()) {
continue;
}
if (entry.getKey().indexOf('l') >= 0) {
try {
enchantLevel = Integer.parseInt(entry.getValue());
} catch (NumberFormatException e) {
// Level can't be parsed. Warn about spec.
return null;
}
}
if (entry.getKey().indexOf('t') >= 0) {
enchantment = StringConverters.toEnchant(entry.getValue());
if (enchantment == null) {
// Enchantment is specified but can't be parsed. Warn about spec.
return null;
}
}
}
return new PseudoOption<>(pseudoJson, ItemMatchables.hasEnchant(enchantment, enchantLevel));
}
@Override
public @NotNull Collection<String> suggestOptions(@NotNull CommandSender sender, @NotNull PseudoJson pseudoJson) {
if (!pseudoJson.getIdentifier().equals(getName())) {
if (!StringUtil.startsWithIgnoreCase(getName(), pseudoJson.getIdentifier())) {
// If enchant is not a valid suggestion, suggest nothing.
return Set.of();
}
// Suggest all options while unformed.
Set<String> set = new HashSet<>();
PseudoJson enchant = new PseudoJson(getName());
set.add(enchant.asString());
enchant.put("type", "");
set.add(enchant.asString());
enchant.put("level", "");
set.add(enchant.asString());
enchant.remove("type");
set.add(enchant.asString());
return set;
}
Collection<String> enchantments = null;
Integer enchantLevel = null;
for (Map.Entry<String, String> entry : pseudoJson.getMappings().entrySet()) {
if (entry.getValue().isBlank()) {
continue;
}
if (entry.getKey().indexOf('l') >= 0) {
try {
enchantLevel = Integer.parseInt(entry.getValue());
} catch (NumberFormatException e) {
// Level is present but cannot be parsed. Default 0.
enchantLevel = 0;
}
}
if (entry.getKey().indexOf('t') >= 0) {
Enchantment enchant = StringConverters.toEnchant(entry.getValue());
if (enchant == null) {
// Enchantment is specified but can't be parsed, tab complete.
enchantments = TabCompleter.completeKeyed(entry.getValue(), Enchantment.values());
} else {
enchantments = Set.of(entry.getValue());
}
}
}
PseudoJson enchant = new PseudoJson(getName());
if (enchantLevel != null) {
enchant.put("level", String.valueOf(enchantLevel));
}
if (enchantments == null) {
return Set.of(enchant.asString());
}
return enchantments.stream().map(enchantment -> {
// Reuse is fine, single-threaded.
enchant.put("type", enchantment);
return enchant;
}).map(PseudoJson::asString).collect(Collectors.toSet());
}
@Override
public boolean isUnique() {
return false;
}
}
@@ -0,0 +1,78 @@
/*
* 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.commands.search;
import com.github.jikoo.planarwrappers.util.StringConverters;
import com.lishid.openinv.search.match.ItemMatchables;
import com.lishid.openinv.search.match.MatchableItem;
import com.lishid.openinv.util.PseudoJson;
import com.lishid.openinv.util.TabCompleter;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.bukkit.Material;
import org.bukkit.command.CommandSender;
import org.bukkit.util.StringUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class ItemTypeOption implements CompletableOption<MatchableItem> {
@Override
public @NotNull String getName() {
return "type";
}
@Override
public @Nullable PseudoOption<MatchableItem> parse(@NotNull CommandSender sender, @NotNull PseudoJson pseudoJson) {
String typeName = pseudoJson.getMappings().entrySet().stream().findFirst().map(Map.Entry::getValue).orElse(null);
Material material = StringConverters.toMaterial(typeName);
if (material == null) {
// Invalid material, warn about matcher spec.
return null;
}
return new PseudoOption<>(pseudoJson, ItemMatchables.isType(material));
}
@Override
public @NotNull Collection<String> suggestOptions(@NotNull CommandSender sender, @NotNull PseudoJson pseudoJson) {
if (!pseudoJson.getIdentifier().equals(getName())) {
// Suggest name while unformed.
if (StringUtil.startsWithIgnoreCase(getName(), pseudoJson.getIdentifier())) {
return Set.of(getName() + ":");
}
// If name is not a valid suggestion, suggest nothing.
return Set.of();
}
String typeName = pseudoJson.getMappings().entrySet().stream().findFirst().map(Map.Entry::getValue).orElse("");
// Find and suggest matching types in full pseudojson.
return TabCompleter.completeMaterial(typeName).stream().map(match -> {
PseudoJson type = new PseudoJson(getName());
type.put(getName(), match);
return type;
}).map(PseudoJson::asString).collect(Collectors.toSet());
}
@Override
public boolean isUnique() {
return true;
}
}
@@ -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.commands.search;
import com.lishid.openinv.OpenInv;
import com.lishid.openinv.search.bucket.PlayerBucket;
import com.lishid.openinv.search.bucket.SearchBucket;
import com.lishid.openinv.util.Permissions;
import com.lishid.openinv.util.PseudoJson;
import java.util.Collection;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class PlayerSearchOption implements CompletableOption<SearchBucket> {
private final OpenInv plugin;
public PlayerSearchOption(OpenInv plugin) {
this.plugin = plugin;
}
@Override
public @NotNull String getName() {
return "player";
}
@Override
public boolean matches(@NotNull CommandSender sender, @NotNull PseudoJson pseudoJson) {
return CompletableOption.super.matches(sender, pseudoJson)
&& Permissions.SEARCH_PLAYERS_ONLINE.hasPermission(sender);
}
@Override
public @Nullable PseudoOption<SearchBucket> parse(@NotNull CommandSender sender, @NotNull PseudoJson pseudoJson) {
Optional<Boolean> optional = pseudoJson.getMappings().entrySet().stream()
.filter(entry -> entry.getKey().contains("offline"))
.findFirst()
.map(Map.Entry::getValue)
.map(Boolean::parseBoolean);
return new PseudoOption<>(
pseudoJson, new PlayerBucket(plugin,
!(optional.orElse(false) && Permissions.SEARCH_PLAYERS_OFFLINE.hasPermission(sender))));
}
@Override
public @NotNull Collection<String> suggestOptions(@NotNull CommandSender sender, @NotNull PseudoJson pseudoJson) {
// TODO
return Set.of();
}
@Override
public boolean isUnique() {
return true;
}
}
@@ -0,0 +1,27 @@
/*
* 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.commands.search;
import com.lishid.openinv.util.PseudoJson;
import org.jetbrains.annotations.NotNull;
/**
* Data holder for completable options.
*
* @param <T> the type of data
*/
public record PseudoOption<T>(@NotNull PseudoJson pseudoJson, @NotNull T option) {}
@@ -0,0 +1,66 @@
/*
* 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.internal;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
import org.bukkit.inventory.InventoryView;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public interface IPlayerDataManager {
/**
* Loads a Player for an OfflinePlayer.
* </p>
* This method is potentially blocking, and should not be called on the main thread.
*
* @param offline the OfflinePlayer
* @return the Player loaded
*/
@Nullable Player loadPlayer(@NotNull OfflinePlayer offline);
/**
* Creates a new Player from an existing one that will function slightly better offline.
*
* @return the Player
*/
@NotNull Player inject(@NotNull Player player);
/**
* Opens an ISpecialInventory for a Player.
*
* @param player the Player opening the ISpecialInventory
* @param inventory the Inventory
*`
* @return the InventoryView opened
*/
@Nullable InventoryView openInventory(@NotNull Player player, @NotNull ISpecialInventory inventory);
/**
* 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,83 @@
/*
* 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.internal;
import com.lishid.openinv.OpenInv;
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());
if (localTitle != null) {
title = localTitle;
} else {
title = owner.getName() + titleDefaultSuffix;
}
}
return title;
}
}
@@ -0,0 +1,205 @@
/*
* 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.listeners;
import com.lishid.openinv.OpenInv;
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
*/
public class InventoryListener implements Listener {
private final OpenInv plugin;
public InventoryListener(final OpenInv plugin) {
this.plugin = plugin;
}
@EventHandler
public void onInventoryClose(@NotNull final InventoryCloseEvent event) {
if (!(event.getPlayer() instanceof Player)) {
return;
}
Player player = (Player) event.getPlayer();
if (this.plugin.getPlayerSilentChestStatus(player)) {
this.plugin.getAnySilentContainer().deactivateContainer(player);
}
}
@EventHandler(priority = EventPriority.LOWEST)
public 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)
public 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,94 @@
/*
* 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.listeners;
import com.lishid.openinv.OpenInv;
import com.lishid.openinv.util.Permissions;
import org.bukkit.entity.Player;
import org.bukkit.event.Event.Result;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.Action;
import org.bukkit.event.player.PlayerChangedWorldEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
public class PlayerListener implements Listener {
private final OpenInv plugin;
public PlayerListener(OpenInv plugin) {
this.plugin = plugin;
}
@EventHandler(priority = EventPriority.LOWEST)
public void onPlayerJoin(final PlayerJoinEvent event) {
plugin.setPlayerOnline(event.getPlayer());
}
@EventHandler(priority = EventPriority.MONITOR)
public void onPlayerQuit(PlayerQuitEvent event) {
plugin.setPlayerOffline(event.getPlayer());
}
@EventHandler
public void onWorldChange(PlayerChangedWorldEvent event) {
plugin.changeWorld(event.getPlayer());
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onPlayerInteract(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.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());
if (!any && needsAny) {
return;
}
boolean silent = Permissions.SILENT.hasPermission(player) && plugin.getPlayerSilentChestStatus(player);
// If anycontainer or silentcontainer is active
if (any || silent) {
if (plugin.getAnySilentContainer().activateContainer(player, silent, event.getClickedBlock())) {
if (silent && plugin.notifySilentChest() && needsAny && plugin.notifyAnyChest()) {
plugin.sendSystemMessage(player, "messages.info.containerBlockedSilent");
} else if (needsAny && plugin.notifyAnyChest()) {
plugin.sendSystemMessage(player, "messages.info.containerBlocked");
} else if (silent && plugin.notifySilentChest()) {
plugin.sendSystemMessage(player, "messages.info.containerSilent");
}
}
event.setCancelled(true);
}
}
}
@@ -0,0 +1,42 @@
/*
* 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.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());
}
}
@@ -0,0 +1,210 @@
/*
* 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.search;
import com.lishid.openinv.OpenInv;
import com.lishid.openinv.search.bucket.SearchBucket;
import com.lishid.openinv.search.match.ItemMatcher;
import com.lishid.openinv.search.match.MatchResult;
import com.lishid.openinv.search.match.Matchable;
import com.lishid.openinv.util.MessagePart;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.stream.Collectors;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.TextComponent;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitRunnable;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* A search function.
*/
public class Search {
public static final BaseComponent SEPARATOR = new TextComponent(", ");
private final Set<MessagePart> matches = new HashSet<>();
private final ItemMatcher matcher;
private final double totalMatchables;
private final Iterator<SearchBucket> bucketIterator;
private final Runnable callback;
private SearchBucket current = null;
private int polls = 0;
public Search(
@NotNull ItemMatcher matcher,
@NotNull Collection<@NotNull SearchBucket> buckets,
@Nullable Runnable callback) {
// Just in case, copy contents to new set.
Set<SearchBucket> bucketSet = new HashSet<>(buckets);
this.matcher = matcher;
this.totalMatchables = bucketSet.stream().mapToInt(SearchBucket::size).sum();
this.bucketIterator = bucketSet.iterator();
this.callback = callback;
}
private boolean isComplete(OpenInv plugin) {
if (matches.size() >= plugin.getSearchResultsMax()) {
return true;
}
if (current != null && current.hasNext()) {
return false;
}
while (bucketIterator.hasNext()) {
current = bucketIterator.next();
if (current.hasNext()) {
return false;
}
}
return true;
}
/**
* Search the configured amount of Matchables.
*
* <p>Matchables are assembled asynchronously, then
*
* @param plugin the OpenInv instance
*/
private void poll(OpenInv plugin) {
Collection<Matchable> syncMatchables = new ArrayList<>();
// Assemble matchables async, then search synchronously.
for (int i = 0; !isComplete(plugin) && i < plugin.getSearchPollsPerTick(); ++i) {
++polls;
// Calling #isComplete will advance to next InventoryProvider as necessary.
// Bucket matchables for searching synchronously.
syncMatchables.add(current.next());
}
Future<Boolean> future = plugin.getServer().getScheduler().callSyncMethod(plugin, () -> {
for (Matchable matchable : syncMatchables) {
MatchResult match = matchable.match(matcher);
if (match.isMatch()) {
matches.addAll(Arrays.asList(match.getMatch()));
}
}
return true;
});
try {
future.get();
} catch (InterruptedException | ExecutionException e) {
// Eat exception to prevent spam.
}
}
private void sendProgress(CommandSender sender, OpenInv plugin) {
if (!(sender instanceof Player) || polls % (10 * plugin.getSearchPollsPerTick()) != 1) {
return;
}
double progress = 100 * Math.min(polls, totalMatchables) / totalMatchables;
DecimalFormat percent = new DecimalFormat("00.0");
plugin.sendSystemMessage((Player) sender, "messages.info.search.progress",
"%progress%", percent.format(progress),
"%matches%", String.valueOf(matches.size()));
}
private void sendResults(CommandSender sender, OpenInv plugin) {
if (matches.isEmpty()) {
String localizedMessage = plugin.getLocalizedMessage(sender, "messages.info.search.noMatches");
if (localizedMessage == null) {
localizedMessage = "No items found with search parameters.";
}
sender.sendMessage(localizedMessage);
return;
}
String prefix = plugin.getLocalizedMessage(sender, "messages.info.search.matches");
if (prefix == null) {
prefix = "Locations of items matching parameters:";
}
if (sender instanceof Player) {
TextComponent textComponent = new TextComponent(prefix);
Iterator<MessagePart> iterator = matches.iterator();
int total = 0;
int searchResultsMax = plugin.getSearchResultsMax();
while (iterator.hasNext()) {
textComponent.addExtra(iterator.next().forPlayer());
++total;
if (total >= searchResultsMax) {
break;
}
if (iterator.hasNext()) {
textComponent.addExtra(SEPARATOR);
}
}
// Paper deprecated Spigot's message API.
// This is a risky deprecation ignore - Spigot may eventually do the sane thing
// and just add a method accepting components to the player directly.
//noinspection deprecation
sender.spigot().sendMessage(textComponent);
return;
}
String message = matches.stream()
.limit(plugin.getSearchResultsMax())
.map(MessagePart::forConsole)
.collect(Collectors.joining(", ", prefix, ""));
sender.sendMessage(message);
}
public void schedule(CommandSender sender, OpenInv plugin) {
new BukkitRunnable() {
@Override
public void run() {
// Scheduler struggles to cancel async tasks with high frequency.
if (isCancelled()) {
return;
}
poll(plugin);
if (isComplete(plugin)) {
if (isCancelled()) {
return;
}
sendResults(sender, plugin);
cancel();
if (callback != null) {
callback.run();
}
return;
}
sendProgress(sender, plugin);
}
}.runTaskTimerAsynchronously(plugin, 0L, 1L);
}
}
@@ -0,0 +1,123 @@
/*
* 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.search.bucket;
import com.lishid.openinv.search.match.MatchResult;
import com.lishid.openinv.search.match.Matchable;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.function.BiConsumer;
import org.bukkit.Chunk;
import org.bukkit.World;
import org.jetbrains.annotations.NotNull;
public class ChunkBucket implements SearchBucket {
private final World world;
private final boolean load;
private final List<ChunkCoord> chunks;
protected int index = -1;
private Boolean paper = null;
public ChunkBucket(@NotNull World world, int chunkX, int chunkZ, int radius, boolean load) {
this.world = world;
this.load = load;
this.chunks = new ArrayList<>();
// Order chunks based (loosely) on proximity.
spiralRectangle(radius, (deltaX, deltaZ) -> chunks.add(new ChunkCoord(chunkX + deltaX, chunkZ + deltaZ)));
}
@Override
public @NotNull Matchable next() throws IndexOutOfBoundsException {
ChunkCoord chunkCoord = chunks.get(++index);
if (!world.isChunkGenerated(chunkCoord.x(), chunkCoord.z())) {
return Matchable.EMPTY;
}
if (!load && !world.isChunkLoaded(chunkCoord.x(), chunkCoord.z())) {
return Matchable.EMPTY;
}
// If async chunk loading is not available, load chunk when checking.
if (!isPaper()) {
return new MatchableChunk(world, chunkCoord.x(), chunkCoord.z());
}
CompletableFuture<Chunk> chunkAt = world.getChunkAtAsync(chunkCoord.x(), chunkCoord.z());
Chunk chunk;
try {
chunk = chunkAt.get();
} catch (InterruptedException | ExecutionException e) {
return matcher -> MatchResult.NO_MATCH;
}
return new MatchableChunk(chunk);
}
private boolean isPaper() {
if (paper == null) {
try {
// Check for Paper.
Class.forName("com.destroystokyo.paper.PaperConfig");
paper = true;
} catch (ClassNotFoundException e) {
paper = false;
}
}
return paper;
}
@Override
public boolean hasNext() {
return index < chunks.size() - 1;
}
@Override
public int size() {
return chunks.size();
}
/**
* Produce a series of integer coordinates starting at 0, 0 that extend outward to form a centered square,
* producing closer proximity results sooner than a traditional double for loop from min to max.
*
* @param radius the radius of the square
* @param coordConsumer the method consuming the values produced
*/
private static void spiralRectangle(int radius, BiConsumer<Integer, Integer> coordConsumer) {
for (int x = 0; x <= radius; ++x) {
for (int z = 0; z <= radius; ++z) {
coordConsumer.accept(x, z);
if (x != 0) {
coordConsumer.accept(-x, z);
if (z != 0) {
coordConsumer.accept(-x, -z);
}
}
if (z != 0) {
coordConsumer.accept(x, -z);
}
}
}
}
}
@@ -0,0 +1,22 @@
/*
* 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.search.bucket;
/**
* A data holder for chunk coordinates.
*/
public record ChunkCoord(int x, int z) {}
@@ -0,0 +1,128 @@
/*
* 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.search.bucket;
import com.lishid.openinv.search.match.ItemMatcher;
import com.lishid.openinv.search.match.MatchResult;
import com.lishid.openinv.search.match.Matchable;
import com.lishid.openinv.util.MessagePart;
import java.util.ArrayList;
import java.util.List;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.ClickEvent;
import net.md_5.bungee.api.chat.HoverEvent;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.chat.hover.content.Text;
import org.bukkit.Chunk;
import org.bukkit.World;
import org.bukkit.block.BlockState;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryHolder;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
class MatchableChunk implements Matchable {
private Chunk chunk;
private World world;
private int chunkX;
private int chunkZ;
MatchableChunk(@NotNull Chunk chunk) {
this.chunk = chunk;
}
MatchableChunk(@NotNull World world, int chunkX, int chunkZ) {
this.world = world;
this.chunkX = chunkX;
this.chunkZ = chunkZ;
}
private Chunk getChunk() {
if (chunk == null) {
chunk = world.getChunkAt(chunkX, chunkZ);
}
return chunk;
}
@Override
public @NotNull MatchResult match(@NotNull ItemMatcher matcher) {
List<BlockState> blocks = new ArrayList<>();
for (BlockState tileEntity : getChunk().getTileEntities()) {
if (!(tileEntity instanceof InventoryHolder)) {
continue;
}
Inventory inventory = ((InventoryHolder) tileEntity).getInventory();
for (ItemStack content : inventory.getContents()) {
if (matcher.matches(content)) {
blocks.add(tileEntity);
break;
}
}
}
if (blocks.isEmpty()) {
return MatchResult.NO_MATCH;
}
MessagePart[] matches = new MessagePart[blocks.size()];
for (int i = 0; i < matches.length; ++i) {
BlockState state = blocks.get(i);
matches[i] = new MessagePart(() -> getComponent(state), () -> getString(state));
}
return new MatchResult(matches);
}
private static BaseComponent getComponent(BlockState state) {
TextComponent component = new TextComponent(state.getType().name());
String command = String.format(
"/execute in %s minecraft:tp %s %s %s",
state.getWorld().getName(),
state.getX(),
state.getY() + 1,
state.getZ());
component.setClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, command));
String hover = String.format(
"%s{world:%s,x:%s,y:%s,z:%s}\n\nClick to teleport.",
state.getType().getKey(),
state.getWorld().getName(),
state.getX(),
state.getY(),
state.getZ());
Text hoverText = new Text(new BaseComponent[] { new TextComponent(hover)});
component.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, hoverText));
return component;
}
private static String getString(BlockState state) {
return String.format(
"%s{world:%s,x:%s,y:%s,z:%s}",
state.getType().getKey(),
state.getWorld().getName(),
state.getX(),
state.getY(),
state.getZ());
}
}
@@ -0,0 +1,174 @@
/*
* 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.search.bucket;
import com.lishid.openinv.OpenInv;
import com.lishid.openinv.search.match.ItemMatcher;
import com.lishid.openinv.search.match.MatchResult;
import com.lishid.openinv.search.match.Matchable;
import com.lishid.openinv.util.MessagePart;
import java.util.UUID;
import java.util.function.Supplier;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.ClickEvent;
import net.md_5.bungee.api.chat.HoverEvent;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.chat.hover.content.Text;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.InventoryType;
import org.bukkit.inventory.InventoryView;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.PlayerInventory;
import org.jetbrains.annotations.NotNull;
public class MatchablePlayer implements Matchable {
private final OpenInv plugin;
private final OfflinePlayer offlinePlayer;
public MatchablePlayer(OpenInv plugin, OfflinePlayer offlinePlayer) {
this.plugin = plugin;
this.offlinePlayer = offlinePlayer;
}
@Override
public @NotNull MatchResult match(@NotNull ItemMatcher matcher) {
Player player;
if (offlinePlayer instanceof Player) {
player = (Player) offlinePlayer;
} else {
player = plugin.loadPlayer(offlinePlayer);
}
if (player == null) {
return MatchResult.NO_MATCH;
}
boolean matchInv = false;
boolean matchEnder = false;
PlayerInventory playerInventory = player.getInventory();
// Check inventory. Modern implementations include armor and off hand in contents.
for (ItemStack content : playerInventory.getContents()) {
if (matcher.matches(content)) {
matchInv = true;
break;
}
}
InventoryView openInventory = player.getOpenInventory();
// If no matches are found yet, check cursor.
if (!matchInv && matcher.matches(openInventory.getCursor())) {
matchInv = true;
}
// Check player crafting inventory.
if (!matchInv && openInventory.getType() == InventoryType.CRAFTING) {
for (ItemStack content : openInventory.getTopInventory().getContents()) {
if (matcher.matches(content)) {
matchInv = true;
break;
}
}
}
// Check ender chest.
for (ItemStack content : player.getEnderChest().getContents()) {
if (matcher.matches(content)) {
matchEnder = true;
break;
}
}
if (!matchInv && !matchEnder) {
return MatchResult.NO_MATCH;
}
String name = player.getName();
UUID uuid = player.getUniqueId();
if (matchInv && matchEnder) {
return new MatchResult(new MessagePart[] {
new MessagePart(() -> getBoth(name, uuid), () -> name + " (inv, ender)")
});
}
Supplier<BaseComponent> componentSupplier;
Supplier<String> stringSupplier;
if (matchInv) {
componentSupplier = () -> getSingle(name, () -> getInvClick(uuid), () -> getInvHover(name));
stringSupplier = () -> name + " (inv)";
} else {
componentSupplier = () -> getSingle(name, () -> getEnderClick(uuid), () -> getEnderHover(name));
stringSupplier = () -> name + " (ender)";
}
return new MatchResult(new MessagePart[] { new MessagePart(componentSupplier, stringSupplier) });
}
private static BaseComponent getBoth(String name, UUID uuid) {
BaseComponent component = new TextComponent(name + " (inv,");
component.setClickEvent(getInvClick(uuid));
component.setHoverEvent(getInvHover(name));
BaseComponent enderComponent = new TextComponent(" ender)");
enderComponent.setClickEvent(getEnderClick(uuid));
enderComponent.setHoverEvent(getEnderHover(name));
component.addExtra(enderComponent);
return component;
}
private static BaseComponent getSingle(String name, Supplier<ClickEvent> click, Supplier<HoverEvent> hover) {
BaseComponent component = new TextComponent(name);
component.setClickEvent(click.get());
component.setHoverEvent(hover.get());
return component;
}
private static ClickEvent getInvClick(UUID uuid) {
return getClick("/openinv:openinv ", uuid);
}
private static HoverEvent getInvHover(String name) {
return getHover("/openinv ", name);
}
private static ClickEvent getEnderClick(UUID uuid) {
return getClick("/openinv:openender ", uuid);
}
private static HoverEvent getEnderHover(String name) {
return getHover("/openender ", name);
}
private static ClickEvent getClick(String command, UUID uuid) {
// Actual command run is not exposed to user, so there's no reason not to use UUID and be precise.
return new ClickEvent(ClickEvent.Action.RUN_COMMAND, command + uuid);
}
private static HoverEvent getHover(String command, String name) {
BaseComponent[] text = new BaseComponent[] { new TextComponent(command + name) };
return new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text(text));
}
}
@@ -0,0 +1,56 @@
/*
* 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.search.bucket;
import com.lishid.openinv.OpenInv;
import com.lishid.openinv.search.match.Matchable;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
public class PlayerBucket implements SearchBucket {
private final OpenInv plugin;
private final OfflinePlayer[] players;
private int index = -1;
public PlayerBucket(OpenInv plugin, boolean online) {
this.plugin = plugin;
if (online) {
this.players = Bukkit.getOnlinePlayers().toArray(new Player[0]);
} else {
this.players = Bukkit.getOfflinePlayers();
}
}
@Override
public boolean hasNext() {
return index < players.length - 1;
}
@Override
public @NotNull Matchable next() {
return new MatchablePlayer(plugin, players[++index]);
}
@Override
public int size() {
return 1;
}
}
@@ -0,0 +1,30 @@
/*
* 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.search.bucket;
import com.lishid.openinv.search.match.Matchable;
import org.jetbrains.annotations.NotNull;
public interface SearchBucket {
boolean hasNext();
@NotNull Matchable next() throws IndexOutOfBoundsException;
int size();
}
@@ -0,0 +1,64 @@
/*
* 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.search.match;
import org.bukkit.Material;
import org.bukkit.enchantments.Enchantment;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class ItemMatchables {
public static MatchableItem isType(@NotNull Material material) {
return itemStack -> itemStack.getType() == material;
}
public static MatchableItem hasAmount(int minAmount) {
return itemStack -> itemStack.getAmount() >= minAmount;
}
public static MatchableMeta hasEnchant(@Nullable Enchantment enchantment, @Nullable Integer minLevel) {
return itemMeta -> {
if (minLevel == null) {
if (enchantment == null) {
// Level and enchantment not specified, any enchantment of any level matches.
return !itemMeta.getEnchants().isEmpty();
}
// Level not specified, any level of enchantment matches.
return itemMeta.hasEnchant(enchantment);
}
if (enchantment != null) {
// Level and enchantment are specified, enchantment must meet or exceed level.
return itemMeta.getEnchantLevel(enchantment) >= minLevel;
}
// Enchantment not specified, any enchantment that meets or exceeds level matches.
for (int enchantLevel : itemMeta.getEnchants().values()) {
if (enchantLevel >= minLevel) {
return true;
}
}
return false;
};
}
private ItemMatchables() {}
}
@@ -0,0 +1,79 @@
/*
* 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.search.match;
import java.util.Collection;
import java.util.HashSet;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.jetbrains.annotations.Nullable;
/**
* A collection of multiple filters used to match items.
*/
public class ItemMatcher {
private final Collection<MatchableItem> baseOptions;
private final Collection<MatchableMeta> metaOptions;
public ItemMatcher(
Collection<MatchableItem> baseOptions,
Collection<MatchableMeta> metaOptions) {
this.baseOptions = new HashSet<>(baseOptions);
this.metaOptions = new HashSet<>(metaOptions);
}
public boolean matches(@Nullable ItemStack other) {
// Ensure item exists.
if (other == null || other.getType() == Material.AIR) {
return false;
}
// Match options against base item.
for (MatchableItem baseOption : baseOptions) {
if (!baseOption.matches(other)) {
return false;
}
}
// If no meta options exist, item matches.
if (metaOptions.isEmpty()) {
return true;
}
// Require item meta for meta options.
ItemMeta meta;
if (!other.hasItemMeta() || (meta = other.getItemMeta()) == null) {
return false;
}
// Match meta-based options.
for (MatchableMeta metaOption : metaOptions) {
if (!metaOption.matches(meta)) {
return false;
}
}
return true;
}
public boolean isEmpty() {
return baseOptions.isEmpty() && metaOptions.isEmpty();
}
}
@@ -0,0 +1,45 @@
/*
* 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.search.match;
import com.lishid.openinv.util.MessagePart;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class MatchResult {
public static final MatchResult NO_MATCH = new MatchResult();
private final MessagePart[] result;
private MatchResult() {
result = null;
}
public MatchResult(MessagePart @NotNull [] result) {
this.result = result;
}
public boolean isMatch() {
return result != null;
}
public MessagePart @Nullable [] getMatch() {
return result;
}
}
@@ -0,0 +1,27 @@
/*
* 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.search.match;
import org.jetbrains.annotations.NotNull;
public interface Matchable {
Matchable EMPTY = matcher -> MatchResult.NO_MATCH;
@NotNull MatchResult match(@NotNull ItemMatcher matcher);
}
@@ -0,0 +1,26 @@
/*
* 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.search.match;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
public interface MatchableItem {
boolean matches(@NotNull ItemStack itemStack);
}
@@ -0,0 +1,26 @@
/*
* 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.search.match;
import org.bukkit.inventory.meta.ItemMeta;
import org.jetbrains.annotations.NotNull;
public interface MatchableMeta {
boolean matches(@NotNull ItemMeta itemMeta);
}
@@ -1,16 +1,35 @@
package com.lishid.openinv.util;
/*
* 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/>.
*/
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
package com.lishid.openinv.util;
import com.google.common.collect.Multimap;
import com.google.common.collect.TreeMultimap;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Predicate;
/**
* A minimal time-based cache implementation backed by a HashMap and TreeMultimap.
*
* A minimal thread-safe time-based cache implementation backed by a HashMap and TreeMultimap.
*
* @author Jikoo
*/
public class Cache<K, V> {
@@ -18,30 +37,20 @@ 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;
private final Predicate<V> inUseCheck;
private final Consumer<V> 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
* @param inUseCheck Predicate used to check if a key is considered in use
* @param postRemoval Consumer used to perform any operations required when a key is invalidated
*/
public Cache(long retention, Function<V> inUseCheck, Function<V> postRemoval) {
this.internal = new HashMap<K, V>();
public Cache(final long retention, final Predicate<V> inUseCheck, final Consumer<V> postRemoval) {
this.internal = new HashMap<>();
this.expiry = TreeMultimap.create(new Comparator<Long>() {
@Override
public int compare(Long long1, Long long2) {
return long1.compareTo(long2);
}
},
new Comparator<K>() {
@Override
public int compare(K k1, K k2) {
return 0;
}
});
this.expiry = TreeMultimap.create(Long::compareTo, (k1, k2) -> Objects.equals(k1, k2) ? 0 : 1);
this.retention = retention;
this.inUseCheck = inUseCheck;
@@ -51,66 +60,74 @@ public class Cache<K, V> {
/**
* 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(K key, V value) {
public void put(final K key, final V value) {
// Invalidate key - runs lazy check and ensures value won't be cleaned up early
invalidate(key);
this.invalidate(key);
internal.put(key, value);
expiry.put(System.currentTimeMillis() + retention, 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(K key) {
public V get(final K key) {
// Run lazy check to clean cache
lazyCheck();
this.lazyCheck();
return internal.get(key);
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(K key) {
public boolean containsKey(final K key) {
// Run lazy check to clean cache
lazyCheck();
this.lazyCheck();
return internal.containsKey(key);
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(K key) {
public void invalidate(final K key) {
// Run lazy check to clean cache
lazyCheck();
this.lazyCheck();
if (!internal.containsKey(key)) {
// Value either not present or cleaned by lazy check. Either way, we're good
return;
}
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
internal.remove(key);
// 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 = expiry.entries().iterator(); iterator.hasNext();) {
if (key.equals(iterator.next().getValue())) {
iterator.remove();
break;
// 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;
}
}
}
}
@@ -119,11 +136,13 @@ public class Cache<K, V> {
* Forcibly invalidates all keys, even if they are considered to be in use.
*/
public void invalidateAll() {
for (V value : internal.values()) {
postRemoval.run(value);
synchronized (this.internal) {
for (V value : this.internal.values()) {
this.postRemoval.accept(value);
}
this.expiry.clear();
this.internal.clear();
}
expiry.clear();
internal.clear();
}
/**
@@ -132,28 +151,36 @@ public class Cache<K, V> {
*/
private void lazyCheck() {
long now = System.currentTimeMillis();
long nextExpiry = now + retention;
for (Iterator<Map.Entry<Long, K>> iterator = expiry.entries().iterator(); iterator.hasNext();) {
Map.Entry<Long, K> entry = iterator.next();
synchronized (this.internal) {
List<K> inUse = new ArrayList<>();
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;
if (entry.getKey() > now) {
break;
}
iterator.remove();
if (this.inUseCheck.test(this.internal.get(entry.getValue()))) {
inUse.add(entry.getValue());
continue;
}
V value = this.internal.remove(entry.getValue());
if (value == null) {
continue;
}
this.postRemoval.accept(value);
}
iterator.remove();
if (inUseCheck.run(internal.get(entry.getValue()))) {
expiry.put(nextExpiry, entry.getValue());
continue;
long nextExpiry = now + this.retention;
for (K value : inUse) {
this.expiry.put(nextExpiry, value);
}
V value = internal.remove(entry.getValue());
if (value == null) {
continue;
}
postRemoval.run(value);
}
}
@@ -0,0 +1,158 @@
/*
* 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 com.lishid.openinv.OpenInv;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.bukkit.OfflinePlayer;
import org.bukkit.configuration.ConfigurationSection;
public class ConfigUpdater {
private final OpenInv plugin;
public ConfigUpdater(OpenInv plugin) {
this.plugin = plugin;
}
public void checkForUpdates() {
final int version = plugin.getConfig().getInt("config-version", 1);
ConfigurationSection defaults = plugin.getConfig().getDefaults();
if (defaults == null || version >= defaults.getInt("config-version")) {
return;
}
plugin.getLogger().info("Configuration update found! Performing update...");
// Backup the old config file
try {
plugin.getConfig().save(new File(plugin.getDataFolder(), "config_old.yml"));
plugin.getLogger().info("Backed up config.yml to config_old.yml before updating.");
} catch (IOException e) {
plugin.getLogger().warning("Could not back up config.yml before updating!");
}
plugin.getServer().getScheduler().runTaskAsynchronously(plugin, () -> {
if (version < 2) {
updateConfig1To2();
}
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.search.polls-per-tick", 5);
plugin.getConfig().set("settings.search.max-results", 40);
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() {
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() {
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 (section == null) {
return;
}
Set<String> keys = section.getKeys(false);
// Ensure section has content
if (keys.isEmpty()) {
return;
}
final Map<String, Boolean> toggles = new HashMap<>();
for (String playerName : keys) {
OfflinePlayer player = plugin.matchPlayer(playerName);
if (player != null) {
toggles.put(plugin.getPlayerID(player), section.getBoolean(playerName + ".toggle", false));
}
}
plugin.getServer().getScheduler().runTask(plugin, () -> {
// Wipe old ConfigurationSection
plugin.getConfig().set(sectionName, null);
// Prepare new ConfigurationSection
ConfigurationSection newSection = plugin.getConfig().getConfigurationSection(newSectionName);
if (newSection == null) {
newSection = plugin.getConfig().createSection(newSectionName);
}
// Set new values
for (Map.Entry<String, Boolean> entry : toggles.entrySet()) {
newSection.set(entry.getKey(), entry.getValue());
}
});
}
}
@@ -1,12 +0,0 @@
package com.lishid.openinv.util;
/**
* Abstraction for some simple cache calls.
*
* @author Jikoo
*/
public abstract class Function<V> {
public abstract boolean run(V value);
}
@@ -0,0 +1,221 @@
/*
* 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 com.lishid.openinv.internal.IAnySilentContainer;
import com.lishid.openinv.internal.IPlayerDataManager;
import com.lishid.openinv.internal.ISpecialEnderChest;
import com.lishid.openinv.internal.ISpecialPlayerInventory;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
public class InternalAccessor {
private final Plugin plugin;
private final String version;
private boolean supported = false;
private IPlayerDataManager playerDataManager;
private IAnySilentContainer anySilentContainer;
public InternalAccessor(final Plugin plugin) {
this.plugin = plugin;
String packageName = plugin.getServer().getClass().getPackage().getName();
this.version = packageName.substring(packageName.lastIndexOf('.') + 1);
try {
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.isUsable();
} catch (Exception ignored) {}
}
public String getReleasesLink() {
switch (version) {
case "1_4_5":
case "1_4_6":
case "v1_4_R1":
case "v1_5_R2":
case "v1_5_R3":
case "v1_6_R1":
case "v1_6_R2":
case "v1_6_R3":
case "v1_7_R1":
case "v1_7_R2":
case "v1_7_R3":
case "v1_7_R4":
case "v1_8_R1":
case "v1_8_R2":
case "v1_9_R1":
case "v1_9_R2":
case "v1_10_R1":
case "v1_11_R1":
case "v1_12_R1":
return "https://github.com/lishid/OpenInv/releases/tag/4.0.0 (OpenInv-legacy)";
case "v1_13_R1":
return "https://github.com/lishid/OpenInv/releases/tag/4.0.0";
case "v1_13_R2":
return "https://github.com/lishid/OpenInv/releases/tag/4.0.7";
case "v1_14_R1":
return "https://github.com/lishid/OpenInv/releases/tag/4.1.1";
case "v1_16_R1":
return "https://github.com/lishid/OpenInv/releases/tag/4.1.4";
case "v1_8_R3":
case "v1_15_R1":
case "v1_16_R2":
return "https://github.com/lishid/OpenInv/releases/tag/4.1.5";
case "v1_16_R3":
return "https://github.com/Jikoo/OpenInv/releases/tag/4.1.8";
case "v1_17_R1":
default:
return "https://github.com/Jikoo/OpenInv/releases";
}
}
private <T> T createObject(final Class<? extends T> assignableClass, final String className,
final Object... params) throws ClassCastException, ClassNotFoundException,
InstantiationException, IllegalAccessException, IllegalArgumentException,
InvocationTargetException, NoSuchMethodException, SecurityException {
// 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) {
return assignableClass.cast(internalClass.getConstructor().newInstance());
}
// Search constructors for one matching the given parameters
nextConstructor: for (Constructor<?> constructor : internalClass.getConstructors()) {
Class<?>[] requiredClasses = constructor.getParameterTypes();
if (requiredClasses.length != params.length) {
continue;
}
for (int i = 0; i < params.length; ++i) {
if (!requiredClasses[i].isAssignableFrom(params[i].getClass())) {
continue nextConstructor;
}
}
return assignableClass.cast(constructor.newInstance(params));
}
StringBuilder builder = new StringBuilder("Found class ").append(internalClass.getName())
.append(" but cannot find any matching constructors for [");
for (Object object : params) {
builder.append(object.getClass().getName()).append(", ");
}
builder.delete(builder.length() - 2, builder.length());
String message = builder.append(']').toString();
this.plugin.getLogger().warning(message);
throw new IllegalArgumentException(message);
}
/**
* Creates an instance of the IAnySilentContainer implementation for the current server version.
*
* @return the IAnySilentContainer
* @throws IllegalStateException if server version is unsupported
*/
public IAnySilentContainer getAnySilentContainer() {
if (!this.supported) {
throw new IllegalStateException(String.format("Unsupported server version %s!", this.version));
}
return this.anySilentContainer;
}
/**
* Creates an instance of the IPlayerDataManager implementation for the current server version.
*
* @return the IPlayerDataManager
* @throws IllegalStateException if server version is unsupported
*/
public IPlayerDataManager getPlayerDataManager() {
if (!this.supported) {
throw new IllegalStateException(String.format("Unsupported server version %s!", this.version));
}
return this.playerDataManager;
}
/**
* Gets the server implementation version. If not initialized, returns the string "null"
* instead.
*
* @return the version, or "null"
*/
public String getVersion() {
return this.version != null ? this.version : "null";
}
/**
* Checks if the server implementation is supported.
*
* @return true if initialized for a supported server version
*/
public boolean isSupported() {
return this.supported;
}
/**
* Creates an instance of the ISpecialEnderChest implementation for the given Player, or
* null if the current version is unsupported.
*
* @param player the Player
* @param online true if the Player is online
* @return the ISpecialEnderChest created
* @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()));
}
}
/**
* Creates an instance of the ISpecialPlayerInventory implementation for the given Player..
*
* @param player the Player
* @param online true if the Player is online
* @return the ISpecialPlayerInventory created
* @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()));
}
}
}
@@ -0,0 +1,170 @@
/*
* 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 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 YamlConfiguration getOrLoadLocale(@NotNull String locale) {
YamlConfiguration loaded = locales.get(locale);
if (loaded != null) {
return loaded;
}
InputStream resourceStream = plugin.getResource(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;
}
@Nullable
public 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;
}
@Nullable
public String getValue(@NotNull String key, @Nullable String locale, @NotNull String... 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;
}
}
@@ -0,0 +1,80 @@
/*
* 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.List;
import java.util.function.Supplier;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.ClickEvent;
import net.md_5.bungee.api.chat.TextComponent;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class MessagePart {
private final Supplier<BaseComponent> playerProvider;
private final Supplier<String> consoleProvider;
private @Nullable BaseComponent playerComponent;
private @Nullable String consoleComponent;
public MessagePart(@NotNull Supplier<@NotNull BaseComponent> playerProvider) {
this(playerProvider, () -> unwrapComponent(playerProvider.get()));
}
public MessagePart(
@NotNull Supplier<@NotNull BaseComponent> playerProvider,
@NotNull Supplier<@NotNull String> consoleProvider) {
this.playerProvider = playerProvider;
this.consoleProvider = consoleProvider;
}
public BaseComponent forPlayer() {
if (this.playerComponent == null) {
this.playerComponent = playerProvider.get();
}
return this.playerComponent;
}
public String forConsole() {
if (consoleComponent == null) {
consoleComponent = consoleProvider.get();
}
return consoleComponent;
}
private static String unwrapComponent(BaseComponent component) {
StringBuilder builder = new StringBuilder();
ClickEvent clickEvent = component.getClickEvent();
if (clickEvent != null) {
// Prefer click events, i.e. "Check out our GitHub" with a link -> raw link
builder.append(clickEvent.getValue());
} else if (component instanceof TextComponent) {
builder.append(((TextComponent) component).getText());
}
List<BaseComponent> extra = component.getExtra();
if (extra != null) {
for (BaseComponent extraComponent : extra) {
builder.append(unwrapComponent(extraComponent));
}
}
return builder.toString();
}
}
@@ -0,0 +1,82 @@
/*
* 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 org.bukkit.permissions.Permissible;
public enum Permissions {
OPENINV("openinv"),
OVERRIDE("override"),
EXEMPT("exempt"),
CROSSWORLD("crossworld"),
SILENT("silent"),
SILENT_DEFAULT("silent.default", true),
ANYCHEST("anychest"),
ANY_DEFAULT("any.default", true),
ENDERCHEST("openender"),
ENDERCHEST_ALL("openenderall"),
SEARCH("search"),
SEARCH_PLAYERS_ONLINE("search.players.online"),
SEARCH_PLAYERS_OFFLINE("search.players.offline"),
SEARCH_CHUNKS_LOADED("search.chunks.loaded"),
SEARCH_CHUNKS_UNLOADED("search.chunks.unloaded"),
EDITINV("editinv"),
EDITENDER("editender"),
OPENSELF("openself"),
OPENONLINE("openonline"),
OPENOFFLINE("openoffline"),
SPECTATE("spectate");
private final String permission;
private final boolean uninheritable;
Permissions(String permission) {
this(permission, false);
}
Permissions(String permission, boolean uninheritable) {
this.permission = "OpenInv." + permission;
this.uninheritable = uninheritable;
}
public boolean hasPermission(Permissible permissible) {
boolean hasPermission = permissible.hasPermission(permission);
if (uninheritable || hasPermission || permissible.isPermissionSet(permission)) {
return hasPermission;
}
StringBuilder permissionDestroyer = new StringBuilder(permission);
for (int lastPeriod = permissionDestroyer.lastIndexOf("."); lastPeriod > 0;
lastPeriod = permissionDestroyer.lastIndexOf(".")) {
permissionDestroyer.delete(lastPeriod + 1, permissionDestroyer.length()).append('*');
hasPermission = permissible.hasPermission(permissionDestroyer.toString());
if (hasPermission || permissible.isPermissionSet(permissionDestroyer.toString())) {
return hasPermission;
}
permissionDestroyer.delete(lastPeriod, permissionDestroyer.length());
}
return permissible.hasPermission("*");
}
}
@@ -0,0 +1,181 @@
/*
* 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.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import org.jetbrains.annotations.NotNull;
/**
* A data container for JSON-esque expressions.
*
* <p>Ex: A ChunkBucket can be expressed <code>chunks{world:world_name,x:0,z:0,radius:10,load:true}</code>. The
* starting "chunks" identifies it as a ChunkBucket and the content in braces contains the construction detail.
* However, a simpler detail, such as a MatchableItem, may be specified <code>type:IRON_SWORD</code> where "type"
* is both the identifier of the MatchableItem and the key of the sole value.
*
* <p>A best-effort will be made to match other start and finish demarcation techniques.
*/
public class PseudoJson {
private static final char[] BRACE_OPEN = new char[] { '{', '[', '(' };
private static final char[] BRACE_CLOSE = new char[] { '}', ']', ')' };
private static final Set<Character> RESERVED_CHARACTERS = new HashSet<>();
static {
for (char brace : BRACE_OPEN) {
RESERVED_CHARACTERS.add(brace);
}
for (char brace : BRACE_CLOSE) {
RESERVED_CHARACTERS.add(brace);
}
RESERVED_CHARACTERS.add(',');
RESERVED_CHARACTERS.add(':');
}
private final String identifier;
private final Map<String, String> mappings;
public PseudoJson(@NotNull String identifier) {
this(identifier, new HashMap<>());
}
public PseudoJson(@NotNull String identifier, @NotNull Map<String, String> mappings) {
this.identifier = identifier;
mappings.forEach(PseudoJson::validate);
this.mappings = mappings;
}
public @NotNull String getIdentifier() {
return identifier;
}
public @NotNull Optional<String> put(@NotNull String key, @NotNull String value) {
validate(key, value);
return Optional.ofNullable(this.mappings.put(key, value));
}
public @NotNull Optional<String> get(@NotNull String key) {
return Optional.ofNullable(this.mappings.get(key));
}
public @NotNull Map<String, String> getMappings() {
return Collections.unmodifiableMap(mappings);
}
public Optional<String> remove(@NotNull String key) {
return Optional.ofNullable(this.mappings.get(key));
}
public String asString() {
StringBuilder builder = new StringBuilder(identifier);
builder.append('{');
for (Iterator<Map.Entry<String, String>> iterator = mappings.entrySet().iterator(); iterator.hasNext();) {
Map.Entry<String, String> entry = iterator.next();
builder.append(entry.getKey()).append(':').append(entry.getValue());
if (iterator.hasNext()) {
builder.append(',');
}
}
builder.append('}');
return builder.toString();
}
@Override
public String toString() {
return asString();
}
private static void validate(String key, String value) {
if (RESERVED_CHARACTERS.stream().anyMatch(character -> key.indexOf(character) >= 0 || value.indexOf(character) >= 0)) {
throw new IllegalArgumentException(String.format("Primitive pseudo-JSON reserves characters %s", RESERVED_CHARACTERS));
}
}
public static PseudoJson fromString(String pseudoJson) {
int open = -1;
for (char brace : BRACE_OPEN) {
open = pseudoJson.indexOf(brace);
if (open >= 0) {
break;
}
}
String identifier = getIdentifier(pseudoJson, open);
int close = -1;
for (char brace : BRACE_CLOSE) {
close = pseudoJson.lastIndexOf(brace);
if (close >= 0) {
break;
}
}
if (open >= 0) {
if (close <= open) {
// Ignore bad ending brace.
pseudoJson = pseudoJson.substring(open + 1);
} else {
// Use only bracketed content
pseudoJson = pseudoJson.substring(open + 1, close);
}
}
return new PseudoJson(identifier, getMappings(pseudoJson));
}
private static String getIdentifier(String pseudoJson, int braceOpen) {
int identifierEnd;
if (braceOpen >= 0) {
identifierEnd = braceOpen;
} else {
// No quote, use first mapping.
identifierEnd = pseudoJson.indexOf(':');
if (identifierEnd == -1) {
// No mappings, use whole string.
return pseudoJson;
}
}
return pseudoJson.substring(0, identifierEnd);
}
private static Map<String, String> getMappings(String pseudoJson) {
String[] mappings = pseudoJson.split(",");
Map<String, String> data = new HashMap<>();
for (String mapping : mappings) {
String[] datum = mapping.split(":");
if (datum.length >= 2) {
data.put(datum[0], datum[1]);
}
}
return data;
}
}
@@ -0,0 +1,182 @@
/*
* 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.Keyed;
import org.bukkit.Material;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.util.StringUtil;
import org.jetbrains.annotations.NotNull;
/**
* 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(@NotNull 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(@NotNull String argument, @NotNull Class<? extends Enum<?>> enumClazz) {
return completeObject(argument, Enum::name, enumClazz.getEnumConstants());
}
/**
* Offer tab completions for {@link Material Materials}.
*
* @param argument the argument to complete
* @return the matching {@code Material} keys
*/
public static List<String> completeMaterial(@NotNull String argument) {
argument = argument.toLowerCase(Locale.ENGLISH);
List<String> completions = new ArrayList<>();
for (Material material : Material.values()) {
if (material.name().toLowerCase(Locale.ENGLISH).startsWith(argument)
|| material.getKey().toString().startsWith(argument)
|| material.getKey().getKey().startsWith(argument)) {
completions.add(material.getKey().toString());
}
}
return completions;
}
/**
* Offer tab completions for {@link Keyed} values.
*
* @param argument the argument to complete
* @param options the {@code Keyed} values
* @return the matching keys
*/
public static List<String> completeKeyed(@NotNull String argument, @NotNull Keyed @NotNull [] options) {
argument = argument.toLowerCase(Locale.ENGLISH);
List<String> completions = new ArrayList<>();
for (Keyed keyed : options) {
if (keyed.getKey().toString().startsWith(argument)
|| keyed.getKey().getKey().startsWith(argument)) {
completions.add(keyed.getKey().toString());
}
}
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(@NotNull String argument, @NotNull String @NotNull [] 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(@NotNull 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(@NotNull String argument, @NotNull Function<T, String> converter, @NotNull T @NotNull [] options) {
argument = argument.toLowerCase(Locale.ENGLISH);
List<String> completions = new ArrayList<>();
for (T option : options) {
String optionString = converter.apply(option).toLowerCase(Locale.ENGLISH);
if (optionString.startsWith(argument)) {
completions.add(optionString);
}
}
return completions;
}
private TabCompleter() {}
}
+7
View File
@@ -0,0 +1,7 @@
config-version: 5
settings:
search:
polls-per-tick: 5
max-results: 40
disable-saving: false
locale: 'en_us'
+30
View File
@@ -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'
Loaded 100 of 233 files, more files were not shown because too many files have changed in this diff. Show more