Compare commits

...
273 Commits
Author SHA1 Message Date
Luck 63878166db Release 5.2 2020-10-14 12:18:50 +01:00
Luck 2ea43d4b2e Some misc refactoring and cleanup 2020-09-10 21:13:12 +01:00
Luck 442c7d46f9 Ensure that player data is removed even if a corresponding user doesn't exist 2020-09-07 18:16:25 +01:00
Luck 971bb67b51 Add support for user deletion from the web editor 2020-09-07 18:13:33 +01:00
Luck 020aff66aa Refactor web editor session handling 2020-09-07 18:06:40 +01:00
Luck 198b86d7c3 Implement the basis for deleting player uuid data from storage 2020-09-07 17:22:42 +01:00
Luck 84e7fd4d09 Update usage string for /lp import 2020-09-07 16:55:08 +01:00
Luck 4027129052 Default to merge when using /lp import (#2601) 2020-09-07 16:54:27 +01:00
Luck 63f67ca72c Default use-vault-server to false (#2599) 2020-09-07 16:51:07 +01:00
Luck 2adaea2dac Avoid calling JsonObject#size for compat with older gson versions (#2598) 2020-09-05 22:00:56 +01:00
Luck 5e48224c78 Use a map factory when resolving permissions for cached data 2020-08-30 09:14:33 +01:00
Luck cc1d8984da Config option for registering brigadier command list data 2020-08-30 09:13:59 +01:00
Luck 8d9b4182f4 Modify issue templates slightly 2020-08-29 18:37:21 +01:00
Luck f972113d11 Add GitHub issue templates
Credit to @Andre601 and PR #1735 for the original proposal of these templates.

I've held off from adding this for a while, but I now feel that the GitHub UI is nice enough to justify the change.
2020-08-29 18:32:39 +01:00
Luck 4d5a24f2c4 ContextSetJsonSerializer output should be sorted 2020-08-14 16:25:55 +01:00
Luck e2b575dd24 Handle null or empty server/world field in SQL (#2553) 2020-08-13 11:40:33 +01:00
Luck 7fae141e33 Ignore cancelled events for OP command disabling (#2527) 2020-08-13 11:03:24 +01:00
Luck 6f90e545fc Don't attempt further poll operations after SQL messenger has closed (#2516) 2020-08-13 10:49:18 +01:00
Luck ee3bb22c2c Improve Bukkit identifyClassLoader (#2444) 2020-08-13 10:31:20 +01:00
Luck 361a4d66bc Refactor exporter into separate classes 2020-08-13 10:12:36 +01:00
Luck e1bac438aa Add /lp export --without-groups (#2498) 2020-08-13 10:00:49 +01:00
Luck ac9706b83f Ignore FileAlreadyExistsException in MoreFiles#createDirectories (#2525) 2020-08-10 22:29:12 +01:00
Luck fea757887c Use official configurate 3.7 branch 2020-08-01 11:08:46 +01:00
Luck 1eab127f50 Refactor op command detection 2020-08-01 10:20:49 +01:00
Luck 92e008ff0a Fix bug in PermissionHolder#hasNode (#2490)
Regression introduced in bd6d3ab7c0
2020-07-26 14:14:41 +01:00
Luck 9659333d0e Some misc cleanup for migration commands 2020-07-25 16:56:52 +01:00
Larry d8020e77fe Fix import with no users object (#2488, #2489) 2020-07-24 11:43:49 +01:00
Luck 232d17436c Rename config based static calculator 2020-07-21 11:26:16 +01:00
Luck 1461651e22 Include a wider range of potential contexts in web editor payload (#2470) 2020-07-21 11:25:50 +01:00
Luck ef556f7cf7 Pre-generate event classes (#2463) 2020-07-16 21:00:17 +01:00
Luck 47ca6b162b Catch throwable instead of exception when handling hikari errors (#2444) 2020-07-16 20:26:54 +01:00
Luck f72a5cb6b5 Ensure group/track names are always lowercase'd when created via the API (#2440) 2020-07-16 20:19:33 +01:00
Luck cd0fd80b33 Impose 15 second timeout on all http calls (#2436) 2020-07-16 20:15:53 +01:00
Sam Goodger 0fd7f643a7 Add option to use bytebin for exports/imports (#2432) 2020-07-04 20:41:34 +01:00
Mark 6c7c1b67b8 Try to reopen Redis pub-sub connection on failure (#2430) 2020-07-04 20:08:58 +01:00
Luck 4514a17eaf Remove MRUCache
Now we're using ConcurrentHashMap, it's usefulness in terms of performance is debatable
2020-07-03 18:23:34 +01:00
Luck adbd2fc81f Use ConcurrentHashMap instead of Caffeine in CachedDataManager
The behaviour of Caffeine cache invalidation calls is "undefined for an entry that is being loaded (or reloaded)" - this causes a nasty race condition in CachedDataManager, because we rely on the call to #invalidate to completely clear the cache and prevent old/outdated data from sticking around & being used for permission queries.

This is an unfortunate characteristic of Caffeine, because other than that, it is perfect for our use-case.
2020-07-03 17:57:53 +01:00
Luck 8b506b6a95 Don't re-use PermissionCache instances 2020-07-03 15:18:58 +01:00
Luck 426c8565fd Small optimization to processExportedPermissions 2020-07-03 14:57:46 +01:00
Luck bd6d3ab7c0 Optimize PermissionHolder#hasNode 2020-07-02 12:28:30 +01:00
Luck d12d13d6ea Fix NodeMap#size method and rename some things 2020-07-02 12:16:13 +01:00
Luck cc08390abe Remove NodeMap immutable cache 2020-07-02 12:06:37 +01:00
Luck b460e282b1 Add ConstraintNodeMatcher toString method for use in commands 2020-07-02 10:44:52 +01:00
Tobi 005d19f2a3 Fix JavaDoc typo (#2421) 2020-07-02 10:42:37 +01:00
Jonas Baars 8817e17d2c Fix typo in config file 2020-06-29 15:17:42 +01:00
Luck 0f23b7a460 Avoid calling #stream on the result of Bukkit getOnlinePlayers 2020-06-25 10:24:33 +01:00
Luck 21ae1dbac0 Fix potential race condition in MRUCache 2020-06-21 22:07:53 +01:00
Luck 6cb0f5e810 Use a non-async cache in CachedDataManager 2020-06-21 21:23:57 +01:00
Luck 2ac7d3dfe6 Refactor command argument parsing 2020-06-21 19:58:17 +01:00
Luck bfaa543ecd Fix NPE in ConfigurateConfigAdapter#getStringList (#2399) 2020-06-21 10:18:27 +01:00
Luck f893b1c632 Add optional time parameters to permission/parent removetemp commands (#1384) 2020-06-20 22:01:27 +01:00
Luck cb5a28f043 Use the only/default track if one is not specifed in the promote/demote command (#1581) 2020-06-20 21:03:01 +01:00
Luck bd9718c5af Add log notification filters (#1980) 2020-06-20 20:46:54 +01:00
Luck 97a0dbebcb Add DataTypeFilter#NONE 2020-06-13 21:07:59 +01:00
Luck df324de9ec Add DataTypeFilter 2020-06-12 23:14:33 +01:00
Luck 6fa690a49d Update API pom description 2020-06-12 20:36:41 +01:00
Luck 2bb1aa7511 Change behaviour of resolveInheritanceTree when 'resolve inheritance' flag is false 2020-06-12 20:34:57 +01:00
Luck 0bef0902c0 Update some dependencies 2020-06-10 20:41:39 +01:00
Luck f10f60e27f Define dependency on Vault during enable (#1959) 2020-06-10 20:24:11 +01:00
Luck 91337b49ea Just use the section char instead of some other random one
Idk wtf I was thinking
2020-06-08 23:47:10 +01:00
Luck f5e41f7e7e Cleanup chat meta commands 2020-06-08 23:44:25 +01:00
Luck fe0e82f285 Verbose command shouldn't be able to run as console 2020-06-04 14:46:11 +01:00
Luck 8996cf02af Add player argument to'/lp verbose command' to execute the test command as another player 2020-06-04 01:32:56 +01:00
Luck e37c77a1be Add '/lp verbose command <command>' for easy testing of command permissions 2020-06-03 22:32:24 +01:00
Luck 11998b2dd6 Some misc cleanup 2020-06-03 21:20:41 +01:00
Luck 8754123535 Fix parsing second optional argument in editor command 2020-05-30 02:39:37 +01:00
Luck 91d75424f4 Include users which inherit the group when running /lp group <group> editor 2020-05-30 02:13:18 +01:00
Luck c5c99abd16 Improve argument based perm checks for the editor 2020-05-30 01:54:18 +01:00
Luck 6c88f7629e Add optional filter argument to editor command 2020-05-30 01:47:31 +01:00
Luck 98c71739f8 Fix user ordering in the editor 2020-05-30 01:37:24 +01:00
Luck 25551641fc Add option to merge into instead of overriding existing data in imports (#2324) 2020-05-28 21:36:06 +01:00
Luck 00b701f129 Improve undo command generation (#2318) 2020-05-28 20:53:07 +01:00
Luck ab082aa311 Group names should always be lowercase in inheritance nodes 2020-05-28 00:49:46 +01:00
Joshua Schmitt dc32c84bb3 Increase world column max length (#2334) 2020-05-27 14:02:53 +01:00
Luck 0bb4161397 Use nicer toString for Durations in log messages 2020-05-27 10:46:24 +01:00
Luck 4a049ca732 Use contains instead of startsWith for context tab completions 2020-05-27 10:43:30 +01:00
Luck d5ac45d230 Formalize more of the contexts spec 2020-05-25 22:46:26 +01:00
Luck 6b3e4bdc71 Add ContextUpdateEvent and related functionality 2020-05-25 21:49:49 +01:00
Luck 5c0d82f306 Refactor config handler classes 2020-05-25 10:42:32 +01:00
Luck 5a6176def5 Enforce removal of server=global and world=global contexts at a higher level 2020-05-21 17:30:13 +01:00
Luck 13ebc87b29 Fix compatibility with modified SimplePluginManager in Mohist fork
https://github.com/Mohist-Community/Mohist/blob/eab0f722a48759152f5d562b39689317f647ce99/src/main/java/org/bukkit/plugin/SimplePluginManager.java#L674
2020-05-20 21:37:07 +01:00
Luck 1ca1bcf1d1 Make getUniqueUsers more resilient to invalid uuids 2020-05-20 21:23:40 +01:00
Luck ea5a05d770 Fix incorrectly reading node value from the value field when reading from the flatfile meta section (#2312) 2020-05-20 18:22:10 +01:00
Luck ed85ab1bfd Change ContextSet 'satisfy' behaviour (#2300) 2020-05-20 14:51:03 +01:00
Luck c09e4a1aa0 Update web app urls 2020-05-20 14:45:20 +01:00
Luck ee36fa9d81 Fix prefix being displayed twice in extensions info section 2020-05-19 17:47:07 +01:00
Luck bfa5fc43cd Rename getWithPermission method internally 2020-05-19 17:45:16 +01:00
Luck 5d6389249a Fix bug with searching for group permissions via the api 2020-05-19 13:32:06 +01:00
Luck 637cb0d836 Fix group info command 2020-05-19 09:55:00 +01:00
Luck bfebe6a5d0 Cleanup MetaStack, cache the computed prefix/suffix when meta accumulator completes 2020-05-19 00:36:39 +01:00
Luck 4cbbbd390f Don't schedule command list updates if the server is stopping (#2301) 2020-05-16 18:19:05 +01:00
Luck c120fa6a4d Fix startup on Nukkit (#2290) 2020-05-16 11:52:51 +01:00
Luck 918ea7503a Fix bug with duplicated users in editor (lucko/LuckPermsWeb#235) 2020-05-13 11:20:11 +01:00
Luck dbe07827bd Update readme 2020-05-12 23:12:53 +01:00
Luck 98eb756951 Add contextual data section to group info command too 2020-05-12 18:34:07 +01:00
Luck 86eeeb5b94 Slightly change plugin prefix 2020-05-12 17:54:06 +01:00
Luck a3e6628e11 Improve user info command 2020-05-12 16:54:02 +01:00
Luck 84bc4217de Fix command arg rewriting 2020-05-12 16:35:43 +01:00
Luck b88819b9bb Cleanup Bukkit/Nukkit dummy classes, some aren't necessary anymore 2020-05-11 21:58:24 +01:00
Luck c7a0e59919 Catch RejectedExecutionException in BufferedRequest (#2289) 2020-05-11 21:15:42 +01:00
Luck 9984d4be42 Optimize ActionLog and Paginated util 2020-05-11 21:00:42 +01:00
Luck a1c91b9007 Improve sync task performance 2020-05-11 13:46:02 +01:00
Luck 6a256d59f5 Fix null check in selectAllGroupPermissions 2020-05-11 13:12:30 +01:00
Luck fca2f1e6de Optimize loading lots of groups from SQL storage types (#2287) 2020-05-11 12:54:42 +01:00
Luck ec7994a561 Print more useful debug message when a LinkageError is thrown due to bad SLF4J classloading
This is a longstanding issue, mostly caused by plugins with bad Maven shade configurations.

https://github.com/lucko/LuckPerms/issues?q=slf4j
2020-05-11 01:41:01 +01:00
Luck 2839c36ea2 Fix NPE in Nukkit disconnect handling (#2269) 2020-05-10 22:24:47 +01:00
Luck 9f22bf2fbf Update the client commands list when permissions are changed (#2262) 2020-05-10 22:18:01 +01:00
Luck 5f0df1b167 Increase editor users limit to 1000 & sort users according to their max inherited group weight 2020-05-10 16:31:35 +01:00
Luck 3bf2fab2e9 Release API 5.1 2020-05-10 14:02:57 +01:00
Luck 73d76d860e Move PlayerAdapter to platform package 2020-05-10 13:51:30 +01:00
Luck bcfcb64931 Add modifyUser and modifyGroup convenience API methods 2020-05-10 12:40:20 +01:00
Luck a19cb71394 Add PermissionHolder#getQueryOptions API method 2020-05-10 12:22:40 +01:00
Luck 52731fe68e Add getInheritedGroups and getNodes filtered by type methods to the API (#1926) 2020-05-10 01:19:43 +01:00
Luck 7845d89f10 Add some convenience API methods (#1926) 2020-05-09 23:41:28 +01:00
Luck 3d358e57ff Fix group listmembers command behaviour with the default group (#2088) 2020-05-09 19:16:17 +01:00
Luck bd9ae9dbd5 Cleanup command manager aliases handling 2020-05-09 19:04:50 +01:00
Luck f281927578 Re-add group/g --> parent alias (#2054) 2020-05-09 18:40:15 +01:00
Luck 665c53093a Tweak HikariCP config to hopefully improve recovery from database downtime (#2223, #2084) 2020-05-09 18:19:49 +01:00
Luck 10fbc24f43 Add option for Sponge style wildcards on other platforms (#1625) 2020-05-09 17:48:29 +01:00
Luck 6631e9144e Put Nullable annotations in the right place 2020-05-09 17:32:52 +01:00
Luck 7ae532f082 Add UserManager/GroupManager searchAll method 2020-05-09 17:27:17 +01:00
Luck 1af55e3720 Rename InheritanceHandler --> InheritanceGraphFactory 2020-05-07 03:48:09 +01:00
Luck fe32aa2d33 Download dependencies in parallel 2020-05-06 02:50:19 +01:00
Luck f6460c2802 Fix Year 2038 problem for future installs (#2126) 2020-05-06 00:13:19 +01:00
Luck 045c10048d RedisMessenger support for SSL (#2263) 2020-05-05 17:38:24 +01:00
Luck 308356de80 Refactor group name parsing in commands 2020-05-05 17:24:44 +01:00
Luck 8771d4c5e5 Detect incompatible BungeeCord versions 2020-05-03 00:23:58 +01:00
Luck cd25681cd3 Remove special message for error 403
it was a firewall config error at my end after all lol woops
2020-04-28 21:40:52 +01:00
Luck ad6a11819d Ignore null entries in Permission#children map on Bukkit/Nukkit (#2235) 2020-04-28 13:54:23 +01:00
Luck b17da055af Increase UserHousekeeper cleanup interval & print debug message when unloading 2020-04-28 13:47:13 +01:00
Luck 2fd896508b Attempt to fix issue with user instances being unloaded for online players on Bungee (#2238, #2225, #2107 & more) 2020-04-28 13:46:17 +01:00
Luck 99348fce53 Reply with more descriptive message when http requests fail 2020-04-28 13:29:09 +01:00
Luck eb0d758efc Fix Bukkit command map access (#2229) 2020-04-28 12:57:40 +01:00
Luck c03aca35d6 Add checkTime property to verbose data (#2226) 2020-04-26 10:50:10 +01:00
Tobi f484e87828 Add other options to meta formatting config comment (#2222, #2090) 2020-04-24 19:57:50 +01:00
Luck 07b2df7c7e Cleanup configuration files slightly 2020-04-23 13:05:39 +01:00
Luck cab15d39c3 Make the injected LP permSubs replacement value maps thread safe 2020-04-21 20:06:52 +01:00
Luck 36335c10a4 Cleanup imports 2020-04-17 17:49:32 +01:00
Luck 642d14d003 Allow leading '/' in LP commands executed from the console
https://github.com/Turbotailz/LuckPermsWeb/issues/34
2020-04-17 17:46:48 +01:00
Luck 175808e251 Attempt to fix some PowerfulPerms migration issues (#2168) 2020-04-16 15:13:41 +01:00
Luck 14d952a212 Code style 2020-04-16 15:09:01 +01:00
Luck 37e09caac5 Slightly optimize SimpleMetaValueSelector by only invoking the selection strategy if there is a selection to be made 2020-04-16 14:50:44 +01:00
Luck 4d7a6cb658 Add meta-value-selection config setting (#2167) 2020-04-16 14:44:38 +01:00
Luck cc80fe5105 Cleanup ConfigKeys class a bit 2020-04-09 12:00:32 +01:00
Luck b79f6b0fdd Build against SpongeAPI 7.2 2020-04-09 11:47:10 +01:00
Luck 83e9ac04b2 Optimize VerboseHandler by delaying construction of stack trace array until later 2020-04-07 17:56:56 +01:00
Luck 8cecfe5b7c Avoid calls to the Bukkit singleton 2020-04-07 17:50:08 +01:00
Luck 54aed80db8 Fix applying track reordering from the webeditor (#2144) 2020-04-06 11:55:49 +01:00
Luck 0d89fc9d28 Revert "Trial using the default Java hostname verifier instead of okhttp's (#2134)"
This reverts commit cee34d0c31.
2020-04-05 17:49:20 +01:00
Luck cee34d0c31 Trial using the default Java hostname verifier instead of okhttp's (#2134) 2020-04-05 17:41:32 +01:00
Luck d2e60eb5ac Attempt to fix issue converting MongoDB document _id to UUID (#2110) 2020-04-05 16:42:09 +01:00
Luck c71742a996 Use slightly less spammy logging when user data isn't loaded in Bungee permission check handling 2020-04-05 16:25:33 +01:00
Luck fe139f8420 Replace 'none' option with 'auto' in messaging service config description 2020-04-05 16:04:38 +01:00
caramat bb015d0c88 Fix the link to Storage types wiki page in config files (#2135) 2020-04-05 16:03:19 +01:00
Luck 94809c2a66 Defer legacy component handling on 1.7 servers to Kyori lib 2020-04-05 15:48:07 +01:00
Luck 141d3e26ea Update dependencies 2020-04-04 22:47:43 +01:00
Luck efb958ba44 Move Bungee connection event registerIntent call to after possible method return 2020-04-04 12:04:22 +01:00
Luck 9f0fe9e0cb Use a dynamically generated class instead of a reflection Proxy in NonClosableConnection 2020-03-31 14:29:09 +01:00
Luck 2028d65579 Rename some classes 2020-03-31 13:38:28 +01:00
Luck c98c60b120 Fix IllegalStateException when constructing a MetaCache for CalculatedSubjects (#2123) 2020-03-30 22:57:08 +01:00
Luck 6805574553 Use a MethodHandle to call event class constructors 2020-03-30 19:46:30 +01:00
Luck c3128dec0d Generate actual classes for LP events at runtime instead of using proxies + InvocationHandlers 2020-03-30 19:19:27 +01:00
Luck b65639cd76 Ignore empty string world names in Bukkit WorldCalculator (#2119) 2020-03-30 14:32:27 +01:00
Luck 42e9f43443 Store result of primary group calculation in CachedMetaData 2020-03-30 14:13:17 +01:00
Luck c40ad63291 Slightly refactor AbstractConfigurateStorage#appendNode 2020-03-28 10:22:04 +00:00
Luck 0964762217 Avoid call to PermissionUser.initialize in PEX migration (#2102) 2020-03-26 23:09:09 +00:00
Luck 3553a0f9fe Don't attempt to migrate empty users in PEX migration (#2102) 2020-03-26 22:32:53 +00:00
Luck 24304a61a7 Work around issue accessing PEX async in migration (#2102) 2020-03-26 17:18:00 +00:00
Luck 497846e5a6 Run some auto code inspections 2020-03-26 17:01:55 +00:00
Luck 0729be0f0a Print IOException when uploading web editor data fails (#2087) 2020-03-24 10:44:31 +00:00
Luck 2adea2d3dc Improve reliability of java version check in ReflectionClassLoader (#2093) 2020-03-24 10:35:13 +00:00
Luck 30d7768299 Fix parsing contexts from commands with server/world = global (#2093) 2020-03-24 10:03:34 +00:00
Luck 3a7fadb843 Perform uninject tasks synchronously on Bukkit player quit (#2080) 2020-03-24 09:43:32 +00:00
Luck 779bbb9f88 Fix typo
https://github.com/lucko/LuckPerms/commit/3973aa1000f40aca181225961aea9a65852169a9#r37923265
2020-03-19 16:24:14 +00:00
Luck a721cc0b7c Remove unused DefaultQueryOptions class 2020-03-19 12:57:52 +00:00
Luck 3973aa1000 Add notice to inform users to ignore 'illegal reflective access' warning messages (#952) 2020-03-19 12:14:17 +00:00
Luck ac9fe74785 Catch subclasses of Throwable when trying to create a FileWatcher (#2066) 2020-03-19 11:30:19 +00:00
Luck 46c9e3b713 Clarify method name in QuotedStringTokenizer 2020-03-19 11:06:41 +00:00
Luck 05b82cdbab Slightly optimize Permissible#getEffectivePermissions 2020-03-19 11:04:52 +00:00
Luck 3086724e92 Don't print InterruptedExceptions in AbstractFileWatcher (#2044) 2020-03-14 21:26:13 +00:00
Luck fbac03e372 Remove MinimalApiProvider & refactor usages away from static access 2020-03-13 18:13:12 +00:00
Luck 2518a7c8e5 Fix NPE in QueryOptionsImpl (#2051, #2052) 2020-03-11 11:38:38 +00:00
Luck bbec0932fd Fix typo 2020-03-10 23:55:47 +00:00
Luck bca766c20d Ensure immutability of QueryOptions options map 2020-03-04 12:45:13 +00:00
Luck bb3afa21a4 Always store node metadata keys in a HashMap, not IdentityHashMap, and ensure immutability when built 2020-03-04 12:44:47 +00:00
Luck 85451a9a20 Remove legacy import functionality
Users wishing to import data from the old export formats will need to downgrade LuckPerms to a version before this commit, import their data, and then upgrade again.
2020-03-03 11:20:32 +00:00
Luck d475ace76c Export users & tracks in a consistent order 2020-03-03 11:03:30 +00:00
Luck 9f3395f256 Fix StringIndexOutOfBoundsException with empty string in QuotedStringTokenizer (#2041) 2020-03-02 22:52:08 +00:00
Luck c6bda0875c Change argument tokenizer to support unicode double quote characters (#1999) 2020-03-02 15:28:32 +00:00
Luck 4f63a00bad Update FUNDING.yml 2020-03-01 10:23:40 +00:00
Luck 0a3141841c Rename variable for silent flag 2020-02-27 13:20:14 +00:00
Luck 6fea194ef5 Don't attempt to refresh auto op if server is stopping (#1989) 2020-02-27 13:19:53 +00:00
Luck e66ab1be0d Properly close file watcher on disable
https://github.com/lucko/LuckPerms/commit/5ce8217cd53a6540e0e9bd74a996538b55555059#r37466536
2020-02-24 23:45:30 +00:00
Luck 5ce8217cd5 Refactor file watcher 2020-02-24 18:26:52 +00:00
povsisterandLuck 2ed45c92a6 Enhance default config (#2005, #2006)
Co-authored-by: Luck <git@lucko.me>
2020-02-21 20:08:58 +00:00
Luck 9f7d431d34 Accept empty contexts.json file as valid 2020-02-11 15:35:16 +00:00
Luck d937c9ce06 Update commodore to 1.7 2020-02-10 00:45:47 +00:00
Luck 6a96d40da4 Tiny cleanup to #expiry method in NodeBuilder 2020-02-10 00:27:29 +00:00
Luck 2eabe845d7 Update commodore to 1.6 (#1939) 2020-02-09 15:44:52 +00:00
Luck 4cfee76202 Change layout of library storage 2020-02-09 15:24:04 +00:00
Luck 446fa48d9a Optimize LuckPermsPermissionMap putAll implementation 2020-02-05 20:59:10 +00:00
Luck 9546987970 Fix AbstractUserManager to only load users once in #loadAllUsers 2020-02-03 00:44:08 +00:00
Luck 7a624fb449 Add additional call to invalidate cached data during a sync task 2020-02-03 00:30:18 +00:00
Luck 26de6cf86f Refactor CraftBukkitImplementation utility 2020-01-25 16:43:53 +00:00
Luck 5fe7302965 Simplify vault hasPerm check for users 2020-01-25 16:43:31 +00:00
Luck a779b31ca5 Refactor SQL storage implementation 2020-01-25 16:02:37 +00:00
Luck 8fa629a243 Fix/catch IllegalArgumentException when estimating potential world contexts (#1951) 2020-01-25 12:59:41 +00:00
Luck 0864cf756c Cancel delayed/repeating tasks at the start of the disable process (#1935) 2020-01-25 12:38:24 +00:00
Luck e9d72df890 Check for an empty buffer in async tab complete handler (#1949) 2020-01-25 12:25:00 +00:00
Luck c39a44c3ac Add reminder to remove other plugin jar after migration (#1923) 2020-01-25 12:17:23 +00:00
Luck 7e8402b542 Fix compile errors 2020-01-17 10:05:19 +00:00
Luck a050a2604f Fix error extracting command label in AsyncTabComplete handler 2020-01-16 17:55:03 +00:00
Luck abad672bef Implement support for Vanilla command target selectors (e.g. @p, @r) for Bukkit & Sponge 2020-01-16 17:49:28 +00:00
Luck fe3d715f09 Implement Paper's AsyncTabCompleteEvent 2020-01-16 17:20:54 +00:00
Luck e151fbe298 Build against 1.15 APIs 2020-01-16 16:05:19 +00:00
Luck 2ccb475aa0 Cleanup command abstraction classes 2020-01-15 23:54:43 +00:00
Luck 4c83d439af Refactor Vault provider world rewriting 2020-01-14 15:18:45 +00:00
Luck 352f3bd263 Ensure response body is closed when a http request is not successful (#1912) 2020-01-10 12:50:40 +00:00
Luck b73b73e221 Add rewrite rule in legacy importer for for switchprimarygroup command (#1922) 2020-01-09 23:18:05 +00:00
Luck c454d17826 Refactor Vault Chat #setMeta impl 2020-01-07 15:07:21 +00:00
Luck 098f8d8231 Fix NPE when trying to set (chat)meta with a null would through Vault (#1920) 2020-01-07 14:24:04 +00:00
Luck c1e3660551 Update commodore to 1.5 2020-01-04 23:26:53 +00:00
Luck 3ceb53aa4f Improve n/bukkit SenderFactory getPermissionValue impls 2020-01-04 22:00:53 +00:00
Luck a33f326cfc Fix duration parser handling of weeks, months & years (#1905) 2019-12-31 18:04:03 +00:00
Luck 83883aa7e6 Replace internal brigadier command structure with Commodore's file format 2019-12-29 22:01:38 +00:00
Luck 3fae4e5e9e Refactor CacheMetadata 2019-12-29 18:34:55 +00:00
Luck 4e9b11f30a Only apply DefaultsProcessor for users 2019-12-29 18:01:13 +00:00
Luck 46dc4e5104 Cleanup verbose logging for groups 2019-12-29 17:54:11 +00:00
Luck 9c8bdb1166 Fix support for setting vault npc op status 2019-12-29 17:53:39 +00:00
Luck 242993763c Remove vault-debug option
This is now covered pretty much entirely by verbose data.
2019-12-29 17:37:08 +00:00
Luck b84518e1f1 Lots of misc cleanup 2019-12-29 16:26:05 +00:00
Luck b2593d409e Invalidate all user/group caches after node changes are made to groups via the API 2019-12-29 01:17:30 +00:00
Luck d3b3a8af38 Some small cleanup changes 2019-12-29 00:55:41 +00:00
Luck ec2cb5007c Move op handling into a permission processor 2019-12-28 23:41:02 +00:00
Luck ca33cee79d Fix DateTimeFormatter usage in VerboseListener 2019-12-27 23:30:33 +00:00
Luck 2d456bd346 Iterators class refactoring 2019-12-27 23:17:18 +00:00
Luck 10680668db Fix NPEs caused by RepeatingTask initialisation race condition 2019-12-26 20:25:14 +00:00
Luck 0e64b61be4 Improve DurationParser regex 2019-12-25 00:04:39 +00:00
Luck 96c53091d0 Make use of Instant / Duration classes where possible for time handling 2019-12-24 23:10:26 +00:00
Luck 6e64f80c81 Cleanup temporary handing in PermissionHolder#setNode 2019-12-24 18:11:58 +00:00
Luck 8906e4c4fc Fix PermissionHolder#setNode with accumulate temporary modifier (#1839) 2019-12-24 17:54:35 +00:00
Luck cc5d7ea873 Don't randomly add 1 second to parsed dates 2019-12-24 17:49:27 +00:00
Luck 6237d58ad6 Fix license header checks violation 2019-12-24 17:16:40 +00:00
Luck fae5ebe226 Prefer "storage provider" to "dao" in logging messages for consistency with rest of code base 2019-12-24 14:08:23 +00:00
Luck 9a92920120 Downgrade h2 version to 1.4.199
Fixes https://gist.github.com/lucko/2ba1a9eb52fd76da19e54888c2595381

See: h2database/h2database#2078
2019-12-24 14:00:48 +00:00
Luck dc5c6b0138 Set character set to utf8 for messenger table (#1887) 2019-12-24 13:46:19 +00:00
Luck 88c432496b Reimplement "legacy" shorthand syntax
i.e. using ( ) for groups, and | for list separation
2019-12-24 13:24:20 +00:00
Luck 63b890d522 Rename EventFactory --> EventDispatcher 2019-12-24 13:05:48 +00:00
Luck cd5c8d7cdf NodeEqualityPredicate refactoring 2019-12-21 19:58:37 -05:00
Luck d80017ba7b Refactor /lp import command, better handling when no file extension is provided 2019-12-21 17:46:37 -05:00
Luck 5eb8a262be Set giveDefault to true when running the 'clear' command 2019-12-14 17:22:48 +00:00
Luck 7474842b45 Cleanup the way contexts for transient permissions are determined 2019-12-14 17:06:26 +00:00
Luck 9093385de3 Fix NPE in Permissible#recalculatePermissions 2019-12-13 14:30:37 +00:00
Luck bd349ac4a3 Use standard (not Identity) HashMaps for OptionKeys and NodeMetadataKeys 2019-12-13 14:02:31 +00:00
Luck 37367fe91f Invalidate QueryOptions cache when op status changes 2019-12-13 13:56:34 +00:00
Luck dce792083b Fix some issues with null world contexts in migration scripts 2019-12-13 11:51:42 +00:00
Luck 06f7900318 Use thee default set of flags by default in QueryOptions.Builder, etc 2019-12-13 11:28:46 +00:00
electron d8ac199858 Additional SQL query escaping fixes (#1847, #1854) 2019-12-05 21:46:00 +00:00
Luck 9cf21e2927 Include loaded extensions in the info command (#1849) 2019-12-05 21:44:45 +00:00
electron 86a15b9a66 Escape table name with backticks (`) in sql messenger (#1847, #1848) 2019-12-04 20:50:56 +00:00
electron 31b57df567 Fix json action serialization (#1843, #1844) 2019-12-04 11:18:37 +00:00
Luck a07f876c85 Re-implement parsing first/second context values as server/world respectively (#1837) 2019-12-02 21:23:58 +00:00
Luck 2eb0c1e67e Use getPlainDisplayName to get name for hover messages (#1663) 2019-12-02 21:03:45 +00:00
Luck 6325e25630 Limit the number of scheduler work threads 2019-12-02 15:10:18 +00:00
Luck 6fdd349654 Fix (un)setting nodes through Vault with null worlds (#1826) 2019-11-29 12:16:24 +00:00
Luck 01e17adf82 Cleanup imports 2019-11-25 22:52:37 +00:00
Luck acdd5f3faa Don't attempt to load permissions data for login events cancelled at the lowest priority
Makes LuckPerms more compatible with "anti-bot" plugins.
2019-11-25 22:51:11 +00:00
Luck 4d3c692402 Fix concurrency bug in CachedDataManager MRU cache
This resolves a longstanding issue with TownyPerms (#1330)
2019-11-25 20:31:14 +00:00
Luck 787f691f44 Improve shorthand parser 2019-11-25 00:34:23 +00:00
OmegaWeapon_ 500385cd3e Add temporary modifier to the argument list for permission/parent settemp (#1817) 2019-11-24 16:27:36 +00:00
Luck 6df64a29dc Don't set username to null when a user is requested by uuid alone 2019-11-24 00:40:38 +00:00
Luck 233324f6c9 NodeMap optimizations 2019-11-24 00:40:38 +00:00
Andre_601 da253c47da Update badges for new API coordinates (#1816) 2019-11-24 00:38:22 +00:00
502 changed files with 15111 additions and 9824 deletions

No files matched your search

+1
View File
@@ -1 +1,2 @@
github: lucko
patreon: luckdev
+40
View File
@@ -0,0 +1,40 @@
---
name: 🐛 ​ Bug report
about: Report an issue or problem with the way LuckPerms is functioning.
title: ''
labels: 'type: issue'
assignees: ''
---
<!-- Before going any further, please check to see if your problem is fixed by updating to a newer version. The latest development builds can be obtained from https://luckperms.net/download -->
### Description
<!-- Please provide a short description of the issue in the space below. -->
### Reproduction steps
<!-- Please provide a clear list of steps we should follow in order to reproduce your issue in the space below. -->
1. Open the config.yml file and set example to true.
2. Restart the server
3. Run /lp user example permission set x from the console
4. See error
### Expected behaviour
<!-- Please provide a description of what you expected to happen. -->
### Environment details
<!-- Please provide details of your server environment in the space below. -->
* Server type/version: `ExampleSpigot` running version `1.12.2` build `???`
* LuckPerms version: `v???`
<!-- If you think it would be helpful, please also include a "pastebin" of any relevant config files or log output. You should use https://gist.github.com/ to upload, then include the link below. -->
### Any other relevant details
<!-- Please include any other relevant details in the space below. -->
+5
View File
@@ -0,0 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: 💬 ​ Ask a question
url: https://discord.gg/luckperms
about: We use Discord to provide support for the project. If you have a question, need help, or aren't sure if you've found a bug or not, please come and talk to us there!
+16
View File
@@ -0,0 +1,16 @@
---
name: 💡 ​ Suggestion
about: Suggest an idea for an improvement or change to LuckPerms.
title: ''
labels: 'type: suggestion'
assignees: ''
---
### Description
<!-- Please provide a short description of the suggestion in the space below. -->
### Proposed behaviour
<!-- Please provide a short explanation of how the feature should work / be changed, and how this will effect the project. -->
+13 -9
View File
@@ -1,21 +1,25 @@
![alt text](https://i.imgur.com/ToguFkQ.png "Banner")
# LuckPerms
[![Build Status](https://ci.lucko.me/job/LuckPerms/badge/icon)](https://ci.lucko.me/job/LuckPerms/)
[![Javadocs](https://javadoc.io/badge/me.lucko.luckperms/luckperms-api.svg)](https://javadoc.io/doc/me.lucko.luckperms/luckperms-api)
[![Maven Central](https://img.shields.io/maven-metadata/v/https/repo1.maven.org/maven2/me/lucko/luckperms/luckperms-api/maven-metadata.xml.svg?label=maven%20central&colorB=brightgreen)](https://search.maven.org/artifact/me.lucko.luckperms/luckperms-api)
[![javadoc](https://javadoc.io/badge2/net.luckperms/api/javadoc.svg)](https://javadoc.io/doc/net.luckperms/api)
[![Maven Central](https://img.shields.io/maven-metadata/v/https/repo1.maven.org/maven2/net/luckperms/api/maven-metadata.xml.svg?label=maven%20central&colorB=brightgreen)](https://search.maven.org/artifact/net.luckperms/api)
[![Discord](https://img.shields.io/discord/241667244927483904.svg?logo=discord&label=)](https://discord.gg/luckperms)
LuckPerms is an advanced permissions implementation aiming to be a fast, reliable and flexible alternative to existing permission plugins. The project's main goals are centered around high performance and a wide feature set, filling the gaps of functionality and building upon existing features found in other plugins.
LuckPerms is a permissions plugin for Minecraft servers. It allows server admins to control what features players can use by creating groups and assigning permissions.
LuckPerms also includes an extensive API for developers, and support for a variety of Minecraft server software & data storage options.
It is:
Still not convinced? More information can be found on the wiki, under: [Why LuckPerms?](https://github.com/lucko/LuckPerms/wiki/Why-LuckPerms)
* **fast** - written with performance and scalability in mind.
* **reliable** - trusted by thousands of server admins, and the largest of server networks.
* **easy to use** - setup permissions using commands, directly in config files, or using the web editor.
* **flexible** - supports a variety of data storage options, and works on lots of different server types.
* **extensive** - a plethora of customization options and settings which can be changed to suit your server.
* **free** - available for download and usage at no cost, and permissively licensed so it can remain free forever.
For more information, see the wiki article on [Why LuckPerms?](https://github.com/lucko/LuckPerms/wiki/Why-LuckPerms)
## Useful Links
The latest **downloads** can be found on the [project's homepage](https://luckperms.net/).
More information about the project & how to use the plugin can be found in the [wiki](https://github.com/lucko/LuckPerms/wiki).
The latest downloads & other useful links can be found on the project homepage at [luckperms.net](https://luckperms.net/).
## Building
LuckPerms uses Gradle to handle dependencies & building.
+4 -3
View File
@@ -1,5 +1,5 @@
group = 'net.luckperms'
project.version = '5.0'
project.version = '5.2'
dependencies {
compileOnly 'org.checkerframework:checker-qual:2.5.5'
@@ -56,7 +56,7 @@ if (project.hasProperty('sonatypeUsername') && project.hasProperty('sonatypePass
pom {
project {
name 'LuckPerms API'
description 'An advanced permissions plugin for Bukkit/Spigot, BungeeCord, Sponge, Nukkit and Velocity.'
description 'A permissions plugin for Minecraft servers.'
url 'https://luckperms.net'
licenses {
@@ -68,9 +68,10 @@ if (project.hasProperty('sonatypeUsername') && project.hasProperty('sonatypePass
developers {
developer {
id 'lucko'
name 'Luck'
url 'https://lucko.me'
email 'git@lucko.me'
url 'https://github.com/lucko'
}
}
@@ -39,8 +39,11 @@ import net.luckperms.api.model.group.GroupManager;
import net.luckperms.api.model.user.User;
import net.luckperms.api.model.user.UserManager;
import net.luckperms.api.node.NodeBuilderRegistry;
import net.luckperms.api.node.matcher.NodeMatcherFactory;
import net.luckperms.api.platform.Platform;
import net.luckperms.api.platform.PlayerAdapter;
import net.luckperms.api.platform.PluginMetadata;
import net.luckperms.api.query.QueryOptionsRegistry;
import net.luckperms.api.track.Track;
import net.luckperms.api.track.TrackManager;
@@ -114,6 +117,32 @@ public interface LuckPerms {
*/
@NonNull TrackManager getTrackManager();
/**
* Gets the {@link PlayerAdapter} instance, a utility class for adapting platform Player
* instances to {@link User}s.
*
* <p>The {@code playerClass} parameter must be equal to the class or interface used by the
* server platform to represent players.</p>
*
* <p>Specifically:</p>
*
* <p></p>
* <ul>
* <li>{@code org.bukkit.entity.Player}</li>
* <li>{@code net.md_5.bungee.api.connection.ProxiedPlayer}</li>
* <li>{@code org.spongepowered.api/entity.living.player.Player}</li>
* <li>{@code cn.nukkit.Player}</li>
* <li>{@code com.velocitypowered.api.proxy.Player}</li>
* </ul>
*
* @param playerClass the class used by the platform to represent players
* @param <T> the player class type
* @return the player adapter
* @throws IllegalArgumentException if the player class is not correct
* @since 5.1
*/
<T> @NonNull PlayerAdapter<T> getPlayerAdapter(@NonNull Class<T> playerClass);
/**
* Gets the {@link Platform}, which represents the server platform the
* plugin is running on.
@@ -172,6 +201,14 @@ public interface LuckPerms {
*/
@NonNull NodeBuilderRegistry getNodeBuilderRegistry();
/**
* Gets the {@link QueryOptionsRegistry}.
*
* @return the query options registry
* @since 5.1
*/
@NonNull QueryOptionsRegistry getQueryOptionsRegistry();
/**
* Gets the {@link MetaStackFactory}.
*
@@ -183,6 +220,14 @@ public interface LuckPerms {
*/
@NonNull MetaStackFactory getMetaStackFactory();
/**
* Gets the {@link NodeMatcherFactory}.
*
* @return the node matcher factory
* @since 5.1
*/
@NonNull NodeMatcherFactory getNodeMatcherFactory();
/**
* Schedules the execution of an update task, and returns an encapsulation
* of the task as a {@link CompletableFuture}.
@@ -25,7 +25,10 @@
package net.luckperms.api.cacheddata;
import net.luckperms.api.context.ContextManager;
import net.luckperms.api.model.PermissionHolder;
import net.luckperms.api.model.group.Group;
import net.luckperms.api.model.user.User;
import net.luckperms.api.query.QueryOptions;
import org.checkerframework.checker.nullness.qual.NonNull;
@@ -56,23 +59,58 @@ public interface CachedDataManager {
@NonNull Container<CachedMetaData> metaData();
/**
* Gets PermissionData from the cache, given a specified context.
* Gets PermissionData from the cache, using the given query options.
*
* @param queryOptions the query options
* @return a permission data instance
* @throws NullPointerException if contexts is null
*/
@NonNull CachedPermissionData getPermissionData(@NonNull QueryOptions queryOptions);
/**
* Gets MetaData from the cache, given a specified context.
* Gets MetaData from the cache, using the given query options.
*
* @param queryOptions the query options
* @return a meta data instance
* @throws NullPointerException if contexts is null
*/
@NonNull CachedMetaData getMetaData(@NonNull QueryOptions queryOptions);
/**
* Gets PermissionData from the cache, using the most appropriate query options
* available at the time.
*
* <p>For {@link User}s, the most appropriate query options will be their
* {@link ContextManager#getQueryOptions(User) current active query options} if the
* corresponding player is online, and otherwise, will fallback to
* {@link ContextManager#getStaticQueryOptions() the current static query options}
* if they are offline.</p>
*
* <p>For {@link Group}s, the most appropriate query options will always be
* {@link ContextManager#getStaticQueryOptions()} the current static query options.</p>
*
* @return a permission data instance
* @since 5.1
*/
@NonNull CachedPermissionData getPermissionData();
/**
* Gets MetaData from the cache, using the most appropriate query options
* available at the time.
*
* <p>For {@link User}s, the most appropriate query options will be their
* {@link ContextManager#getQueryOptions(User) current active query options} if the
* corresponding player is online, and otherwise, will fallback to
* {@link ContextManager#getStaticQueryOptions() the current static query options}
* if they are offline.</p>
*
* <p>For {@link Group}s, the most appropriate query options will always be
* {@link ContextManager#getStaticQueryOptions()} the current static query options.</p>
*
* @return a meta data instance
* @since 5.1
* @see PermissionHolder#getQueryOptions()
*/
@NonNull CachedMetaData getMetaData();
/**
* Invalidates all cached {@link CachedPermissionData} and {@link CachedMetaData}
* instances.
@@ -48,42 +48,52 @@ public interface CachedMetaData extends CachedData {
@Nullable String getMetaValue(String key);
/**
* Gets the user's highest priority prefix, or null if the user has no prefixes
* Gets the holder's highest priority prefix, or null if the holder has no prefixes
*
* @return a prefix string, or null
*/
@Nullable String getPrefix();
/**
* Gets the user's highest priority suffix, or null if the user has no suffixes
* Gets the holder's highest priority suffix, or null if the holder has no suffixes
*
* @return a suffix string, or null
*/
@Nullable String getSuffix();
/**
* Gets an immutable copy of the meta this user has.
* Gets an immutable copy of the meta this holder has.
*
* @return an immutable map of meta
*/
@NonNull Map<String, List<String>> getMeta();
/**
* Gets an immutable sorted map of all of the prefixes the user has, whereby the first value is the highest priority
* prefix.
* Gets an immutable sorted map of all of the prefixes the holder has, whereby the first
* value is the highest priority prefix.
*
* @return a sorted map of prefixes
*/
@NonNull SortedMap<Integer, String> getPrefixes();
/**
* Gets an immutable sorted map of all of the suffixes the user has, whereby the first value is the highest priority
* suffix.
* Gets an immutable sorted map of all of the suffixes the holder has, whereby the first
* value is the highest priority suffix.
*
* @return a sorted map of suffixes
*/
@NonNull SortedMap<Integer, String> getSuffixes();
/**
* Gets the name of the holders primary group.
*
* <p>Will return {@code null} for Group holder types.</p>
*
* @return the name of the primary group
* @since 5.1
*/
@Nullable String getPrimaryGroup();
/**
* Gets the definition used for the prefix stack
*
@@ -26,12 +26,64 @@
package net.luckperms.api.context;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* Represents an individual context pair.
*
* <p>Context keys and values may not be null or empty. A key/value will be
* deemed empty if it's length is zero, or if it consists of only space
* characters.</p>
*
* @see ContextSet
*/
public interface Context {
/**
* Tests whether {@code key} is valid.
*
* <p>Context keys and values may not be null or empty. A key/value will be
* deemed empty if it's length is zero, or if it consists of only space
* characters.</p>
*
* <p>An exception is thrown when an invalid key is added to a {@link ContextSet}.</p>
*
* @param key the key to test
* @return true if valid, false otherwise.
* @since 5.1
*/
static boolean isValidKey(@Nullable String key) {
if (key == null || key.isEmpty()) {
return false;
}
// look for a non-whitespace character
for (int i = 0, n = key.length(); i < n; i++) {
if (key.charAt(i) != ' ') {
return true;
}
}
return false;
}
/**
* Tests whether {@code value} is valid.
*
* <p>Context keys and values may not be null or empty. A key/value will be
* deemed empty if it's length is zero, or if it consists of only space
* characters.</p>
*
* <p>An exception is thrown when an invalid value is added to a {@link ContextSet}.</p>
*
* @param value the value to test
* @return true if valid, false otherwise.
* @since 5.1
*/
static boolean isValidValue(@Nullable String value) {
return isValidKey(value); // the same for now...
}
/**
* Gets the context key.
*
@@ -37,11 +37,11 @@ import java.util.Optional;
* Manages {@link ContextCalculator}s, and calculates applicable contexts for a
* given type.
*
* This interface accepts {@link Object} types as a parameter to avoid having to depend
* <p>This interface accepts {@link Object} types as a parameter to avoid having to depend
* on specific server implementations. In all cases, the "player" or "subject" type for
* the platform must be used.
* the platform must be used.</p>
*
* Specifically:
* <p>Specifically:</p>
*
* <p></p>
* <ul>
@@ -138,6 +138,18 @@ public interface ContextManager {
*/
void unregisterCalculator(@NonNull ContextCalculator<?> calculator);
/**
* Signal to the {@link ContextManager} that a {@code subject}s
* current contexts have changed.
*
* <p>It is not strictly necessary to make a call to this method every time a context
* changes.</p>
*
* @param subject the subject
* @since 5.2
*/
void signalContextUpdate(@NonNull Object subject);
/**
* Gets the {@link ContextSetFactory}, responsible for creating
* {@link ContextSet} instances.
@@ -150,7 +162,11 @@ public interface ContextManager {
* Invalidates the lookup cache for a given subject
*
* @param subject the subject
* @deprecated use {@link #signalContextUpdate(Object)}
*/
void invalidateCache(@NonNull Object subject);
@Deprecated
default void invalidateCache(@NonNull Object subject) {
signalContextUpdate(subject);
}
}
@@ -0,0 +1,63 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.luckperms.api.context;
import net.luckperms.api.query.OptionKey;
/**
* Mode for determining whether a {@link ContextSet} satisfies another.
*
* @since 5.2
* @see ContextSet#isSatisfiedBy(ContextSet, ContextSatisfyMode)
*/
public enum ContextSatisfyMode {
/**
* Mode where a {@link ContextSet} A will be satisfied by another set B, if at least one of the
* key-value entries per key in A are also in B.
*
* <p>For example, given <code>A = {server=survival, world=overworld, world=nether}</code>,
* another set {@code X} will satisfy {@code A} if {@code X} contains
* <code>server=survival AND (world=overworld OR world=nether)</code>.</p>
*/
AT_LEAST_ONE_VALUE_PER_KEY,
/**
* Mode where a {@link ContextSet} A will be satisfied by another set B, if all key-value
* entries in A are also in B.
*
* <p>For example, given <code>A = {server=survival, world=overworld, world=nether}</code>,
* another set {@code X} will satisfy {@code A} if {@code X} contains
* <code>server=survival AND world=overworld AND world=nether</code>.</p>
*/
ALL_VALUES_PER_KEY;
/**
* The {@link OptionKey} for {@link ContextSatisfyMode}.
*/
public static final OptionKey<ContextSatisfyMode> KEY = OptionKey.of("contextsatisfymode", ContextSatisfyMode.class);
}
@@ -40,19 +40,33 @@ import java.util.Set;
* something will apply.</p>
*
* <p>A single "context" consists of a key and a value, both strings. The key
* represents the type of context, and the value represents the setting of the
* context key.</p>
* describes the type of the context, and the value represents what the key is
* set to.</p>
*
* <p>For example, a context with {@code key=world} and {@code value=world_nether}
* describes that a subject is in the "world_nether" world.</p>
*
* <p>Contexts are exposed to end users and manipulated when managing permissions.
* For this reason, context keys should strike a balance between being descriptive
* and succinct.</p>
*
* <p>Context keys and values are case-insensitive, and will be automatically
* converted to {@link String#toLowerCase() lowercase} when added to a
* context set. Keys and values cannot be null or empty (len=0 or consisting of
* only whitespace).</p>
*
* <p>If a context key is formed of more than one word, the parts should be
* separated by the '{@code -}' character. (e.g. "{@code server-type}")</p>
*
* <p>If a context key is likely to conflict with another plugin, it should be
* appropriately namespaced using the name of the plugin providing the context.
* The namespace should be at the start of the context key, and separated using
* the '{@code :}' character. (e.g. "{@code worldguard:region}" for WorldGuard
* regions)</p>
*
* <p>Contexts can be combined with each other to form so called
* "context sets" - simply a collection of context pairs.</p>
*
* <p>Context keys and values are case-insensitive, and will be converted to
* {@link String#toLowerCase() lowercase} by all implementations.</p>
*
* <p>Context keys and values may not be null or empty. A key/value will be
* deemed empty if it's length is zero, or if it consists of only space
* characters.</p>
*
* <p>Two default ContextSet implementations are provided.
* {@link MutableContextSet} allows the addition and removal of context keys
* after construction, and {@link ImmutableContextSet} does not.</p>
@@ -197,17 +211,47 @@ public interface ContextSet extends Iterable<Context> {
}
/**
* Returns if this {@link ContextSet} is fully "satisfied" by another set.
* Returns if the {@link ContextSet} contains any of the given context pairings.
*
* <p>For a context set to "satisfy" another, it must itself contain all of
* the context pairings in the other set.</p>
*
* <p>Mathematically, this method returns true if this set is a <b>subset</b> of the other.</p>
*
* @param other the other set to check
* @return true if all entries in this set are also in the other set
* @param key the key to look for
* @param values the values to look for
* @return true if the set contains any of the pairs
* @since 5.2
*/
boolean isSatisfiedBy(@NonNull ContextSet other);
default boolean containsAny(@NonNull String key, @NonNull Iterable<String> values) {
Objects.requireNonNull(key, "key");
Objects.requireNonNull(values, "values");
for (String value : values) {
if (contains(key, value)) {
return true;
}
}
return false;
}
/**
* Returns if this {@link ContextSet} is "satisfied" by another set.
*
* <p>{@link ContextSatisfyMode#AT_LEAST_ONE_VALUE_PER_KEY} is the mode used by this method.</p>
*
* @param other the other set
* @return true if this context set is satisfied by the other
*/
default boolean isSatisfiedBy(@NonNull ContextSet other) {
return isSatisfiedBy(other, ContextSatisfyMode.AT_LEAST_ONE_VALUE_PER_KEY);
}
/**
* Returns if this {@link ContextSet} is "satisfied" by another set, according to the given
* {@code mode}.
*
* @param other the other set
* @param mode the mode to use
* @return true if this context set is satisfied by the other
* @since 5.2
*/
boolean isSatisfiedBy(@NonNull ContextSet other, @NonNull ContextSatisfyMode mode);
/**
* Returns if the {@link ContextSet} is empty.
@@ -0,0 +1,76 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.luckperms.api.event.context;
import net.luckperms.api.context.ContextManager;
import net.luckperms.api.event.LuckPermsEvent;
import net.luckperms.api.event.util.Param;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.Optional;
/**
* Called when a subject's current/active contexts are updated.
*
* <p>There are no guarantees that this event will be called for every update. It is merely to be
* used as a "hint" for plugins which depend on the current active contexts for a player.</p>
*
* <p>It will always be fired following a call to
* {@link ContextManager#signalContextUpdate(Object)}.</p>
*
* <p>The {@link #getSubject() subject} is always an instance of the platform's subject type. See
* {@link ContextManager} for details.</p>
*
* <p>Unlike most other LuckPerms events, this event is not fired asynchronously. Care should be
* taken to ensure listeners are lightweight. Additionally, listeners should ensure they do not
* cause further updates to player context, thus possibly causing a stack overflow.</p>
*
* @since 5.2
*/
public interface ContextUpdateEvent extends LuckPermsEvent {
/**
* Gets the subject whose contexts were updated.
*
* @return the subject
*/
@Param(0)
@NonNull Object getSubject();
/**
* Gets the subject whose contexts were updated, casted to a given type.
*
* @param subjectClass the type to cast to
* @param <T> the subject type
* @return the casted subject
*/
default <T> @NonNull Optional<T> getSubject(@NonNull Class<T> subjectClass) {
Object subject = getSubject();
return subjectClass.isInstance(subject) ? Optional.of(subjectClass.cast(subject)) : Optional.empty();
}
}
@@ -26,8 +26,8 @@
package net.luckperms.api.event.group;
import net.luckperms.api.event.LuckPermsEvent;
import net.luckperms.api.event.util.Param;
import net.luckperms.api.event.cause.CreationCause;
import net.luckperms.api.event.util.Param;
import net.luckperms.api.model.group.Group;
import org.checkerframework.checker.nullness.qual.NonNull;
@@ -26,8 +26,8 @@
package net.luckperms.api.event.group;
import net.luckperms.api.event.LuckPermsEvent;
import net.luckperms.api.event.util.Param;
import net.luckperms.api.event.cause.DeletionCause;
import net.luckperms.api.event.util.Param;
import net.luckperms.api.node.Node;
import org.checkerframework.checker.nullness.qual.NonNull;
@@ -26,8 +26,8 @@
package net.luckperms.api.event.log;
import net.luckperms.api.actionlog.Action;
import net.luckperms.api.event.type.Cancellable;
import net.luckperms.api.event.LuckPermsEvent;
import net.luckperms.api.event.type.Cancellable;
import net.luckperms.api.event.util.Param;
import org.checkerframework.checker.nullness.qual.NonNull;
@@ -26,8 +26,8 @@
package net.luckperms.api.event.log;
import net.luckperms.api.actionlog.Action;
import net.luckperms.api.event.type.Cancellable;
import net.luckperms.api.event.LuckPermsEvent;
import net.luckperms.api.event.type.Cancellable;
import net.luckperms.api.event.util.Param;
import org.checkerframework.checker.nullness.qual.NonNull;
@@ -26,8 +26,8 @@
package net.luckperms.api.event.log;
import net.luckperms.api.actionlog.Action;
import net.luckperms.api.event.type.Cancellable;
import net.luckperms.api.event.LuckPermsEvent;
import net.luckperms.api.event.type.Cancellable;
import net.luckperms.api.event.util.Param;
import net.luckperms.api.platform.PlatformEntity;
@@ -26,8 +26,8 @@
package net.luckperms.api.event.log;
import net.luckperms.api.actionlog.Action;
import net.luckperms.api.event.type.Cancellable;
import net.luckperms.api.event.LuckPermsEvent;
import net.luckperms.api.event.type.Cancellable;
import net.luckperms.api.event.util.Param;
import org.checkerframework.checker.nullness.qual.NonNull;
@@ -25,8 +25,8 @@
package net.luckperms.api.event.sync;
import net.luckperms.api.event.type.Cancellable;
import net.luckperms.api.event.LuckPermsEvent;
import net.luckperms.api.event.type.Cancellable;
import net.luckperms.api.event.util.Param;
import org.checkerframework.checker.nullness.qual.NonNull;
@@ -25,8 +25,8 @@
package net.luckperms.api.event.sync;
import net.luckperms.api.event.type.Cancellable;
import net.luckperms.api.event.LuckPermsEvent;
import net.luckperms.api.event.type.Cancellable;
/**
* Called before a sync task runs
@@ -26,8 +26,8 @@
package net.luckperms.api.event.track;
import net.luckperms.api.event.LuckPermsEvent;
import net.luckperms.api.event.util.Param;
import net.luckperms.api.event.cause.CreationCause;
import net.luckperms.api.event.util.Param;
import net.luckperms.api.track.Track;
import org.checkerframework.checker.nullness.qual.NonNull;
@@ -26,8 +26,8 @@
package net.luckperms.api.event.track;
import net.luckperms.api.event.LuckPermsEvent;
import net.luckperms.api.event.util.Param;
import net.luckperms.api.event.cause.DeletionCause;
import net.luckperms.api.event.util.Param;
import org.checkerframework.checker.nullness.qual.NonNull;
@@ -26,8 +26,8 @@
package net.luckperms.api.event.user.track;
import net.luckperms.api.event.LuckPermsEvent;
import net.luckperms.api.event.util.Param;
import net.luckperms.api.event.type.Sourced;
import net.luckperms.api.event.util.Param;
import net.luckperms.api.model.user.User;
import net.luckperms.api.track.Track;
@@ -26,18 +26,24 @@
package net.luckperms.api.model;
import net.luckperms.api.cacheddata.CachedDataManager;
import net.luckperms.api.context.ContextManager;
import net.luckperms.api.model.data.DataType;
import net.luckperms.api.model.data.NodeMap;
import net.luckperms.api.model.group.Group;
import net.luckperms.api.model.user.User;
import net.luckperms.api.node.Node;
import net.luckperms.api.node.NodeType;
import net.luckperms.api.query.Flag;
import net.luckperms.api.query.QueryOptions;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.SortedSet;
import java.util.UUID;
import java.util.stream.Collectors;
/**
* Generic superinterface for an object which holds permissions.
@@ -108,6 +114,24 @@ public interface PermissionHolder {
*/
@NonNull String getFriendlyName();
/**
* Gets the most appropriate query options available at the time for the
* {@link PermissionHolder}.
*
* <p>For {@link User}s, the most appropriate query options will be their
* {@link ContextManager#getQueryOptions(User) current active query options} if the
* corresponding player is online, and otherwise, will fallback to
* {@link ContextManager#getStaticQueryOptions() the current static query options}
* if they are offline.</p>
*
* <p>For {@link Group}s, the most appropriate query options will always be
* {@link ContextManager#getStaticQueryOptions()} the current static query options.</p>
*
* @return query options
* @since 5.1
*/
@NonNull QueryOptions getQueryOptions();
/**
* Gets the holders {@link CachedDataManager} cache.
*
@@ -121,7 +145,7 @@ public interface PermissionHolder {
* @param dataType the data type
* @return the data
*/
NodeMap getData(@NonNull DataType dataType);
@NonNull NodeMap getData(@NonNull DataType dataType);
/**
* Gets the holders {@link DataType#NORMAL} data.
@@ -149,35 +173,60 @@ public interface PermissionHolder {
@NonNull NodeMap transientData();
/**
* Gets a flattened/squashed view of the holders permissions.
* Gets a flattened view of the holders own {@link Node}s.
*
* <p>This list is constructed using the values
* of both the transient and enduring backing multimaps.</p>
* <p>This list is constructed using the values of both the {@link #data() normal}
* and {@link #transientData() transient} backing node maps.</p>
*
* <p>This means that it <b>may contain</b> duplicate entries.</p>
*
* <p>Use {@link #getDistinctNodes()} for a view without duplicates.</p>
* <p>It <b>may contain</b> duplicate entries if the same node is added to both the normal
* and transient node maps. You can use {@link #getDistinctNodes()} for a view without
* duplicates.</p>
*
* <p>This method <b>does not</b> resolve inheritance rules.</p>
*
* @return a list of the holders own nodes.
* @return a collection of the holders own nodes.
*/
@NonNull Collection<Node> getNodes();
default @NonNull Collection<Node> getNodes() {
/* This default method is overridden in the implementation, and is just here
to demonstrate what this method does in the API sources. */
List<Node> nodes = new ArrayList<>();
nodes.addAll(data().toCollection());
nodes.addAll(transientData().toCollection());
return nodes;
}
/**
* Gets a sorted set of all held nodes.
* Gets a flattened view of the holders own {@link Node}s of the given {@code type}.
*
* @param type the type of node to filter by
* @param <T> the node type
* @return a filtered collection of the holders own nodes
* @see #getNodes()
* @since 5.1
*/
default <T extends Node> @NonNull Collection<T> getNodes(@NonNull NodeType<T> type) {
/* This default method is overridden in the implementation, and is just here
to demonstrate what this method does in the API sources. */
return getNodes().stream()
.filter(type::matches)
.map(type::cast)
.collect(Collectors.toList());
}
/**
* Gets a flattened and sorted view of the holders own distinct {@link Node}s.
*
* <p>Effectively a sorted version of {@link #getNodes()}, without duplicates. Use the
* aforementioned method if you don't require either of these attributes.</p>
*
* <p>This method <b>does not</b> resolve inheritance rules.</p>
*
* @return an immutable set of permissions in priority order
* @return a sorted set of the holders own distinct nodes
*/
@NonNull SortedSet<Node> getDistinctNodes();
/**
* Recursively resolves this holders permissions.
* Gets a resolved view of the holders own and inherited {@link Node}s.
*
* <p>The returned list will contain every inherited
* node the holder has, in the order that they were inherited in.</p>
@@ -189,22 +238,66 @@ public interface PermissionHolder {
* with the entries from the end of the inheritance tree appearing last.</p>
*
* @param queryOptions the query options
* @return a list of nodes
* @return a list of the holders inherited nodes
*/
@NonNull Collection<Node> resolveInheritedNodes(@NonNull QueryOptions queryOptions);
/**
* Gets a mutable sorted set of the nodes that this object has and inherits, filtered by context
* Gets a resolved view of the holders own and inherited {@link Node}s of a given {@code type}.
*
* <p>Nodes are sorted into priority order. The order of inheritance is only important during
* the process of flattening inherited entries.</p>
* @param type the type of node to filter by
* @param queryOptions the query options
* @param <T> the node type
* @return a filtered list of the holders inherited nodes
* @see #resolveInheritedNodes(QueryOptions)
* @since 5.1
*/
default <T extends Node> @NonNull Collection<T> resolveInheritedNodes(@NonNull NodeType<T> type, @NonNull QueryOptions queryOptions) {
/* This default method is overridden in the implementation, and is just here
to demonstrate what this method does in the API sources. */
return resolveInheritedNodes(queryOptions).stream()
.filter(type::matches)
.map(type::cast)
.collect(Collectors.toList());
}
/**
* Gets a resolved and sorted view of the holders own and inherited distinct {@link Node}s.
*
* <p>Effectively a sorted version of {@link #resolveInheritedNodes(QueryOptions)},
* without duplicates. Use the aforementioned method if you don't require either of these
* attributes.</p>
*
* <p>Inheritance is performed according to the platforms rules, and the order will vary
* depending on the accumulation order. By default, the holders own nodes are first in the list,
* with the entries from the end of the inheritance tree appearing last.</p>
*
* @param queryOptions the query options
* @return an immutable sorted set of permissions
* @throws NullPointerException if the context is null
* @return a sorted set of the holders distinct inherited nodes
*/
@NonNull SortedSet<Node> resolveDistinctInheritedNodes(@NonNull QueryOptions queryOptions);
/**
* Gets a collection of the {@link Group}s this holder inherits nodes from.
*
* <p>If {@link Flag#RESOLVE_INHERITANCE} is set, this will include holders inherited from both
* directly and indirectly (through directly inherited groups). It will effectively resolve the
* whole "inheritance tree".</p>
*
* <p>If {@link Flag#RESOLVE_INHERITANCE} is not set, then the traversal will only go one
* level up the inheritance tree, and return only directly inherited groups.</p>
*
* <p>The collection will be ordered according to the platforms inheritance rules. The groups
* which are inherited from first will appear earlier in the list.</p>
*
* <p>The list will not contain the holder.</p>
*
* @param queryOptions the query options
* @return a collection of the groups the holder inherits from
* @since 5.1
*/
@NonNull Collection<Group> getInheritedGroups(@NonNull QueryOptions queryOptions);
/**
* Removes any temporary permissions that have expired.
*
@@ -26,11 +26,15 @@
package net.luckperms.api.model.group;
import net.luckperms.api.node.HeldNode;
import net.luckperms.api.node.Node;
import net.luckperms.api.node.matcher.NodeMatcher;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
@@ -98,6 +102,27 @@ public interface GroupManager {
*/
@NonNull CompletableFuture<Void> deleteGroup(@NonNull Group group);
/**
* Loads (or creates) a group from the plugin's storage provider, applies the given
* {@code action}, then saves the group's data back to storage.
*
* <p>This method effectively calls {@link #createAndLoadGroup(String)}, followed by the
* {@code action}, then {@link #saveGroup(Group)}, and returns an encapsulation of the whole
* process as a {@link CompletableFuture}. </p>
*
* @param name the name of the group
* @param action the action to apply to the group
* @return a future to encapsulate the operation
* @since 5.1
*/
default @NonNull CompletableFuture<Void> modifyGroup(@NonNull String name, @NonNull Consumer<? super Group> action) {
/* This default method is overridden in the implementation, and is just here
to demonstrate what this method does in the API sources. */
return createAndLoadGroup(name)
.thenApplyAsync(group -> { action.accept(group); return group; })
.thenCompose(this::saveGroup);
}
/**
* Loads all groups into memory.
*
@@ -105,13 +130,25 @@ public interface GroupManager {
*/
@NonNull CompletableFuture<Void> loadAllGroups();
/**
* Searches the {@link Group#data() normal node maps} of all known {@link Group}s for {@link Node}
* entries matching the given {@link NodeMatcher matcher}.
*
* @param matcher the matcher
* @return the entries which matched
* @since 5.1
*/
@NonNull <T extends Node> CompletableFuture<Map<String, Collection<T>>> searchAll(@NonNull NodeMatcher<? extends T> matcher);
/**
* Searches for a list of groups with a given permission.
*
* @param permission the permission to search for
* @return a list of held permissions, or null if the operation failed
* @throws NullPointerException if the permission is null
* @deprecated use {@link #searchAll(NodeMatcher)}
*/
@Deprecated
@NonNull CompletableFuture<List<HeldNode<String>>> getWithPermission(@NonNull String permission);
/**
@@ -27,11 +27,15 @@ package net.luckperms.api.model.user;
import net.luckperms.api.model.PlayerSaveResult;
import net.luckperms.api.node.HeldNode;
import net.luckperms.api.node.Node;
import net.luckperms.api.node.matcher.NodeMatcher;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
@@ -59,7 +63,7 @@ public interface UserManager {
/**
* Loads a user from the plugin's storage provider into memory.
*
* @param uniqueId the uuid of the user
* @param uniqueId the uuid of the user
* @param username the username, if known
* @return the resultant user
* @throws NullPointerException if the uuid is null
@@ -111,6 +115,27 @@ public interface UserManager {
*/
@NonNull CompletableFuture<Void> saveUser(@NonNull User user);
/**
* Loads a user from the plugin's storage provider, applies the given {@code action},
* then saves the user's data back to storage.
*
* <p>This method effectively calls {@link #loadUser(UUID)}, followed by the {@code action},
* then {@link #saveUser(User)}, and returns an encapsulation of the whole process as a
* {@link CompletableFuture}. </p>
*
* @param uniqueId the uuid of the user
* @param action the action to apply to the user
* @return a future to encapsulate the operation
* @since 5.1
*/
default @NonNull CompletableFuture<Void> modifyUser(@NonNull UUID uniqueId, @NonNull Consumer<? super User> action) {
/* This default method is overridden in the implementation, and is just here
to demonstrate what this method does in the API sources. */
return loadUser(uniqueId)
.thenApplyAsync(user -> { action.accept(user); return user; })
.thenCompose(this::saveUser);
}
/**
* Saves data about a player to the uuid caching system.
*
@@ -122,6 +147,17 @@ public interface UserManager {
*/
@NonNull CompletableFuture<PlayerSaveResult> savePlayerData(@NonNull UUID uniqueId, @NonNull String username);
/**
* Deletes any data about a given player from the uuid caching system.
*
* <p>Note that this method does not affect any saved user/permissions data.</p>
*
* @param uniqueId the users mojang unique id
* @return a future encapsulating the result of the operation
* @since 5.2
*/
@NonNull CompletableFuture<Void> deletePlayerData(@NonNull UUID uniqueId);
/**
* Gets a set all "unique" user UUIDs.
*
@@ -131,13 +167,25 @@ public interface UserManager {
*/
@NonNull CompletableFuture<Set<UUID>> getUniqueUsers();
/**
* Searches the {@link User#data() normal node maps} of all known {@link User}s for {@link Node}
* entries matching the given {@link NodeMatcher matcher}.
*
* @param matcher the matcher
* @return the entries which matched
* @since 5.1
*/
@NonNull <T extends Node> CompletableFuture<Map<UUID, Collection<T>>> searchAll(@NonNull NodeMatcher<? extends T> matcher);
/**
* Searches for a list of users with a given permission.
*
* @param permission the permission to search for
* @return a list of held permissions
* @throws NullPointerException if the permission is null
* @deprecated use {@link #searchAll(NodeMatcher)}
*/
@Deprecated
@NonNull CompletableFuture<List<HeldNode<UUID>>> getWithPermission(@NonNull String permission);
/**
@@ -0,0 +1,54 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.luckperms.api.node;
import org.checkerframework.checker.nullness.qual.NonNull;
/**
* Dummy implementation of {@link NodeEqualityPredicate}, used for the given constant
* implementations.
*
* <p>The implementation rule of not calling {@link Node#equals(Node, NodeEqualityPredicate)} is
* intentionally disregarded by this dummy implementation. The equals method has a special case for
* the dummy instances, preventing a stack overflow.</p>
*/
final class DummyNodeEqualityPredicate implements NodeEqualityPredicate {
private final String name;
DummyNodeEqualityPredicate(String name) {
this.name = name;
}
@Override
public boolean areEqual(@NonNull Node o1, @NonNull Node o2) {
return o1.equals(o2, this);
}
@Override
public String toString() {
return "NodeEqualityPredicate#" + this.name;
}
}
@@ -34,6 +34,7 @@ import org.checkerframework.checker.nullness.qual.NonNull;
*
* @param <T> the identifier type of the holder
*/
@Deprecated
public interface HeldNode<T> {
/**
@@ -40,6 +40,7 @@ import net.luckperms.api.node.types.WeightNode;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.time.Duration;
import java.time.Instant;
import java.util.Collection;
import java.util.Optional;
@@ -63,15 +64,24 @@ import java.util.stream.Stream;
* node, as well as methods to query and extract additional state and properties
* from these settings.</p>
*
* <p>Nodes have the following attributes:</p>
* <p>Nodes have the following 4 key attributes:</p>
* <p></p>
* <ul>
* <li>{@link #getKey() key} - the key of the node</li>
* <li>{@link #getValue() value} - the value of the node (false for negated)</li>
* <li>{@link #getContexts() context} - the contexts required for this node to apply </li>
* <li>{@link #getContexts() context} - the contexts required for this node to apply</li>
* <li>{@link #getExpiry() expiry} - the time when this node should expire</li>
* </ul>
*
* <p>These are the key attributes which are considered when evaluating node
* {@link #equals(Object) equality}.</p>
*
* <p>Nodes can also optionally have {@link #metadata(NodeMetadataKey) metadata} attached to them,
* added during construction using {@link NodeBuilder#withMetadata(NodeMetadataKey, Object)}, and
* queried using {@link #metadata(NodeMetadataKey)} and {@link #getMetadata(NodeMetadataKey)}.
* Such metadata is never considered when evaluating {@link #equals(Object)} or
* {@link #equals(Node, NodeEqualityPredicate)} (any form of equality check).</p>
*
* <p>There are a number of node types, all of which extend from this class:</p>
* <p></p>
* <ul>
@@ -95,7 +105,7 @@ public interface Node {
* @param key the key
* @return the node builder
*/
static @NonNull NodeBuilder builder(@NonNull String key) {
static @NonNull NodeBuilder<?, ?> builder(@NonNull String key) {
return LuckPermsProvider.get().getNodeBuilderRegistry().forKey(key);
}
@@ -153,8 +163,8 @@ public interface Node {
/**
* Gets the time when this node will expire.
*
* @return the {@link Instant} when this node will expire, or null if it
* doesn't have an expiry time
* @return the {@link Instant} when this node will expire, or
* {@code null} if it doesn't have an expiry time
*/
@Nullable Instant getExpiry();
@@ -167,6 +177,17 @@ public interface Node {
*/
boolean hasExpired();
/**
* Gets the time until this node will expire.
*
* <p>Returns {@code null} if the node doesn't have an expiry time,
* and a {@link Duration#isNegative() negative} duration if it has already expired.</p>
*
* @return the duration until the nodes expiry
* @since 5.1
*/
@Nullable Duration getExpiryDuration();
/**
* Gets the contexts required for this node to apply.
*
@@ -31,7 +31,9 @@ import net.luckperms.api.node.metadata.NodeMetadataKey;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.time.Duration;
import java.time.temporal.TemporalAccessor;
import java.time.temporal.TemporalAmount;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
@@ -85,7 +87,20 @@ public interface NodeBuilder<N extends ScopedNode<N, B>, B extends NodeBuilder<N
/**
* Sets the time when the node should expire.
*
* <p>The expiry timestamp is calculated relative to the current
* <p>The expiry time is calculated relative to the current
* system time.</p>
*
* @param duration how long the node should be added for
* @return the builder
* @see Node#getExpiry()
* @since 5.1
*/
@NonNull B expiry(@Nullable TemporalAmount duration);
/**
* Sets the time when the node should expire.
*
* <p>The expiry time is calculated relative to the current
* system time.</p>
*
* @param duration how long the node should be added for
@@ -97,8 +112,7 @@ public interface NodeBuilder<N extends ScopedNode<N, B>, B extends NodeBuilder<N
throw new IllegalArgumentException("duration must be positive");
}
long seconds = Objects.requireNonNull(unit, "unit").toSeconds(duration);
long timeNow = System.currentTimeMillis() / 1000L;
return expiry(timeNow + seconds);
return expiry(Duration.ofSeconds(seconds));
}
/**
@@ -45,64 +45,91 @@ public interface NodeBuilderRegistry {
/**
* Gets a {@link NodeBuilder} applicable for the given key.
*
* <p>Prefer using the {@link Node#builder(String)} accessor.</p>
*
* @param key the key
* @return the node builder
* @see Node#builder(String)
*/
@NonNull NodeBuilder<?, ?> forKey(String key);
/**
* Gets a {@link NodeBuilder} for {@link PermissionNode}s.
*
* <p>Prefer using the {@link PermissionNode#builder()} accessor.</p>
*
* @return the node builder
* @see PermissionNode#builder()
*/
PermissionNode.@NonNull Builder forPermission();
/**
* Gets a {@link NodeBuilder} for {@link RegexPermissionNode}s.
*
* <p>Prefer using the {@link RegexPermissionNode#builder()} accessor.</p>
*
* @return the node builder
* @see RegexPermissionNode#builder()
*/
RegexPermissionNode.@NonNull Builder forRegexPermission();
/**
* Gets a {@link NodeBuilder} for {@link InheritanceNode}s.
*
* <p>Prefer using the {@link InheritanceNode#builder()} accessor.</p>
*
* @return the node builder
* @see InheritanceNode#builder()
*/
InheritanceNode.@NonNull Builder forInheritance();
/**
* Gets a {@link NodeBuilder} for {@link PrefixNode}s.
*
* <p>Prefer using the {@link PrefixNode#builder()} accessor.</p>
*
* @return the node builder
* @see PrefixNode#builder()
*/
PrefixNode.@NonNull Builder forPrefix();
/**
* Gets a {@link NodeBuilder} for {@link SuffixNode}s.
*
* <p>Prefer using the {@link SuffixNode#builder()} accessor.</p>
*
* @return the node builder
* @see SuffixNode#builder()
*/
SuffixNode.@NonNull Builder forSuffix();
/**
* Gets a {@link NodeBuilder} for {@link MetaNode}s.
*
* <p>Prefer using the {@link MetaNode#builder()} accessor.</p>
*
* @return the node builder
* @see MetaNode#builder()
*/
MetaNode.@NonNull Builder forMeta();
/**
* Gets a {@link NodeBuilder} for {@link WeightNode}s.
*
* <p>Prefer using the {@link WeightNode#builder()} accessor.</p>
*
* @return the node builder
* @see WeightNode#builder()
*/
WeightNode.@NonNull Builder forWeight();
/**
* Gets a {@link NodeBuilder} for {@link DisplayNameNode}s.
*
* <p>Prefer using the {@link DisplayNameNode#builder()} accessor.</p>
*
* @return the node builder
* @see DisplayNameNode#builder()
*/
DisplayNameNode.@NonNull Builder forDisplayName();
@@ -30,7 +30,18 @@ import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.function.Predicate;
/**
* A rule for determining if two nodes are equal.
* An equality test for determining if two nodes are to be considered equal.
*
* <p>Recall that {@link Node}s have 4 key attributes: key, value, context, expiry.</p>
*
* <p>In the default {@link Node#equals(Object)} implementation (equivalent to {@link #EXACT}),
* all 4 of these key attributes are considered. However, there are occasions where such strict
* equality checking is not desired, hence the use of this class.</p>
*
* <p>{@link NodeEqualityPredicate}s can either be used inline, by directly calling the
* {@link #areEqual(Node, Node)} method, or can be passed as a parameter to the
* {@link Node#equals(Node, NodeEqualityPredicate)} method. Either approach is valid, and both will
* result in the same result.</p>
*
* <p>Generally, implementations of this interface should fulfil the same
* requirements as the {@link Object#equals(Object)} contract.</p>
@@ -61,60 +72,88 @@ public interface NodeEqualityPredicate {
return other -> areEqual(node, other);
}
/*
* Some 'default' implementations of NodeEqualityPredicate are provided below.
*
* These are implemented in the common code, by a special case in the
* implementation of Node#equals. As noted above, this should generally be
* avoided.
*/
/**
* Represents an exact match.
*
* <p>All attributes of the nodes must match for them to be considered
* equal.</p>
*/
NodeEqualityPredicate EXACT = new NodeEqualityPredicate() {
@Override public boolean areEqual(@NonNull Node o1, @NonNull Node o2) { return o1.equals(o2, this); }
};
/**
* All attributes must match, except for
* {@link Node#getValue() value}, which is ignored.
*/
NodeEqualityPredicate IGNORE_VALUE = new NodeEqualityPredicate() {
@Override public boolean areEqual(@NonNull Node o1, @NonNull Node o2) { return o1.equals(o2, this); }
};
/**
* All attributes must match, except for the
* {@link Node#getExpiry() expiry time}, which is ignored.
* <p>Returns true if: (and)</p>
* <p></p>
* <ul>
* <li>{@link Node#getKey() key} = key</li>
* <li>{@link Node#getValue() value} = value</li>
* <li>{@link Node#getContexts() context} = context</li>
* <li>{@link Node#getExpiry() expiry} = expiry</li>
* </ul>
*
* <p>Note that with this setting, whether a node is temporary or not is
* still considered.</p>
* <p>All 4 attributes of the nodes must match to be considered equal.</p>
*
* <p>This is the default form of equality, used by {@link Node#equals(Object)}.</p>
*/
NodeEqualityPredicate IGNORE_EXPIRY_TIME = new NodeEqualityPredicate() {
@Override public boolean areEqual(@NonNull Node o1, @NonNull Node o2) { return o1.equals(o2, this); }
};
NodeEqualityPredicate EXACT = new DummyNodeEqualityPredicate("EXACT");
/**
* All attributes must match, except for
* {@link Node#getValue() value} and the
* Only the {@link Node#getKey() key}s need match, all other attributes are ignored.
*/
NodeEqualityPredicate ONLY_KEY = new DummyNodeEqualityPredicate("ONLY_KEY");
/**
* All attributes must match, except for {@link Node#getValue() value}, which is ignored.
*
* <p>Returns true if: (and)</p>
* <p></p>
* <ul>
* <li>{@link Node#getKey() key} = key</li>
* <li>{@link Node#getContexts() context} = context</li>
* <li>{@link Node#getExpiry() expiry} = expiry</li>
* </ul>
*/
NodeEqualityPredicate IGNORE_VALUE = new DummyNodeEqualityPredicate("IGNORE_VALUE");
/**
* All attributes must match, except for the {@link Node#getExpiry() expiry time}, which is
* ignored.
*
* <p>Note that with this setting, whether a node has an expiry or not is still considered.</p>
*
* <p>Returns true if: (and)</p>
* <p></p>
* <ul>
* <li>{@link Node#getKey() key} = key</li>
* <li>{@link Node#getValue() value} = value</li>
* <li>{@link Node#getContexts() context} = context</li>
* <li>{@link Node#hasExpiry() has expiry} = has expiry</li>
* </ul>
*/
NodeEqualityPredicate IGNORE_EXPIRY_TIME = new DummyNodeEqualityPredicate("IGNORE_EXPIRY_TIME");
/**
* All attributes must match, except for {@link Node#getValue() value} and the
* {@link Node#getExpiry() expiry time}, which are ignored.
*
* <p>Note that with this setting, whether a node has an expiry or not is still considered.</p>
*
* <p>Returns true if: (and)</p>
* <p></p>
* <ul>
* <li>{@link Node#getKey() key} = key</li>
* <li>{@link Node#getContexts() context} = context</li>
* <li>{@link Node#hasExpiry() has expiry} = has expiry</li>
* </ul>
*/
NodeEqualityPredicate IGNORE_EXPIRY_TIME_AND_VALUE = new NodeEqualityPredicate() {
@Override public boolean areEqual(@NonNull Node o1, @NonNull Node o2) { return o1.equals(o2, this); }
};
NodeEqualityPredicate IGNORE_EXPIRY_TIME_AND_VALUE = new DummyNodeEqualityPredicate("IGNORE_EXPIRY_TIME_AND_VALUE");
/**
* All attributes must match, except for
* {@link Node#getValue() value} and the if the node is
* {@link Node#hasExpiry() temporary}, which are ignored.
* All attributes must match, except for {@link Node#getValue() value} and the if the node
* {@link Node#hasExpiry() has an expiry}, which are ignored.
*
* <p>Effectively only considers the key and the context.</p>
*
* <p>Returns true if: (and)</p>
* <p></p>
* <ul>
* <li>{@link Node#getKey() key} = key</li>
* <li>{@link Node#getContexts() context} = context</li>
* </ul>
*/
NodeEqualityPredicate IGNORE_VALUE_OR_IF_TEMPORARY = new NodeEqualityPredicate() {
@Override public boolean areEqual(@NonNull Node o1, @NonNull Node o2) { return o1.equals(o2, this); }
};
NodeEqualityPredicate IGNORE_VALUE_OR_IF_TEMPORARY = new DummyNodeEqualityPredicate("IGNORE_VALUE_OR_IF_TEMPORARY");
}
@@ -0,0 +1,145 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.luckperms.api.node.matcher;
import net.luckperms.api.LuckPermsProvider;
import net.luckperms.api.node.Node;
import net.luckperms.api.node.NodeEqualityPredicate;
import net.luckperms.api.node.NodeType;
import net.luckperms.api.node.types.MetaNode;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.function.Predicate;
/**
* A predicate which matches certain {@link Node}s.
*
* @param <T> the node type matched
* @since 5.1
*/
public interface NodeMatcher<T extends Node> extends Predicate<Node> {
/**
* Gets a {@link NodeMatcher} which matches nodes with the same {@link Node#getKey() key}.
*
* <p>The {@link String#equalsIgnoreCase(String)} method is used to test key equality.</p>
*
* @param key the key
* @return the matcher
*/
static @NonNull NodeMatcher<Node> key(@NonNull String key) {
return LuckPermsProvider.get().getNodeMatcherFactory().key(key);
}
/**
* Gets a {@link NodeMatcher} which matches nodes with the same {@link Node#getKey() key}.
*
* <p>The {@link String#equalsIgnoreCase(String)} method is used to test key equality.</p>
*
* @param node the node to use for the key
* @param <T> the node type
* @return the matcher
*/
static <T extends Node> @NonNull NodeMatcher<T> key(@NonNull T node) {
return LuckPermsProvider.get().getNodeMatcherFactory().key(node);
}
/**
* Gets a {@link NodeMatcher} which matches nodes with a {@link Node#getKey() key} starting
* with the given string.
*
* @param startingWith the string to match
* @return the matcher
*/
static @NonNull NodeMatcher<Node> keyStartsWith(@NonNull String startingWith) {
return LuckPermsProvider.get().getNodeMatcherFactory().keyStartsWith(startingWith);
}
/**
* Gets a {@link NodeMatcher} which matches nodes which are
* {@link Node#equals(Node, NodeEqualityPredicate) equal to} the given {@code other} node
* according to the {@link NodeEqualityPredicate}.
*
* @param other the other node to test against
* @param equalityPredicate the equality predicate
* @param <T> the node type
* @return the matcher
*/
static <T extends Node> @NonNull NodeMatcher<T> equals(@NonNull T other, @NonNull NodeEqualityPredicate equalityPredicate) {
return LuckPermsProvider.get().getNodeMatcherFactory().equals(other, equalityPredicate);
}
/**
* Gets a {@link NodeMatcher} which matches {@link MetaNode}s with the same
* {@link MetaNode#getMetaKey() meta key}.
*
* <p>The {@link String#equalsIgnoreCase(String)} method is used to test key equality.</p>
*
* @param metaKey the meta key
* @return the matcher
*/
static @NonNull NodeMatcher<MetaNode> metaKey(@NonNull String metaKey) {
return LuckPermsProvider.get().getNodeMatcherFactory().metaKey(metaKey);
}
/**
* Gets a {@link NodeMatcher} which matches {@link MetaNode}s with the same
* {@link MetaNode#getMetaKey() meta key}.
*
* <p>The {@link String#equalsIgnoreCase(String)} method is used to test key equality.</p>
*
* @param metaNode the meta node to use for the meta key
* @return the matcher
*/
static @NonNull NodeMatcher<MetaNode> metaKey(@NonNull MetaNode metaNode) {
return metaKey(metaNode.getMetaKey());
}
/**
* Gets a {@link NodeMatcher} which matches {@link Node}s with the same
* type as the given {@link NodeType}.
*
* <p>{@link NodeType#PERMISSION}, {@link NodeType#CHAT_META} and
* {@link NodeType#META_OR_CHAT_META} are not supported by this method.</p>
*
* @param type the node type
* @param <T> the node type
* @return the matcher
*/
static <T extends Node> @NonNull NodeMatcher<T> type(NodeType<? extends T> type) {
return LuckPermsProvider.get().getNodeMatcherFactory().type(type);
}
/**
* Tests to see if the given {@link Node} matches.
*
* @param node the node to test
* @return true if the node matched
*/
@Override
boolean test(@NonNull Node node);
}
@@ -0,0 +1,120 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.luckperms.api.node.matcher;
import net.luckperms.api.node.Node;
import net.luckperms.api.node.NodeEqualityPredicate;
import net.luckperms.api.node.NodeType;
import net.luckperms.api.node.types.MetaNode;
import org.checkerframework.checker.nullness.qual.NonNull;
/**
* A factory which creates {@link NodeMatcher}s.
*
* @since 5.1
*/
public interface NodeMatcherFactory {
/**
* Gets a {@link NodeMatcher} which matches nodes with the same {@link Node#getKey() key}.
*
* <p>The {@link String#equalsIgnoreCase(String)} method is used to test key equality.</p>
*
* <p>Prefer using the {@link NodeMatcher#key(String)} accessor.</p>
*
* @param key the key
* @return the matcher
*/
@NonNull NodeMatcher<Node> key(@NonNull String key);
/**
* Gets a {@link NodeMatcher} which matches nodes with the same {@link Node#getKey() key}.
*
* <p>The {@link String#equalsIgnoreCase(String)} method is used to test key equality.</p>
*
* <p>Prefer using the {@link NodeMatcher#key(Node)} accessor.</p>
*
* @param node the node to use for the key
* @param <T> the node type
* @return the matcher
*/
<T extends Node> @NonNull NodeMatcher<T> key(@NonNull T node);
/**
* Gets a {@link NodeMatcher} which matches nodes with a {@link Node#getKey() key} starting
* with the given string.
*
* <p>Prefer using the {@link NodeMatcher#keyStartsWith(String)} accessor.</p>
*
* @param startingWith the string to match
* @return the matcher
*/
@NonNull NodeMatcher<Node> keyStartsWith(@NonNull String startingWith);
/**
* Gets a {@link NodeMatcher} which matches nodes which are
* {@link Node#equals(Node, NodeEqualityPredicate) equal to} the given {@code other} node
* according to the {@link NodeEqualityPredicate}.
*
* <p>Prefer using the {@link NodeMatcher#equals(Node, NodeEqualityPredicate)} accessor.</p>
*
* @param other the other node to test against
* @param equalityPredicate the equality predicate
* @param <T> the node type
* @return the matcher
*/
@NonNull <T extends Node> NodeMatcher<T> equals(@NonNull T other, @NonNull NodeEqualityPredicate equalityPredicate);
/**
* Gets a {@link NodeMatcher} which matches {@link MetaNode}s with the same
* {@link MetaNode#getMetaKey() meta key}.
*
* <p>The {@link String#equalsIgnoreCase(String)} method is used to test key equality.</p>
*
* <p>Prefer using the {@link NodeMatcher#metaKey(String)} accessor.</p>
*
* @param metaKey the meta key
* @return the matcher
*/
@NonNull NodeMatcher<MetaNode> metaKey(@NonNull String metaKey);
/**
* Gets a {@link NodeMatcher} which matches {@link Node}s with the same
* type as the given {@link NodeType}.
*
* <p>{@link NodeType#PERMISSION}, {@link NodeType#CHAT_META} and
* {@link NodeType#META_OR_CHAT_META} are not supported by this method.</p>
*
* <p>Prefer using the {@link NodeMatcher#type(NodeType)} accessor.</p>
*
* @param type the node type
* @param <T> the node type
* @return the matcher
*/
<T extends Node> @NonNull NodeMatcher<T> type(NodeType<? extends T> type);
}
@@ -0,0 +1,116 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.luckperms.api.platform;
import net.luckperms.api.cacheddata.CachedDataManager;
import net.luckperms.api.cacheddata.CachedMetaData;
import net.luckperms.api.cacheddata.CachedPermissionData;
import net.luckperms.api.context.ContextManager;
import net.luckperms.api.context.ImmutableContextSet;
import net.luckperms.api.model.user.User;
import net.luckperms.api.model.user.UserManager;
import net.luckperms.api.query.QueryOptions;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.UUID;
/**
* A utility class for adapting platform Player instances to LuckPerms {@link User}s.
*
* <p>Note: this class will only work for online players.</p>
*
* <p>The "player type" parameter must be equal to the class or interface used by the
* server platform to represent players.</p>
*
* <p>Specifically:</p>
*
* <p></p>
* <ul>
* <li>{@code org.bukkit.entity.Player}</li>
* <li>{@code net.md_5.bungee.api.connection.ProxiedPlayer}</li>
* <li>{@code org.spongepowered.api/entity.living.player.Player}</li>
* <li>{@code cn.nukkit.Player}</li>
* <li>{@code com.velocitypowered.api.proxy.Player}</li>
* </ul>
*
* @param <T> the player type
* @since 5.1
*/
public interface PlayerAdapter<T> {
/**
* Gets the {@link User} instance for the given {@code player}.
*
* @param player the player
* @return the user
* @see UserManager#getUser(UUID)
*/
@NonNull User getUser(@NonNull T player);
/**
* Gets current {@link ImmutableContextSet active context} for the {@code player}.
*
* @param player the player
* @return the active context for the player
* @see ContextManager#getContext(Object)
*/
@NonNull ImmutableContextSet getContext(@NonNull T player);
/**
* Gets current {@link QueryOptions active query options} for the {@code player}.
*
* @param player the player
* @return the active query options for the player
* @see ContextManager#getQueryOptions(Object)
*/
@NonNull QueryOptions getQueryOptions(@NonNull T player);
/**
* Gets the current {@link CachedPermissionData} for the {@code player},
* using their {@link #getQueryOptions(Object) active query options}.
*
* @param player the player
* @return the cached permission data for the player
* @see CachedDataManager#getPermissionData()
*/
default @NonNull CachedPermissionData getPermissionData(@NonNull T player) {
return getUser(player).getCachedData().getPermissionData(getQueryOptions(player));
}
/**
* Gets the current {@link CachedMetaData} for the {@code player},
* using their {@link #getQueryOptions(Object) active query options}.
*
* @param player the player
* @return the cached meta data for the player
* @see CachedDataManager#getMetaData()
*/
default @NonNull CachedMetaData getMetaData(@NonNull T player) {
return getUser(player).getCachedData().getMetaData(getQueryOptions(player));
}
}
@@ -27,6 +27,9 @@ package net.luckperms.api.query;
/**
* The flags which can be set for a query.
*
* <p>By default (in places like new instances of {@link QueryOptions.Builder} and
* {@link QueryOptions#defaultContextualOptions()}), all {@link Flag}s are set to true.</p>
*/
public enum Flag {
@@ -26,6 +26,7 @@
package net.luckperms.api.query;
import net.luckperms.api.LuckPermsProvider;
import net.luckperms.api.context.ContextSatisfyMode;
import net.luckperms.api.context.ContextSet;
import net.luckperms.api.context.ImmutableContextSet;
@@ -71,7 +72,7 @@ public interface QueryOptions {
* @return the query options
*/
static @NonNull QueryOptions contextual(@NonNull ContextSet context) {
return defaultContextualOptions().toBuilder().context(context).build();
return builder(QueryMode.CONTEXTUAL).context(context).build();
}
/**
@@ -94,7 +95,7 @@ public interface QueryOptions {
* @return the default non contextual query options
*/
static @NonNull QueryOptions nonContextual() {
return DefaultQueryOptions.nonContextual();
return LuckPermsProvider.get().getQueryOptionsRegistry().defaultNonContextualOptions();
}
/**
@@ -107,7 +108,7 @@ public interface QueryOptions {
* @return the default contextual query options
*/
static @NonNull QueryOptions defaultContextualOptions() {
return DefaultQueryOptions.contextual();
return LuckPermsProvider.get().getQueryOptionsRegistry().defaultContextualOptions();
}
/**
@@ -166,10 +167,29 @@ public interface QueryOptions {
* Gets whether this {@link QueryOptions} satisfies the given required
* {@link ContextSet context}.
*
* <p>{@link ContextSatisfyMode#AT_LEAST_ONE_VALUE_PER_KEY} is used if this {@link QueryOptions}
* instance doesn't have the {@link ContextSatisfyMode#KEY option key} set.</p>
*
* @param contextSet the contexts
* @return the result
*/
boolean satisfies(@NonNull ContextSet contextSet);
default boolean satisfies(@NonNull ContextSet contextSet) {
return satisfies(contextSet, ContextSatisfyMode.AT_LEAST_ONE_VALUE_PER_KEY);
}
/**
* Gets whether this {@link QueryOptions} satisfies the given required
* {@link ContextSet context}.
*
* <p>The {@code defaultContextSatisfyMode} parameter is used if this {@link QueryOptions}
* instance doesn't have the {@link ContextSatisfyMode#KEY option key} set.</p>
*
* @param contextSet the contexts
* @param defaultContextSatisfyMode the default context satisfy mode to use
* @return the result
* @since 5.2
*/
boolean satisfies(@NonNull ContextSet contextSet, @NonNull ContextSatisfyMode defaultContextSatisfyMode);
/**
* Converts this {@link QueryOptions} to a mutable builder.
@@ -208,6 +228,8 @@ public interface QueryOptions {
/**
* Sets the value of the given flag.
*
* <p>By default, all {@link Flag}s are set to true.</p>
*
* @param flag the flag
* @param value the value to set
* @return this builder
@@ -220,6 +242,8 @@ public interface QueryOptions {
* <p>Note that this is a set operation, not append. Existing flags will
* be overridden.</p>
*
* <p>By default, all {@link Flag}s are set to true.</p>
*
* @param flags the flags
* @return this builder
*/
@@ -23,46 +23,37 @@
* SOFTWARE.
*/
package me.lucko.luckperms.common.metastacking;
package net.luckperms.api.query;
import net.luckperms.api.metastacking.MetaStackElement;
import net.luckperms.api.node.types.ChatMetaNode;
import java.util.Optional;
import org.checkerframework.checker.nullness.qual.NonNull;
/**
* Represents a specific entry in a {@link MetaStack}.
* An entry is basically a live/mutable version of a {@link MetaStackElement}.
* A registry providing useful {@link QueryOptions} instances.
*
* @since 5.1
*/
public interface MetaStackEntry {
public interface QueryOptionsRegistry {
/**
* Gets the stack this entry belongs to.
* Gets the default {@link QueryMode#CONTEXTUAL contextual}
* query options.
*
* @return the parent stack
* <p>Prefer using the {@link QueryOptions#defaultContextualOptions()} accessor.</p>
*
* @return the default contextual query options
* @see QueryOptions#defaultContextualOptions()
*/
MetaStack getParentStack();
@NonNull QueryOptions defaultContextualOptions();
/**
* Gets the element this entry was formed form
* Gets the default {@link QueryMode#NON_CONTEXTUAL non contextual}
* query options.
*
* @return the forming element
*/
MetaStackElement getElement();
/**
* Gets the value currently held by this entry.
* <p>Prefer using the {@link QueryOptions#nonContextual()} accessor.</p>
*
* @return the entry
* @return the default non contextual query options
* @see QueryOptions#nonContextual()
*/
Optional<ChatMetaNode<?, ?>> getCurrentValue();
/**
* Accumulates a node to this entry
*
* @param node the node to accumulate
* @return if the node was accepted
*/
boolean accumulateNode(ChatMetaNode<?, ?> node);
@NonNull QueryOptions defaultNonContextualOptions();
}
@@ -32,10 +32,12 @@ import java.util.Objects;
final class SimpleOptionKey<T> implements OptionKey<T> {
private final String name;
private final Class<T> type;
private final int hashCode;
SimpleOptionKey(String name, Class<T> type) {
this.name = name.toLowerCase();
this.type = type;
this.hashCode = Objects.hash(this.name, this.type); // cache hashcode
}
@Override
@@ -64,6 +66,6 @@ final class SimpleOptionKey<T> implements OptionKey<T> {
@Override
public int hashCode() {
return Objects.hash(this.name, this.type);
return this.hashCode;
}
}
@@ -48,6 +48,8 @@ import java.util.function.Consumer;
* it is possible that more {@link DataType}s may be added in the future.
* Ideally the {@link Comparator} implementations should be able to handle these
* smoothly.</p>
*
* @see DataQueryOrderFunction
*/
public enum DataQueryOrder implements Comparator<DataType> {
@@ -60,10 +62,7 @@ public enum DataQueryOrder implements Comparator<DataType> {
if (o1 == o2) {
return 0;
}
return Boolean.compare(
o1 == DataType.TRANSIENT,
o2 == DataType.TRANSIENT
);
return o1 == DataType.TRANSIENT ? 1 : -1;
}
},
@@ -76,10 +75,7 @@ public enum DataQueryOrder implements Comparator<DataType> {
if (o1 == o2) {
return 0;
}
return -Boolean.compare(
o1 == DataType.TRANSIENT,
o2 == DataType.TRANSIENT
);
return o1 == DataType.TRANSIENT ? -1 : 1;
}
};
@@ -44,6 +44,18 @@ public interface DataQueryOrderFunction {
*/
OptionKey<DataQueryOrderFunction> KEY = OptionKey.of("dataqueryorderfunction", DataQueryOrderFunction.class);
/**
* Creates a {@link DataQueryOrderFunction} that always returns the given
* {@code comparator}.
*
* @param comparator the comparator
* @return the data query order function
* @since 5.2
*/
static DataQueryOrderFunction always(Comparator<DataType> comparator) {
return id -> comparator;
}
/**
* Gets the {@link DataQueryOrder} comparator for the given
* {@link PermissionHolder.Identifier holder identifier}.
@@ -0,0 +1,120 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.luckperms.api.query.dataorder;
import net.luckperms.api.model.data.DataType;
import net.luckperms.api.query.QueryOptions;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.Predicate;
/**
* Represents which different {@link DataType}s are used for a query.
*
* <p>The {@link DataTypeFilter} enum simply represents some default
* implementations of the {@link Predicate} required by {@link QueryOptions}
* and the {@link #values(Predicate)} method.</p>
*
* <p>Users are free to implement their own predicate. However, be aware that
* it is possible that more {@link DataType}s may be added in the future.
* Ideally the {@link Predicate} implementations should be able to handle these
* smoothly.</p>
*
* @see DataTypeFilterFunction
* @since 5.2
*/
public enum DataTypeFilter implements Predicate<DataType> {
/**
* A data type filter indicating that all {@link DataType}s should be used.
*/
ALL {
@Override
public boolean test(DataType dataType) {
return true;
}
},
/**
* A data type filter indicating that no {@link DataType}s should be used.
*/
NONE {
@Override
public boolean test(DataType dataType) {
return false;
}
},
/**
* A data type filter indicating that only {@link DataType#NORMAL} should be used.
*/
NORMAL_ONLY {
@Override
public boolean test(DataType dataType) {
return dataType == DataType.NORMAL;
}
},
/**
* A data type filter indicating that only {@link DataType#TRANSIENT} should be used.
*/
TRANSIENT_ONLY {
@Override
public boolean test(DataType dataType) {
return dataType == DataType.TRANSIENT;
}
};
private static final List<DataType> ALL_LIST = Collections.unmodifiableList(Arrays.asList(DataType.NORMAL, DataType.TRANSIENT));
private static final List<DataType> NORMAL_ONLY_LIST = Collections.unmodifiableList(Collections.singletonList(DataType.NORMAL));
private static final List<DataType> TRANSIENT_ONLY_LIST = Collections.unmodifiableList(Collections.singletonList(DataType.TRANSIENT));
/**
* Gets a {@link List} of all {@link DataType}s, filtered by the {@code predicate}.
*
* @param predicate the predicate to filter with
* @return the list of data types
*/
public static @NonNull List<DataType> values(@NonNull Predicate<? super DataType> predicate) {
boolean normal = predicate.test(DataType.NORMAL);
boolean trans = predicate.test(DataType.TRANSIENT);
if (normal && trans) {
return ALL_LIST;
} else if (normal) {
return NORMAL_ONLY_LIST;
} else if (trans) {
return TRANSIENT_ONLY_LIST;
} else {
return Collections.emptyList();
}
}
}
@@ -23,48 +23,47 @@
* SOFTWARE.
*/
package me.lucko.luckperms.common.metastacking;
package net.luckperms.api.query.dataorder;
import net.luckperms.api.metastacking.MetaStackDefinition;
import net.luckperms.api.node.ChatMetaType;
import net.luckperms.api.node.types.ChatMetaNode;
import net.luckperms.api.model.PermissionHolder;
import net.luckperms.api.model.data.DataType;
import net.luckperms.api.query.OptionKey;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.function.Predicate;
/**
* A live stack of {@link MetaStackEntry} instances, formed from a
* {@link MetaStackDefinition}.
* A function that generates a {@link DataTypeFilter} predicate for
* {@link PermissionHolder}s as required during inheritance.
*
* This class is used to construct a formatted string, by accumulating
* nodes to each element.
* @since 5.2
*/
public interface MetaStack {
public interface DataTypeFilterFunction {
/**
* Gets the definition this stack is based upon
*
* @return the definition of the stack
* The {@link OptionKey} for {@link DataTypeFilterFunction}.
*/
MetaStackDefinition getDefinition();
OptionKey<DataTypeFilterFunction> KEY = OptionKey.of("datatypefilterfunction", DataTypeFilterFunction.class);
/**
* Gets the target of this stack
* Creates a {@link DataTypeFilterFunction} that always returns the given
* {@code predicate}.
*
* @return the stack target
* @param predicate the predicate
* @return the data type filter function
*/
ChatMetaType getTargetType();
static DataTypeFilterFunction always(Predicate<DataType> predicate) {
return id -> predicate;
}
/**
* Returns a formatted string, as defined by the {@link MetaStackDefinition},
* using the accumulated elements in the stack.
* Gets the {@link DataTypeFilter} predicate for the given
* {@link PermissionHolder.Identifier holder identifier}.
*
* @return the string output
* @param holderIdentifier the holder identifier
* @return the predicate to use
*/
String toFormattedString();
/**
* Tries to accumulate the given node to all elements in the stack.
*
* @param node the node to accumulate
*/
void accumulateToAll(ChatMetaNode<?, ?> node);
@NonNull Predicate<DataType> getTypeFilter(PermissionHolder.@NonNull Identifier holderIdentifier);
}
@@ -23,42 +23,37 @@
* SOFTWARE.
*/
package net.luckperms.api.query;
package net.luckperms.api.query.meta;
import net.luckperms.api.context.ImmutableContextSet;
import net.luckperms.api.node.types.MetaNode;
import net.luckperms.api.query.OptionKey;
import java.util.EnumSet;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.List;
/**
* Some default {@link QueryOptions} instances.
* A function that selects the {@link MetaNode#getMetaValue() meta value} to be used in queries
* against a given {@link MetaNode#getMetaKey() meta key}.
*
* @since 5.1
*/
final class DefaultQueryOptions {
private DefaultQueryOptions() {
throw new AssertionError();
}
public interface MetaValueSelector {
private static boolean setup = false;
private static final EnumSet<Flag> DEFAULT_FLAGS = EnumSet.allOf(Flag.class);
private static QueryOptions contextual;
private static QueryOptions nonContextual;
/**
* The {@link OptionKey} for {@link MetaValueSelector}.
*/
OptionKey<MetaValueSelector> KEY = OptionKey.of("metavalueselector", MetaValueSelector.class);
private static void setup() {
if (setup) {
return;
}
setup = true;
contextual = QueryOptions.contextual(ImmutableContextSet.empty(), DEFAULT_FLAGS);
nonContextual = QueryOptions.nonContextual(DEFAULT_FLAGS);
}
static QueryOptions contextual() {
setup();
return contextual;
}
static QueryOptions nonContextual() {
setup();
return nonContextual;
}
/**
* Selects the meta value to map to the given key.
*
* <p>The {@code values} list is guaranteed to contain at least 1 element.</p>
*
* @param key the key
* @param values the values, in the order in which they were accumulated.
* @return the selected value
*/
@NonNull String selectValue(@NonNull String key, @NonNull List<String> values);
}
+2 -2
View File
@@ -16,7 +16,7 @@ subprojects {
apply plugin: 'net.minecrell.licenser'
group = 'me.lucko.luckperms'
version = '5.0-SNAPSHOT'
version = '5.2-SNAPSHOT'
sourceCompatibility = 1.8
targetCompatibility = 1.8
@@ -45,7 +45,7 @@ subprojects {
}
project.ext.majorVersion = '5'
project.ext.minorVersion = '0'
project.ext.minorVersion = '2'
project.ext.patchVersion = determinePatchVersion()
project.ext.apiVersion = project.ext.majorVersion + '.' + project.ext.minorVersion
project.ext.fullVersion = project.ext.apiVersion + '.' + project.ext.patchVersion
+5 -4
View File
@@ -3,16 +3,16 @@ plugins {
}
repositories {
maven { url 'https://hub.spigotmc.org/nexus/content/repositories/snapshots/' }
maven { url 'https://papermc.io/repo/repository/maven-public/' }
maven { url 'https://libraries.minecraft.net/' }
}
dependencies {
compile project(':common')
compileOnly 'org.spigotmc:spigot-api:1.14-R0.1-SNAPSHOT'
compileOnly 'net.kyori:text-adapter-bukkit:3.0.2'
compileOnly 'me.lucko:commodore:1.3'
compileOnly 'com.destroystokyo.paper:paper-api:1.15.2-R0.1-SNAPSHOT'
compileOnly 'net.kyori:text-adapter-bukkit:3.0.5'
compileOnly 'me.lucko:commodore:1.7'
compileOnly('net.milkbowl.vault:VaultAPI:1.6') {
exclude(module: 'bukkit')
}
@@ -55,6 +55,7 @@ shadowJar {
relocate 'com.github.benmanes.caffeine', 'me.lucko.luckperms.lib.caffeine'
relocate 'okio', 'me.lucko.luckperms.lib.okio'
relocate 'okhttp3', 'me.lucko.luckperms.lib.okhttp3'
relocate 'net.bytebuddy', 'me.lucko.luckperms.lib.bytebuddy'
relocate 'me.lucko.commodore', 'me.lucko.luckperms.lib.commodore'
relocate 'org.mariadb.jdbc', 'me.lucko.luckperms.lib.mariadb'
relocate 'com.mysql', 'me.lucko.luckperms.lib.mysql'
@@ -0,0 +1,79 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.bukkit;
import com.destroystokyo.paper.event.server.AsyncTabCompleteEvent;
import me.lucko.luckperms.bukkit.util.CommandMapUtil;
import me.lucko.luckperms.common.command.utils.ArgumentTokenizer;
import me.lucko.luckperms.common.sender.Sender;
import org.bukkit.command.Command;
import org.bukkit.command.PluginCommand;
import org.bukkit.event.EventHandler;
import java.util.List;
public class BukkitAsyncCommandExecutor extends BukkitCommandExecutor {
public BukkitAsyncCommandExecutor(LPBukkitPlugin plugin, PluginCommand command) {
super(plugin, command);
}
@EventHandler(ignoreCancelled = true)
public void onAsyncTabComplete(AsyncTabCompleteEvent e) {
if (!e.isCommand()) {
return;
}
String buffer = e.getBuffer();
if (buffer.isEmpty()) {
return;
}
if (buffer.charAt(0) == '/') {
buffer = buffer.substring(1);
}
int firstSpace = buffer.indexOf(' ');
if (firstSpace < 0) {
return;
}
String commandLabel = buffer.substring(0, firstSpace);
Command command = CommandMapUtil.getCommandMap(this.plugin.getBootstrap().getServer()).getCommand(commandLabel);
if (command != this.command) {
return;
}
Sender wrapped = this.plugin.getSenderFactory().wrap(e.getSender());
List<String> arguments = ArgumentTokenizer.TAB_COMPLETE.tokenizeInput(buffer.substring(firstSpace + 1));
List<String> completions = tabCompleteCommand(wrapped, arguments);
e.setCompletions(completions);
e.setHandled(true);
}
}
@@ -25,46 +25,144 @@
package me.lucko.luckperms.bukkit;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import me.lucko.luckperms.bukkit.util.CommandMapUtil;
import me.lucko.luckperms.common.command.CommandManager;
import me.lucko.luckperms.common.command.utils.ArgumentTokenizer;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.sender.Sender;
import org.bukkit.Server;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.command.ConsoleCommandSender;
import org.bukkit.command.PluginCommand;
import org.bukkit.command.TabExecutor;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.server.ServerCommandEvent;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.List;
import java.util.ListIterator;
import java.util.stream.Collectors;
public class BukkitCommandExecutor extends CommandManager implements CommandExecutor, TabExecutor {
private static final Splitter TAB_COMPLETE_ARGUMENT_SPLITTER = Splitter.on(COMMAND_SEPARATOR_PATTERN);
private static final Splitter ARGUMENT_SPLITTER = Splitter.on(COMMAND_SEPARATOR_PATTERN).omitEmptyStrings();
private static final Joiner ARGUMENT_JOINER = Joiner.on(' ');
public class BukkitCommandExecutor extends CommandManager implements CommandExecutor, TabExecutor, Listener {
private static final boolean SELECT_ENTITIES_SUPPORTED;
private final LPBukkitPlugin plugin;
static {
boolean selectEntitiesSupported = false;
try {
Server.class.getMethod("selectEntities", CommandSender.class, String.class);
selectEntitiesSupported = true;
} catch (NoSuchMethodException e) {
// ignore
}
SELECT_ENTITIES_SUPPORTED = selectEntitiesSupported;
}
public BukkitCommandExecutor(LPBukkitPlugin plugin) {
protected final LPBukkitPlugin plugin;
protected final PluginCommand command;
public BukkitCommandExecutor(LPBukkitPlugin plugin, PluginCommand command) {
super(plugin);
this.plugin = plugin;
this.command = command;
}
public void register() {
this.command.setExecutor(this);
this.command.setTabCompleter(this);
this.plugin.getBootstrap().getServer().getPluginManager().registerEvents(this, this.plugin.getBootstrap());
}
@Override
public boolean onCommand(@NonNull CommandSender sender, @NonNull Command command, @NonNull String label, @NonNull String[] args) {
Sender lpSender = this.plugin.getSenderFactory().wrap(sender);
List<String> arguments = stripQuotes(ARGUMENT_SPLITTER.splitToList(ARGUMENT_JOINER.join(args)));
onCommand(lpSender, label, arguments);
Sender wrapped = this.plugin.getSenderFactory().wrap(sender);
List<String> arguments = resolveSelectors(sender, ArgumentTokenizer.EXECUTE.tokenizeInput(args));
executeCommand(wrapped, label, arguments);
return true;
}
@Override
public List<String> onTabComplete(@NonNull CommandSender sender, @NonNull Command command, @NonNull String label, @NonNull String[] args) {
Sender lpSender = this.plugin.getSenderFactory().wrap(sender);
List<String> arguments = stripQuotes(TAB_COMPLETE_ARGUMENT_SPLITTER.splitToList(ARGUMENT_JOINER.join(args)));
Sender wrapped = this.plugin.getSenderFactory().wrap(sender);
List<String> arguments = resolveSelectors(sender, ArgumentTokenizer.TAB_COMPLETE.tokenizeInput(args));
return tabCompleteCommand(wrapped, arguments);
}
return onTabComplete(lpSender, arguments);
// Support LP commands prefixed with a '/' from the console.
@EventHandler(ignoreCancelled = true)
public void onConsoleCommand(ServerCommandEvent e) {
if (!(e.getSender() instanceof ConsoleCommandSender)) {
return;
}
String buffer = e.getCommand();
if (buffer.isEmpty() || buffer.charAt(0) != '/') {
return;
}
buffer = buffer.substring(1);
String commandLabel;
int firstSpace = buffer.indexOf(' ');
if (firstSpace == -1) {
commandLabel = buffer;
} else {
commandLabel = buffer.substring(0, firstSpace);
}
Command command = CommandMapUtil.getCommandMap(this.plugin.getBootstrap().getServer()).getCommand(commandLabel);
if (command != this.command) {
return;
}
e.setCommand(buffer);
}
private List<String> resolveSelectors(CommandSender sender, List<String> args) {
if (!SELECT_ENTITIES_SUPPORTED) {
return args;
}
if (!this.plugin.getConfiguration().get(ConfigKeys.RESOLVE_COMMAND_SELECTORS)) {
return args;
}
for (ListIterator<String> it = args.listIterator(); it.hasNext(); ) {
String arg = it.next();
if (arg.isEmpty() || arg.charAt(0) != '@') {
continue;
}
List<Player> matchedPlayers;
try {
matchedPlayers = this.plugin.getBootstrap().getServer().selectEntities(sender, arg).stream()
.filter(e -> e instanceof Player)
.map(e -> ((Player) e))
.collect(Collectors.toList());
} catch (IllegalArgumentException e) {
this.plugin.getLogger().warn("Error parsing selector '" + arg + "' for " + sender + " executing " + args);
e.printStackTrace();
continue;
}
if (matchedPlayers.isEmpty()) {
continue;
}
if (matchedPlayers.size() > 1) {
this.plugin.getLogger().warn("Error parsing selector '" + arg + "' for " + sender + " executing " + args +
": ambiguous result (more than one player matched) - " + matchedPlayers);
continue;
}
Player player = matchedPlayers.get(0);
it.set(player.getUniqueId().toString());
}
return args;
}
}
@@ -25,7 +25,7 @@
package me.lucko.luckperms.bukkit;
import me.lucko.luckperms.common.config.adapter.ConfigurationAdapter;
import me.lucko.luckperms.common.config.generic.adapter.ConfigurationAdapter;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import org.bukkit.configuration.ConfigurationSection;
@@ -71,8 +71,8 @@ public class BukkitConfigAdapter implements ConfigurationAdapter {
@Override
public List<String> getStringList(String path, List<String> def) {
List<String> ret = this.configuration.getStringList(path);
return ret == null ? def : ret;
List<String> list = this.configuration.getStringList(path);
return list == null ? def : list;
}
@Override
@@ -25,8 +25,6 @@
package me.lucko.luckperms.bukkit;
import me.lucko.luckperms.bukkit.compat.CraftBukkitUtil;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.sender.Sender;
import me.lucko.luckperms.common.sender.SenderFactory;
import me.lucko.luckperms.common.util.TextUtils;
@@ -42,9 +40,8 @@ import org.bukkit.entity.Player;
import java.util.UUID;
public class BukkitSenderFactory extends SenderFactory<CommandSender> {
public BukkitSenderFactory(LuckPermsPlugin plugin) {
public class BukkitSenderFactory extends SenderFactory<LPBukkitPlugin, CommandSender> {
public BukkitSenderFactory(LPBukkitPlugin plugin) {
super(plugin);
}
@@ -78,20 +75,24 @@ public class BukkitSenderFactory extends SenderFactory<CommandSender> {
@Override
protected void sendMessage(CommandSender sender, Component message) {
if (CraftBukkitUtil.isChatCompatible() && sender instanceof Player) {
if (sender instanceof Player) {
TextAdapter.sendComponent(sender, message);
} else {
// Fallback to legacy format
sendMessage(sender, TextUtils.toLegacy(message));
return;
}
// Fallback to legacy format
sendMessage(sender, TextUtils.toLegacy(message));
}
@Override
protected Tristate getPermissionValue(CommandSender sender, String node) {
boolean isSet = sender.isPermissionSet(node);
boolean val = sender.hasPermission(node);
return !isSet ? val ? Tristate.TRUE : Tristate.UNDEFINED : Tristate.of(val);
if (sender.hasPermission(node)) {
return Tristate.TRUE;
} else if (sender.isPermissionSet(node)) {
return Tristate.FALSE;
} else {
return Tristate.UNDEFINED;
}
}
@Override
@@ -99,6 +100,11 @@ public class BukkitSenderFactory extends SenderFactory<CommandSender> {
return sender.hasPermission(node);
}
@Override
protected void performCommand(CommandSender sender, String command) {
getPlugin().getBootstrap().getServer().dispatchCommand(sender, command);
}
private static final class SyncMessengerAgent implements Runnable {
private final CommandSender sender;
private final String message;
@@ -25,7 +25,7 @@
package me.lucko.luckperms.bukkit;
import me.lucko.luckperms.bukkit.compat.NullSafeConsoleCommandSender;
import me.lucko.luckperms.bukkit.util.NullSafeConsoleCommandSender;
import me.lucko.luckperms.common.dependencies.classloader.PluginClassLoader;
import me.lucko.luckperms.common.dependencies.classloader.ReflectionClassLoader;
import me.lucko.luckperms.common.plugin.bootstrap.LuckPermsBootstrap;
@@ -39,13 +39,18 @@ import org.bukkit.Server;
import org.bukkit.command.ConsoleCommandSender;
import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.nio.file.Path;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.stream.Stream;
/**
* Bootstrap plugin for LuckPerms running on Bukkit.
@@ -81,12 +86,13 @@ public class LPBukkitBootstrap extends JavaPlugin implements LuckPermsBootstrap
/**
* The time when the plugin was enabled
*/
private long startTime;
private Instant startTime;
// load/enable latches
private final CountDownLatch loadLatch = new CountDownLatch(1);
private final CountDownLatch enableLatch = new CountDownLatch(1);
private boolean serverStarting = true;
private boolean serverStopping = false;
// if the plugin has been loaded on an incompatible version
private boolean incompatibleVersion = false;
@@ -149,7 +155,9 @@ public class LPBukkitBootstrap extends JavaPlugin implements LuckPermsBootstrap
return;
}
this.startTime = System.currentTimeMillis();
this.serverStarting = true;
this.serverStopping = false;
this.startTime = Instant.now();
try {
this.plugin.enable();
@@ -166,8 +174,8 @@ public class LPBukkitBootstrap extends JavaPlugin implements LuckPermsBootstrap
return;
}
this.serverStopping = true;
this.plugin.disable();
this.serverStarting = true;
}
@Override
@@ -184,6 +192,10 @@ public class LPBukkitBootstrap extends JavaPlugin implements LuckPermsBootstrap
return this.serverStarting;
}
public boolean isServerStopping() {
return this.serverStopping;
}
// provide information about the plugin
@Override
@@ -192,7 +204,7 @@ public class LPBukkitBootstrap extends JavaPlugin implements LuckPermsBootstrap
}
@Override
public long getStartupTime() {
public Instant getStartupTime() {
return this.startTime;
}
@@ -245,13 +257,23 @@ public class LPBukkitBootstrap extends JavaPlugin implements LuckPermsBootstrap
}
@Override
public Stream<String> getPlayerList() {
return getServer().getOnlinePlayers().stream().map(Player::getName);
public Collection<String> getPlayerList() {
Collection<? extends Player> players = getServer().getOnlinePlayers();
List<String> list = new ArrayList<>(players.size());
for (Player player : players) {
list.add(player.getName());
}
return list;
}
@Override
public Stream<UUID> getOnlinePlayers() {
return getServer().getOnlinePlayers().stream().map(Player::getUniqueId);
public Collection<UUID> getOnlinePlayers() {
Collection<? extends Player> players = getServer().getOnlinePlayers();
List<UUID> list = new ArrayList<>(players.size());
for (Player player : players) {
list.add(player.getUniqueId());
}
return list;
}
@Override
@@ -260,6 +282,19 @@ public class LPBukkitBootstrap extends JavaPlugin implements LuckPermsBootstrap
return player != null && player.isOnline();
}
@Override
public @Nullable String identifyClassLoader(ClassLoader classLoader) throws ReflectiveOperationException {
Class<?> pluginClassLoaderClass = Class.forName("org.bukkit.plugin.java.PluginClassLoader");
if (pluginClassLoaderClass.isInstance(classLoader)) {
Method getPluginMethod = pluginClassLoaderClass.getDeclaredMethod("getPlugin");
getPluginMethod.setAccessible(true);
JavaPlugin plugin = (JavaPlugin) getPluginMethod.invoke(classLoader);
return plugin.getName();
}
return null;
}
private static boolean checkIncompatibleVersion() {
try {
Class.forName("com.google.gson.JsonElement");
@@ -25,29 +25,31 @@
package me.lucko.luckperms.bukkit;
import me.lucko.luckperms.bukkit.brigadier.LuckPermsBrigadier;
import me.lucko.luckperms.bukkit.calculator.BukkitCalculatorFactory;
import me.lucko.luckperms.bukkit.compat.LuckPermsBrigadier;
import me.lucko.luckperms.bukkit.context.BukkitContextManager;
import me.lucko.luckperms.bukkit.context.WorldCalculator;
import me.lucko.luckperms.bukkit.inject.permissible.LPPermissible;
import me.lucko.luckperms.bukkit.inject.permissible.LuckPermsPermissible;
import me.lucko.luckperms.bukkit.inject.permissible.PermissibleInjector;
import me.lucko.luckperms.bukkit.inject.permissible.PermissibleMonitoringInjector;
import me.lucko.luckperms.bukkit.inject.server.InjectorDefaultsMap;
import me.lucko.luckperms.bukkit.inject.server.InjectorPermissionMap;
import me.lucko.luckperms.bukkit.inject.server.InjectorSubscriptionMap;
import me.lucko.luckperms.bukkit.inject.server.LPDefaultsMap;
import me.lucko.luckperms.bukkit.inject.server.LPPermissionMap;
import me.lucko.luckperms.bukkit.inject.server.LPSubscriptionMap;
import me.lucko.luckperms.bukkit.inject.server.LuckPermsDefaultsMap;
import me.lucko.luckperms.bukkit.inject.server.LuckPermsPermissionMap;
import me.lucko.luckperms.bukkit.inject.server.LuckPermsSubscriptionMap;
import me.lucko.luckperms.bukkit.listeners.BukkitAutoOpListener;
import me.lucko.luckperms.bukkit.listeners.BukkitCommandListUpdater;
import me.lucko.luckperms.bukkit.listeners.BukkitConnectionListener;
import me.lucko.luckperms.bukkit.listeners.BukkitPlatformListener;
import me.lucko.luckperms.bukkit.messaging.BukkitMessagingFactory;
import me.lucko.luckperms.bukkit.util.PluginManagerUtil;
import me.lucko.luckperms.bukkit.vault.VaultHookManager;
import me.lucko.luckperms.common.api.LuckPermsApiProvider;
import me.lucko.luckperms.common.api.implementation.ApiUser;
import me.lucko.luckperms.common.calculator.CalculatorFactory;
import me.lucko.luckperms.common.command.access.CommandPermission;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.config.adapter.ConfigurationAdapter;
import me.lucko.luckperms.common.config.generic.adapter.ConfigurationAdapter;
import me.lucko.luckperms.common.dependencies.Dependency;
import me.lucko.luckperms.common.event.AbstractEventBus;
import me.lucko.luckperms.common.messaging.MessagingFactory;
@@ -62,7 +64,6 @@ import me.lucko.luckperms.common.tasks.CacheHousekeepingTask;
import me.lucko.luckperms.common.tasks.ExpireTemporaryTask;
import net.luckperms.api.LuckPerms;
import net.luckperms.api.event.user.UserDataRecalculateEvent;
import net.luckperms.api.query.QueryOptions;
import org.bukkit.OfflinePlayer;
@@ -74,7 +75,8 @@ import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.ServicePriority;
import java.io.File;
import java.util.Map;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.TimeUnit;
@@ -93,9 +95,9 @@ public class LPBukkitPlugin extends AbstractLuckPermsPlugin {
private StandardGroupManager groupManager;
private StandardTrackManager trackManager;
private BukkitContextManager contextManager;
private LPSubscriptionMap subscriptionMap;
private LPPermissionMap permissionMap;
private LPDefaultsMap defaultPermissionMap;
private LuckPermsSubscriptionMap subscriptionMap;
private LuckPermsPermissionMap permissionMap;
private LuckPermsDefaultsMap defaultPermissionMap;
private VaultHookManager vaultHookManager = null;
public LPBukkitPlugin(LPBukkitBootstrap bootstrap) {
@@ -112,15 +114,6 @@ public class LPBukkitPlugin extends AbstractLuckPermsPlugin {
this.senderFactory = new BukkitSenderFactory(this);
}
private static boolean isBrigadierSupported() {
try {
Class.forName("com.mojang.brigadier.CommandDispatcher");
return true;
} catch (ClassNotFoundException var1) {
return false;
}
}
@Override
protected Set<Dependency> getGlobalDependencies() {
Set<Dependency> dependencies = super.getGlobalDependencies();
@@ -150,15 +143,24 @@ public class LPBukkitPlugin extends AbstractLuckPermsPlugin {
@Override
protected void registerCommands() {
this.commandManager = new BukkitCommandExecutor(this);
PluginCommand cmd = this.bootstrap.getCommand("luckperms");
cmd.setExecutor(this.commandManager);
cmd.setTabCompleter(this.commandManager);
PluginCommand command = this.bootstrap.getCommand("luckperms");
if (command == null) {
getLogger().severe("Unable to register /luckperms command with the server");
return;
}
if (isAsyncTabCompleteSupported()) {
this.commandManager = new BukkitAsyncCommandExecutor(this, command);
} else {
this.commandManager = new BukkitCommandExecutor(this, command);
}
this.commandManager.register();
// setup brigadier
if (isBrigadierSupported()) {
if (isBrigadierSupported() && getConfiguration().get(ConfigKeys.REGISTER_COMMAND_LIST_DATA)) {
try {
LuckPermsBrigadier.register(this, cmd);
LuckPermsBrigadier.register(this, command);
} catch (Exception e) {
if (!(e instanceof RuntimeException && e.getMessage().contains("not supported by the server"))) {
e.printStackTrace();
@@ -182,16 +184,19 @@ public class LPBukkitPlugin extends AbstractLuckPermsPlugin {
@Override
protected void setupContextManager() {
this.contextManager = new BukkitContextManager(this);
this.contextManager.registerCalculator(new WorldCalculator(this));
WorldCalculator worldCalculator = new WorldCalculator(this);
this.bootstrap.getServer().getPluginManager().registerEvents(worldCalculator, this.bootstrap);
this.contextManager.registerCalculator(worldCalculator);
}
@Override
protected void setupPlatformHooks() {
// inject our own custom permission maps
Runnable[] injectors = new Runnable[]{
new InjectorSubscriptionMap(this),
new InjectorPermissionMap(this),
new InjectorDefaultsMap(this),
new InjectorSubscriptionMap(this)::inject,
new InjectorPermissionMap(this)::inject,
new InjectorDefaultsMap(this)::inject,
new PermissibleMonitoringInjector(this, PermissibleMonitoringInjector.Mode.INJECT)
};
@@ -203,12 +208,26 @@ public class LPBukkitPlugin extends AbstractLuckPermsPlugin {
this.bootstrap.getServer().getScheduler().runTaskLaterAsynchronously(this.bootstrap, injector, 1);
}
/*
* This is an unfortunate solution to a problem which shouldn't even exist. As of Spigot 1.15,
* the way LP establishes it's load order relative to Vault triggers a dependency warning.
* This is a workaround to prevent that from showing, since at the moment, there is nothing I
* can reasonably do to improve this handling in LP without breaking plugins which use/obtain
* Vault in their onEnable without depending on us.
*
* Noteworthy discussion here:
* - https://github.com/lucko/LuckPerms/issues/1959
* - https://hub.spigotmc.org/jira/browse/SPIGOT-5546
* - https://github.com/PaperMC/Paper/pull/3509
*/
PluginManagerUtil.injectDependency(this.bootstrap.getServer().getPluginManager(), this.bootstrap.getName(), "Vault");
// Provide vault support
tryVaultHook(false);
}
@Override
protected AbstractEventBus provideEventBus(LuckPermsApiProvider apiProvider) {
protected AbstractEventBus<?> provideEventBus(LuckPermsApiProvider apiProvider) {
return new BukkitEventBus(this, apiProvider);
}
@@ -266,11 +285,12 @@ public class LPBukkitPlugin extends AbstractLuckPermsPlugin {
// register autoop listener
if (getConfiguration().get(ConfigKeys.AUTO_OP)) {
getApiProvider().getEventBus().subscribe(UserDataRecalculateEvent.class, event -> {
User user = ApiUser.cast(event.getUser());
Optional<Player> player = getBootstrap().getPlayer(user.getUniqueId());
player.ifPresent(p -> refreshAutoOp(p, false));
});
getApiProvider().getEventBus().subscribe(new BukkitAutoOpListener(this));
}
// register bukkit command list updater
if (getConfiguration().get(ConfigKeys.UPDATE_CLIENT_COMMAND_LIST) && BukkitCommandListUpdater.isSupported()) {
getApiProvider().getEventBus().subscribe(new BukkitCommandListUpdater(this));
}
// Load any online users (in the case of a reload)
@@ -281,7 +301,7 @@ public class LPBukkitPlugin extends AbstractLuckPermsPlugin {
if (user != null) {
this.bootstrap.getScheduler().executeSync(() -> {
try {
LPPermissible lpPermissible = new LPPermissible(player, user, this);
LuckPermsPermissible lpPermissible = new LuckPermsPermissible(player, user, this);
PermissibleInjector.inject(player, lpPermissible);
} catch (Throwable t) {
t.printStackTrace();
@@ -317,9 +337,9 @@ public class LPBukkitPlugin extends AbstractLuckPermsPlugin {
}
// uninject custom maps
InjectorSubscriptionMap.uninject();
InjectorPermissionMap.uninject();
InjectorDefaultsMap.uninject();
new InjectorSubscriptionMap(this).uninject();
new InjectorPermissionMap(this).uninject();
new InjectorDefaultsMap(this).uninject();
new PermissibleMonitoringInjector(this, PermissibleMonitoringInjector.Mode.UNINJECT).run();
// unhook vault
@@ -328,28 +348,6 @@ public class LPBukkitPlugin extends AbstractLuckPermsPlugin {
}
}
public void refreshAutoOp(Player player, boolean callerIsSync) {
if (!getConfiguration().get(ConfigKeys.AUTO_OP)) {
return;
}
User user = getUserManager().getIfLoaded(player.getUniqueId());
boolean value;
if (user != null) {
Map<String, Boolean> permData = user.getCachedData().getPermissionData(this.contextManager.getQueryOptions(player)).getPermissionMap();
value = permData.getOrDefault("luckperms.autoop", false);
} else {
value = false;
}
if (callerIsSync) {
player.setOp(value);
} else {
this.bootstrap.getScheduler().executeSync(() -> player.setOp(value));
}
}
private File resolveConfig() {
File configFile = new File(this.bootstrap.getDataFolder(), "config.yml");
if (!configFile.exists()) {
@@ -359,6 +357,23 @@ public class LPBukkitPlugin extends AbstractLuckPermsPlugin {
return configFile;
}
private static boolean classExists(String className) {
try {
Class.forName(className);
return true;
} catch (ClassNotFoundException var1) {
return false;
}
}
private static boolean isBrigadierSupported() {
return classExists("com.mojang.brigadier.CommandDispatcher");
}
private static boolean isAsyncTabCompleteSupported() {
return classExists("com.destroystokyo.paper.event.server.AsyncTabCompleteEvent");
}
@Override
public Optional<QueryOptions> getQueryOptionsForUser(User user) {
return this.bootstrap.getPlayer(user.getUniqueId()).map(player -> this.contextManager.getQueryOptions(player));
@@ -366,9 +381,10 @@ public class LPBukkitPlugin extends AbstractLuckPermsPlugin {
@Override
public Stream<Sender> getOnlineSenders() {
List<Player> players = new ArrayList<>(this.bootstrap.getServer().getOnlinePlayers());
return Stream.concat(
Stream.of(getConsoleSender()),
this.bootstrap.getServer().getOnlinePlayers().stream().map(p -> getSenderFactory().wrap(p))
players.stream().map(p -> getSenderFactory().wrap(p))
);
}
@@ -411,27 +427,27 @@ public class LPBukkitPlugin extends AbstractLuckPermsPlugin {
return this.contextManager;
}
public LPSubscriptionMap getSubscriptionMap() {
public LuckPermsSubscriptionMap getSubscriptionMap() {
return this.subscriptionMap;
}
public void setSubscriptionMap(LPSubscriptionMap subscriptionMap) {
public void setSubscriptionMap(LuckPermsSubscriptionMap subscriptionMap) {
this.subscriptionMap = subscriptionMap;
}
public LPPermissionMap getPermissionMap() {
public LuckPermsPermissionMap getPermissionMap() {
return this.permissionMap;
}
public void setPermissionMap(LPPermissionMap permissionMap) {
public void setPermissionMap(LuckPermsPermissionMap permissionMap) {
this.permissionMap = permissionMap;
}
public LPDefaultsMap getDefaultPermissionMap() {
public LuckPermsDefaultsMap getDefaultPermissionMap() {
return this.defaultPermissionMap;
}
public void setDefaultPermissionMap(LPDefaultsMap defaultPermissionMap) {
public void setDefaultPermissionMap(LuckPermsDefaultsMap defaultPermissionMap) {
this.defaultPermissionMap = defaultPermissionMap;
}
@@ -0,0 +1,61 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.bukkit.brigadier;
import com.mojang.brigadier.tree.LiteralCommandNode;
import me.lucko.commodore.Commodore;
import me.lucko.commodore.CommodoreProvider;
import me.lucko.commodore.file.CommodoreFileFormat;
import me.lucko.luckperms.bukkit.LPBukkitPlugin;
import me.lucko.luckperms.common.sender.Sender;
import org.bukkit.command.Command;
import java.io.InputStream;
/**
* Registers LuckPerms command data to brigadier using {@link Commodore}.
*/
public final class LuckPermsBrigadier {
private LuckPermsBrigadier() {}
public static void register(LPBukkitPlugin plugin, Command pluginCommand) throws Exception {
Commodore commodore = CommodoreProvider.getCommodore(plugin.getBootstrap());
try (InputStream is = plugin.getBootstrap().getResourceStream("luckperms.commodore")) {
if (is == null) {
throw new Exception("Brigadier command data missing from jar");
}
LiteralCommandNode<?> commandNode = CommodoreFileFormat.parse(is);
commodore.register(pluginCommand, commandNode, player -> {
Sender playerAsSender = plugin.getSenderFactory().wrap(player);
return plugin.getCommandManager().hasPermissionForAny(playerAsSender);
});
}
}
}
@@ -35,6 +35,7 @@ import me.lucko.luckperms.common.calculator.PermissionCalculator;
import me.lucko.luckperms.common.calculator.processor.MapProcessor;
import me.lucko.luckperms.common.calculator.processor.PermissionProcessor;
import me.lucko.luckperms.common.calculator.processor.RegexProcessor;
import me.lucko.luckperms.common.calculator.processor.SpongeWildcardProcessor;
import me.lucko.luckperms.common.calculator.processor.WildcardProcessor;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.model.HolderType;
@@ -66,8 +67,17 @@ public class BukkitCalculatorFactory implements CalculatorFactory {
processors.add(new WildcardProcessor());
}
if (this.plugin.getConfiguration().get(ConfigKeys.APPLY_BUKKIT_DEFAULT_PERMISSIONS) && metadata.getHolderType() == HolderType.USER) {
processors.add(new DefaultsProcessor(this.plugin, queryOptions.option(BukkitContextManager.OP_OPTION).orElse(false)));
if (this.plugin.getConfiguration().get(ConfigKeys.APPLYING_WILDCARDS_SPONGE)) {
processors.add(new SpongeWildcardProcessor());
}
boolean op = queryOptions.option(BukkitContextManager.OP_OPTION).orElse(false);
if (metadata.getHolderType() == HolderType.USER && this.plugin.getConfiguration().get(ConfigKeys.APPLY_BUKKIT_DEFAULT_PERMISSIONS)) {
processors.add(new DefaultsProcessor(this.plugin, op));
}
if (op) {
processors.add(new OpProcessor());
}
return new PermissionCalculator(this.plugin, metadata, processors.build());
@@ -0,0 +1,44 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.bukkit.calculator;
import me.lucko.luckperms.common.calculator.processor.PermissionProcessor;
import me.lucko.luckperms.common.calculator.result.TristateResult;
import net.luckperms.api.util.Tristate;
/**
* Permission Processor which is added for opped users, to simply return true if
* no other processors match.
*/
public class OpProcessor implements PermissionProcessor {
private static final TristateResult TRUE_RESULT = new TristateResult.Factory(OpProcessor.class).result(Tristate.TRUE);
@Override
public TristateResult hasPermission(String permission) {
return TRUE_RESULT;
}
}
@@ -1,174 +0,0 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.bukkit.compat;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.mojang.brigadier.arguments.ArgumentType;
import com.mojang.brigadier.arguments.BoolArgumentType;
import com.mojang.brigadier.arguments.IntegerArgumentType;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.builder.ArgumentBuilder;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
import me.lucko.commodore.Commodore;
import me.lucko.commodore.CommodoreProvider;
import me.lucko.luckperms.bukkit.LPBukkitPlugin;
import me.lucko.luckperms.common.sender.Sender;
import org.bukkit.command.Command;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerCommandSendEvent;
import org.bukkit.plugin.PluginManager;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.zip.GZIPInputStream;
public class LuckPermsBrigadier {
public static void register(LPBukkitPlugin plugin, Command pluginCommand) throws Exception {
// register completions with commodore
Commodore commodore = CommodoreProvider.getCommodore(plugin.getBootstrap());
try (InputStream is = plugin.getBootstrap().getResourceStream("luckperms-brigadier.json.gz")) {
if (is == null) {
throw new Exception("Brigadier command data missing from jar!");
}
try (BufferedReader reader = new BufferedReader(new InputStreamReader(new GZIPInputStream(is), StandardCharsets.UTF_8))) {
JsonObject data = new JsonParser().parse(reader).getAsJsonObject();
LiteralArgumentBuilder command = deserializeLiteral(data);
commodore.register(pluginCommand, command);
}
}
// add event listener to prevent completions from being send to players without permission
// to use any LP commands.
PluginManager pluginManager = plugin.getBootstrap().getServer().getPluginManager();
pluginManager.registerEvents(new PermissionListener(plugin, pluginCommand), plugin.getBootstrap());
}
private static final class PermissionListener implements Listener {
private final LPBukkitPlugin plugin;
private final List<String> aliases;
private PermissionListener(LPBukkitPlugin plugin, Command pluginCommand) {
this.plugin = plugin;
this.aliases = Commodore.getAliases(pluginCommand).stream()
.flatMap(alias -> Stream.of(alias, "minecraft:" + alias))
.collect(Collectors.toList());
}
@EventHandler
public void onCommandSend(PlayerCommandSendEvent e) {
Sender playerAsSender = this.plugin.getSenderFactory().wrap(e.getPlayer());
if (!this.plugin.getCommandManager().hasPermissionForAny(playerAsSender)) {
e.getCommands().removeAll(this.aliases);
}
}
}
private static ArgumentBuilder deserialize(JsonObject data) {
String type = data.get("type").getAsString();
switch (type) {
case "literal": {
return deserializeLiteral(data);
}
case "argument": {
return deserializeArgument(data);
}
default:
throw new IllegalArgumentException("type: " + type);
}
}
private static LiteralArgumentBuilder deserializeLiteral(JsonObject data) {
String name = data.get("name").getAsString();
LiteralArgumentBuilder arg = LiteralArgumentBuilder.literal(name);
return deserializeChildren(data, arg);
}
private static RequiredArgumentBuilder deserializeArgument(JsonObject data) {
String name = data.get("name").getAsString();
ArgumentType argumentType = deserializeArgumentType(data);
//noinspection unchecked
RequiredArgumentBuilder arg = RequiredArgumentBuilder.argument(name, argumentType);
return deserializeChildren(data, arg);
}
private static ArgumentType deserializeArgumentType(JsonObject data) {
String parser = data.get("parser").getAsString();
String properties = null;
if (data.has("properties")) {
properties = data.get("properties").getAsString();
}
switch (parser) {
case "brigadier:string": {
Objects.requireNonNull(properties, "string properties");
switch (properties) {
case "SINGLE_WORD":
return StringArgumentType.word();
case "QUOTABLE_PHRASE":
return StringArgumentType.string();
case "GREEDY_PHRASE":
return StringArgumentType.greedyString();
default:
throw new IllegalArgumentException("string property: " + properties);
}
}
case "brigadier:bool":
return BoolArgumentType.bool();
case "brigadier:integer":
return IntegerArgumentType.integer();
default:
throw new IllegalArgumentException("parser: " + parser);
}
}
private static <T extends ArgumentBuilder> T deserializeChildren(JsonObject data, T builder) {
if (data.has("children")) {
JsonArray children = data.get("children").getAsJsonArray();
for (JsonElement child : children) {
//noinspection unchecked
builder.then(deserialize(child.getAsJsonObject()));
}
}
return builder;
}
}
@@ -32,7 +32,6 @@ import me.lucko.luckperms.common.cache.LoadingMap;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.context.ContextManager;
import me.lucko.luckperms.common.context.QueryOptionsCache;
import me.lucko.luckperms.common.context.QueryOptionsSupplier;
import me.lucko.luckperms.common.util.CaffeineFactory;
import net.luckperms.api.context.ImmutableContextSet;
@@ -41,9 +40,10 @@ import net.luckperms.api.query.QueryOptions;
import org.bukkit.entity.Player;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
public class BukkitContextManager extends ContextManager<Player> {
public class BukkitContextManager extends ContextManager<Player, Player> {
public static final OptionKey<Boolean> OP_OPTION = OptionKey.of("op", Boolean.class);
@@ -62,7 +62,7 @@ public class BukkitContextManager extends ContextManager<Player> {
});
public BukkitContextManager(LPBukkitPlugin plugin) {
super(plugin, Player.class);
super(plugin, Player.class, Player.class);
}
public void onPlayerQuit(Player player) {
@@ -70,7 +70,12 @@ public class BukkitContextManager extends ContextManager<Player> {
}
@Override
public QueryOptionsSupplier getCacheFor(Player subject) {
public UUID getUniqueId(Player player) {
return player.getUniqueId();
}
@Override
public QueryOptionsCache<Player> getCacheFor(Player subject) {
if (subject == null) {
throw new NullPointerException("subject");
}
@@ -83,11 +88,7 @@ public class BukkitContextManager extends ContextManager<Player> {
}
@Override
public void invalidateCache(Player subject) {
if (subject == null) {
throw new NullPointerException("subject");
}
protected void invalidateCache(Player subject) {
QueryOptionsCache<Player> cache = this.onlineSubjectCaches.getIfPresent(subject);
if (cache != null) {
cache.invalidate();
@@ -101,7 +102,7 @@ public class BukkitContextManager extends ContextManager<Player> {
@Override
public QueryOptions formQueryOptions(Player subject, ImmutableContextSet contextSet) {
QueryOptions.Builder queryOptions = this.plugin.getConfiguration().get(ConfigKeys.GLOBAL_CONTEXTS).toBuilder();
QueryOptions.Builder queryOptions = this.plugin.getConfiguration().get(ConfigKeys.GLOBAL_QUERY_OPTIONS).toBuilder();
if (subject.isOp()) {
queryOptions.option(OP_OPTION, true);
}
@@ -27,7 +27,9 @@ package me.lucko.luckperms.bukkit.context;
import me.lucko.luckperms.bukkit.LPBukkitPlugin;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.context.contextset.ImmutableContextSetImpl;
import net.luckperms.api.context.Context;
import net.luckperms.api.context.ContextCalculator;
import net.luckperms.api.context.ContextConsumer;
import net.luckperms.api.context.ContextSet;
@@ -36,13 +38,17 @@ import net.luckperms.api.context.ImmutableContextSet;
import org.bukkit.World;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerChangedWorldEvent;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class WorldCalculator implements ContextCalculator<Player> {
public class WorldCalculator implements ContextCalculator<Player>, Listener {
private final LPBukkitPlugin plugin;
public WorldCalculator(LPBukkitPlugin plugin) {
@@ -53,7 +59,9 @@ public class WorldCalculator implements ContextCalculator<Player> {
public void calculate(@NonNull Player subject, @NonNull ContextConsumer consumer) {
Set<String> seen = new HashSet<>();
String world = subject.getWorld().getName().toLowerCase();
while (seen.add(world)) {
// seems like world names can sometimes be the empty string
// see: https://github.com/lucko/LuckPerms/issues/2119
while (Context.isValidValue(world) && seen.add(world)) {
consumer.accept(DefaultContextKeys.WORLD_KEY, world);
world = this.plugin.getConfiguration().get(ConfigKeys.WORLD_REWRITES).getOrDefault(world, world).toLowerCase();
}
@@ -62,10 +70,18 @@ public class WorldCalculator implements ContextCalculator<Player> {
@Override
public ContextSet estimatePotentialContexts() {
List<World> worlds = this.plugin.getBootstrap().getServer().getWorlds();
ImmutableContextSet.Builder builder = ImmutableContextSet.builder();
ImmutableContextSet.Builder builder = new ImmutableContextSetImpl.BuilderImpl();
for (World world : worlds) {
builder.add(DefaultContextKeys.WORLD_KEY, world.getName().toLowerCase());
String name = world.getName().toLowerCase();
if (Context.isValidValue(name)) {
builder.add(DefaultContextKeys.WORLD_KEY, name);
}
}
return builder.build();
}
@EventHandler(priority = EventPriority.LOWEST)
public void onWorldChange(PlayerChangedWorldEvent e) {
this.plugin.getContextManager().signalContextUpdate(e.getPlayer());
}
}
@@ -1,79 +0,0 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.bukkit.inject.dummy;
import org.bukkit.Server;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.generator.ChunkGenerator;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginDescriptionFile;
import org.bukkit.plugin.PluginLoader;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.io.File;
import java.io.InputStream;
import java.util.List;
import java.util.logging.Logger;
/**
* Dummy plugin instance
*/
public class DummyPlugin implements Plugin {
public static final DummyPlugin INSTANCE = new DummyPlugin();
private DummyPlugin() {
}
@Override
public boolean isEnabled() {
return true;
}
@Override public @NonNull File getDataFolder() { return null; }
@Override public @NonNull PluginDescriptionFile getDescription() { return null; }
@Override public @NonNull FileConfiguration getConfig() { return null; }
@Override public InputStream getResource(@NonNull String s) { return null; }
@Override public void saveConfig() {}
@Override public void saveDefaultConfig() {}
@Override public void saveResource(@NonNull String s, boolean b) {}
@Override public void reloadConfig() {}
@Override public @NonNull PluginLoader getPluginLoader() { return null; }
@Override public @NonNull Server getServer() { return null; }
@Override public void onDisable() {}
@Override public void onLoad() {}
@Override public void onEnable() {}
@Override public boolean isNaggable() { return false; }
@Override public void setNaggable(boolean b) {}
@Override public ChunkGenerator getDefaultWorldGenerator(@NonNull String s, String s1) { return null; }
@Override public @NonNull Logger getLogger() { return null; }
@Override public @NonNull String getName() { return null; }
@Override public boolean onCommand(@NonNull CommandSender commandSender, @NonNull Command command, @NonNull String s, @NonNull String[] strings) { return false; }
@Override public List<String> onTabComplete(@NonNull CommandSender commandSender, @NonNull Command command, @NonNull String s, @NonNull String[] strings) { return null; }
}
@@ -23,7 +23,9 @@
* SOFTWARE.
*/
package me.lucko.luckperms.bukkit.inject.dummy;
package me.lucko.luckperms.bukkit.inject.permissible;
import me.lucko.luckperms.common.util.EmptyCollections;
import org.bukkit.permissions.PermissibleBase;
import org.bukkit.permissions.Permission;
@@ -33,14 +35,8 @@ import org.bukkit.plugin.Plugin;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.Function;
public class DummyPermissibleBase extends PermissibleBase {
private static final Field ATTACHMENTS_FIELD;
@@ -61,13 +57,9 @@ public class DummyPermissibleBase extends PermissibleBase {
public static void copyFields(PermissibleBase from, PermissibleBase to) {
try {
ATTACHMENTS_FIELD.set(to, ATTACHMENTS_FIELD.get(from));
} catch (Exception e) {
// ignore
}
try {
PERMISSIONS_FIELD.set(to, PERMISSIONS_FIELD.get(from));
} catch (Exception e) {
// ignore
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
@@ -76,29 +68,11 @@ public class DummyPermissibleBase extends PermissibleBase {
private DummyPermissibleBase() {
super(null);
// we want the singleton dummy attachment to be stateless
// the behaviour of this class is to fail silently, so we can't use Collections.emptyX
try {
ATTACHMENTS_FIELD.set(this, new ArrayList<PermissionAttachment>(){
@Override public boolean add(PermissionAttachment permissionAttachment) { return true; }
@Override public void add(int index, PermissionAttachment element) { }
@Override public boolean addAll(Collection<? extends PermissionAttachment> c) { return true; }
@Override public boolean addAll(int index, Collection<? extends PermissionAttachment> c) { return true; }
});
ATTACHMENTS_FIELD.set(this, EmptyCollections.list());
PERMISSIONS_FIELD.set(this, EmptyCollections.map());
} catch (Exception e) {
// ignore
}
try {
PERMISSIONS_FIELD.set(this, new HashMap<String, PermissionAttachmentInfo>() {
@Override public PermissionAttachmentInfo put(String key, PermissionAttachmentInfo value) { return null; }
@Override public void putAll(Map<? extends String, ? extends PermissionAttachmentInfo> m) { }
@Override public PermissionAttachmentInfo putIfAbsent(String key, PermissionAttachmentInfo value) { return null; }
@Override public PermissionAttachmentInfo compute(String key, BiFunction<? super String, ? super PermissionAttachmentInfo, ? extends PermissionAttachmentInfo> remappingFunction) { return null; }
@Override public PermissionAttachmentInfo computeIfPresent(String key, BiFunction<? super String, ? super PermissionAttachmentInfo, ? extends PermissionAttachmentInfo> remappingFunction) { return null; }
@Override public PermissionAttachmentInfo computeIfAbsent(String key, Function<? super String, ? extends PermissionAttachmentInfo> mappingFunction) { return null; }
});
} catch (Exception e) {
// ignore
e.printStackTrace();
}
}
@@ -26,16 +26,18 @@
package me.lucko.luckperms.bukkit.inject.permissible;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import me.lucko.luckperms.bukkit.LPBukkitPlugin;
import me.lucko.luckperms.bukkit.calculator.DefaultsProcessor;
import me.lucko.luckperms.bukkit.calculator.OpProcessor;
import me.lucko.luckperms.common.calculator.result.TristateResult;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.context.QueryOptionsSupplier;
import me.lucko.luckperms.common.context.QueryOptionsCache;
import me.lucko.luckperms.common.model.User;
import me.lucko.luckperms.common.util.ImmutableCollectors;
import me.lucko.luckperms.common.verbose.event.PermissionCheckEvent;
import net.luckperms.api.query.QueryOptions;
import net.luckperms.api.util.Tristate;
import org.bukkit.entity.Player;
@@ -51,6 +53,7 @@ import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
@@ -70,7 +73,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
* This class is **thread safe**. This means that when LuckPerms is installed on the server,
* is is safe to call Player#hasPermission asynchronously.
*/
public class LPPermissible extends PermissibleBase {
public class LuckPermsPermissible extends PermissibleBase {
private static final Field ATTACHMENTS_FIELD;
@@ -93,7 +96,7 @@ public class LPPermissible extends PermissibleBase {
private final LPBukkitPlugin plugin;
// caches context lookups for the player
private final QueryOptionsSupplier queryOptionsSupplier;
private final QueryOptionsCache<Player> queryOptionsSupplier;
// the players previous permissible. (the one they had before this one was injected)
private PermissibleBase oldPermissible = null;
@@ -103,9 +106,9 @@ public class LPPermissible extends PermissibleBase {
// the attachments hooked onto the permissible.
// this collection is only modified by the attachments themselves
final Set<LPPermissionAttachment> lpAttachments = ConcurrentHashMap.newKeySet();
final Set<LuckPermsPermissionAttachment> hookedAttachments = ConcurrentHashMap.newKeySet();
public LPPermissible(Player player, User user, LPBukkitPlugin plugin) {
public LuckPermsPermissible(Player player, User user, LPBukkitPlugin plugin) {
super(player);
this.user = Objects.requireNonNull(user, "user");
this.player = Objects.requireNonNull(player, "player");
@@ -139,13 +142,19 @@ public class LPPermissible extends PermissibleBase {
throw new NullPointerException("permission");
}
TristateResult result = this.user.getCachedData().getPermissionData(this.queryOptionsSupplier.getQueryOptions()).checkPermission(permission, PermissionCheckEvent.Origin.PLATFORM_LOOKUP_CHECK);
QueryOptions queryOptions = this.queryOptionsSupplier.getQueryOptions();
TristateResult result = this.user.getCachedData().getPermissionData(queryOptions).checkPermission(permission, PermissionCheckEvent.Origin.PLATFORM_LOOKUP_CHECK);
if (result.result() == Tristate.UNDEFINED) {
return false;
}
// ignore matches made from looking up in the permission map (replicate bukkit behaviour)
return !(result.processorClass() == DefaultsProcessor.class && "permission map".equals(result.cause()));
if (result.processorClass() == DefaultsProcessor.class && "permission map".equals(result.cause())) {
return false;
}
// ignore the op processor
return result.processorClass() != OpProcessor.class;
}
@Override
@@ -163,8 +172,8 @@ public class LPPermissible extends PermissibleBase {
throw new NullPointerException("permission");
}
Tristate ts = this.user.getCachedData().getPermissionData(this.queryOptionsSupplier.getQueryOptions()).checkPermission(permission, PermissionCheckEvent.Origin.PLATFORM_PERMISSION_CHECK).result();
return ts != Tristate.UNDEFINED ? ts.asBoolean() : Permission.DEFAULT_PERMISSION.getValue(isOp());
QueryOptions queryOptions = this.queryOptionsSupplier.getQueryOptions();
return this.user.getCachedData().getPermissionData(queryOptions).checkPermission(permission, PermissionCheckEvent.Origin.PLATFORM_PERMISSION_CHECK).result().asBoolean();
}
@Override
@@ -173,16 +182,16 @@ public class LPPermissible extends PermissibleBase {
throw new NullPointerException("permission");
}
Tristate ts = this.user.getCachedData().getPermissionData(this.queryOptionsSupplier.getQueryOptions()).checkPermission(permission.getName(), PermissionCheckEvent.Origin.PLATFORM_PERMISSION_CHECK).result();
if (ts != Tristate.UNDEFINED) {
return ts.asBoolean();
QueryOptions queryOptions = this.queryOptionsSupplier.getQueryOptions();
TristateResult result = this.user.getCachedData().getPermissionData(queryOptions).checkPermission(permission.getName(), PermissionCheckEvent.Origin.PLATFORM_PERMISSION_CHECK);
// override default op handling using the Permission class we have
if (result.processorClass() == OpProcessor.class && this.plugin.getConfiguration().get(ConfigKeys.APPLY_BUKKIT_DEFAULT_PERMISSIONS)) {
// 'op == true' is implied by the presence of the OpProcessor class
return permission.getDefault().getValue(true);
}
if (this.plugin.getConfiguration().get(ConfigKeys.APPLY_BUKKIT_DEFAULT_PERMISSIONS)) {
return permission.getDefault().getValue(isOp());
} else {
return Permission.DEFAULT_PERMISSION.getValue(isOp());
}
return result.result().asBoolean();
}
/**
@@ -192,7 +201,7 @@ public class LPPermissible extends PermissibleBase {
*/
void convertAndAddAttachments(Collection<PermissionAttachment> attachments) {
for (PermissionAttachment attachment : attachments) {
new LPPermissionAttachment(this, attachment).hook();
new LuckPermsPermissionAttachment(this, attachment).hook();
}
}
@@ -203,103 +212,99 @@ public class LPPermissible extends PermissibleBase {
@Override
public @NonNull Set<PermissionAttachmentInfo> getEffectivePermissions() {
return this.user.getCachedData().getPermissionData(this.queryOptionsSupplier.getQueryOptions()).getPermissionMap().entrySet().stream()
.map(entry -> new PermissionAttachmentInfo(this.player, entry.getKey(), null, entry.getValue()))
.collect(ImmutableCollectors.toSet());
Map<String, Boolean> permissionMap = this.user.getCachedData().getPermissionData(this.queryOptionsSupplier.getQueryOptions()).getPermissionMap();
ImmutableSet.Builder<PermissionAttachmentInfo> builder = ImmutableSet.builder();
permissionMap.forEach((key, value) -> builder.add(new PermissionAttachmentInfo(this.player, key, null, value)));
return builder.build();
}
@Override
public @NonNull LPPermissionAttachment addAttachment(@NonNull Plugin plugin) {
if (plugin == null) {
throw new NullPointerException("plugin");
}
public @NonNull LuckPermsPermissionAttachment addAttachment(@NonNull Plugin plugin) {
Objects.requireNonNull(plugin, "plugin");
LPPermissionAttachment ret = new LPPermissionAttachment(this, plugin);
ret.hook();
return ret;
LuckPermsPermissionAttachment attachment = new LuckPermsPermissionAttachment(this, plugin);
attachment.hook();
return attachment;
}
@Override
public @NonNull PermissionAttachment addAttachment(@NonNull Plugin plugin, @NonNull String permission, boolean value) {
if (plugin == null) {
throw new NullPointerException("plugin");
}
if (permission == null) {
throw new NullPointerException("permission");
}
Objects.requireNonNull(plugin, "plugin");
Objects.requireNonNull(permission, "permission");
PermissionAttachment ret = addAttachment(plugin);
ret.setPermission(permission, value);
return ret;
PermissionAttachment attachment = addAttachment(plugin);
attachment.setPermission(permission, value);
return attachment;
}
@Override
public LPPermissionAttachment addAttachment(@NonNull Plugin plugin, int ticks) {
if (plugin == null) {
throw new NullPointerException("plugin");
}
public LuckPermsPermissionAttachment addAttachment(@NonNull Plugin plugin, int ticks) {
Objects.requireNonNull(plugin, "plugin");
if (!plugin.isEnabled()) {
throw new IllegalArgumentException("Plugin " + plugin.getDescription().getFullName() + " is not enabled");
}
LPPermissionAttachment ret = addAttachment(plugin);
if (getPlugin().getBootstrap().getServer().getScheduler().scheduleSyncDelayedTask(plugin, ret::remove, ticks) == -1) {
ret.remove();
LuckPermsPermissionAttachment attachment = addAttachment(plugin);
if (getPlugin().getBootstrap().getServer().getScheduler().scheduleSyncDelayedTask(plugin, attachment::remove, ticks) == -1) {
attachment.remove();
throw new RuntimeException("Could not add PermissionAttachment to " + this.player + " for plugin " + plugin.getDescription().getFullName() + ": Scheduler returned -1");
}
return ret;
return attachment;
}
@Override
public LPPermissionAttachment addAttachment(@NonNull Plugin plugin, @NonNull String permission, boolean value, int ticks) {
if (plugin == null) {
throw new NullPointerException("plugin");
}
if (permission == null) {
throw new NullPointerException("permission");
}
public LuckPermsPermissionAttachment addAttachment(@NonNull Plugin plugin, @NonNull String permission, boolean value, int ticks) {
Objects.requireNonNull(plugin, "plugin");
Objects.requireNonNull(permission, "permission");
LPPermissionAttachment ret = addAttachment(plugin, ticks);
ret.setPermission(permission, value);
return ret;
LuckPermsPermissionAttachment attachment = addAttachment(plugin, ticks);
attachment.setPermission(permission, value);
return attachment;
}
@Override
public void removeAttachment(@NonNull PermissionAttachment attachment) {
if (attachment == null) {
throw new NullPointerException("attachment");
}
Objects.requireNonNull(attachment, "attachment");
LPPermissionAttachment a;
LuckPermsPermissionAttachment luckPermsAttachment;
if (!(attachment instanceof LPPermissionAttachment)) {
if (!(attachment instanceof LuckPermsPermissionAttachment)) {
// try to find a match
LPPermissionAttachment match = this.lpAttachments.stream().filter(at -> at.getSource() == attachment).findFirst().orElse(null);
LuckPermsPermissionAttachment match = this.hookedAttachments.stream().filter(at -> at.getSource() == attachment).findFirst().orElse(null);
if (match != null) {
a = match;
luckPermsAttachment = match;
} else {
throw new IllegalArgumentException("Given attachment is not a LPPermissionAttachment.");
}
} else {
a = (LPPermissionAttachment) attachment;
luckPermsAttachment = (LuckPermsPermissionAttachment) attachment;
}
if (a.getPermissible() != this) {
if (luckPermsAttachment.getPermissible() != this) {
throw new IllegalArgumentException("Attachment does not belong to this permissible.");
}
a.remove();
luckPermsAttachment.remove();
}
@Override
public void recalculatePermissions() {
// do nothing
// this method is called (among other times) when op status is updated.
// because we encapsulate op status within QueryOptions, we need to invalidate
// the query options cache when op status changes.
// (#invalidate is a fast call)
if (this.queryOptionsSupplier != null) { // this method is called by the super class constructor, before this class has fully initialised
this.queryOptionsSupplier.invalidate();
}
// but we don't need to do anything else in this method, unlike the CB impl.
}
@Override
public void clearPermissions() {
this.lpAttachments.forEach(LPPermissionAttachment::remove);
this.hookedAttachments.forEach(LuckPermsPermissionAttachment::remove);
}
public User getUser() {
@@ -339,11 +344,11 @@ public class LPPermissible extends PermissibleBase {
@Override
public boolean add(PermissionAttachment attachment) {
if (LPPermissible.this.lpAttachments.stream().anyMatch(at -> at.getSource() == attachment)) {
if (LuckPermsPermissible.this.hookedAttachments.stream().anyMatch(at -> at.getSource() == attachment)) {
return false;
}
new LPPermissionAttachment(LPPermissible.this, attachment).hook();
new LuckPermsPermissionAttachment(LuckPermsPermissible.this, attachment).hook();
return true;
}
@@ -372,27 +377,27 @@ public class LPPermissible extends PermissibleBase {
@Override
public boolean contains(Object o) {
PermissionAttachment attachment = (PermissionAttachment) o;
return LPPermissible.this.lpAttachments.stream().anyMatch(at -> at.getSource() == attachment);
return LuckPermsPermissible.this.hookedAttachments.stream().anyMatch(at -> at.getSource() == attachment);
}
@Override
public Iterator<PermissionAttachment> iterator() {
return ImmutableList.<PermissionAttachment>copyOf(LPPermissible.this.lpAttachments).iterator();
return ImmutableList.<PermissionAttachment>copyOf(LuckPermsPermissible.this.hookedAttachments).iterator();
}
@Override
public ListIterator<PermissionAttachment> listIterator() {
return ImmutableList.<PermissionAttachment>copyOf(LPPermissible.this.lpAttachments).listIterator();
return ImmutableList.<PermissionAttachment>copyOf(LuckPermsPermissible.this.hookedAttachments).listIterator();
}
@Override
public @NonNull Object[] toArray() {
return ImmutableList.<PermissionAttachment>copyOf(LPPermissible.this.lpAttachments).toArray();
return ImmutableList.<PermissionAttachment>copyOf(LuckPermsPermissible.this.hookedAttachments).toArray();
}
@Override
public @NonNull <T> T[] toArray(@NonNull T[] a) {
return ImmutableList.<PermissionAttachment>copyOf(LPPermissible.this.lpAttachments).toArray(a);
return ImmutableList.<PermissionAttachment>copyOf(LuckPermsPermissible.this.hookedAttachments).toArray(a);
}
@Override public int size() { throw new UnsupportedOperationException(); }
@@ -27,14 +27,15 @@ package me.lucko.luckperms.bukkit.inject.permissible;
import com.google.common.base.Preconditions;
import me.lucko.luckperms.bukkit.inject.dummy.DummyPlugin;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.model.User;
import me.lucko.luckperms.common.node.factory.NodeBuilders;
import net.luckperms.api.model.data.DataType;
import net.luckperms.api.node.Node;
import net.luckperms.api.node.NodeBuilder;
import net.luckperms.api.node.metadata.NodeMetadataKey;
import net.luckperms.api.query.Flag;
import net.luckperms.api.query.QueryOptions;
import org.bukkit.permissions.PermissionAttachment;
import org.bukkit.permissions.PermissionRemovedExecutor;
@@ -54,9 +55,9 @@ import java.util.Set;
*
* Applies all permissions directly to the backing user instance via transient nodes.
*/
public class LPPermissionAttachment extends PermissionAttachment {
public class LuckPermsPermissionAttachment extends PermissionAttachment {
public static final NodeMetadataKey<LPPermissionAttachment> TRANSIENT_SOURCE_KEY = NodeMetadataKey.of("transientsource", LPPermissionAttachment.class);
public static final NodeMetadataKey<LuckPermsPermissionAttachment> TRANSIENT_SOURCE_KEY = NodeMetadataKey.of("transientsource", LuckPermsPermissionAttachment.class);
/**
* The field in PermissionAttachment where the attachments applied permissions
@@ -76,7 +77,7 @@ public class LPPermissionAttachment extends PermissionAttachment {
/**
* The parent LPPermissible
*/
private final LPPermissible permissible;
private final LuckPermsPermissible permissible;
/**
* The plugin which "owns" this attachment, may be null
@@ -103,16 +104,16 @@ public class LPPermissionAttachment extends PermissionAttachment {
*/
private PermissionAttachment source;
public LPPermissionAttachment(LPPermissible permissible, Plugin owner) {
super(DummyPlugin.INSTANCE, null);
public LuckPermsPermissionAttachment(LuckPermsPermissible permissible, Plugin owner) {
super(owner, null);
this.permissible = permissible;
this.owner = owner;
injectFakeMap();
}
public LPPermissionAttachment(LPPermissible permissible, PermissionAttachment source) {
super(DummyPlugin.INSTANCE, null);
public LuckPermsPermissionAttachment(LuckPermsPermissible permissible, PermissionAttachment source) {
super(source.getPlugin(), null);
this.permissible = permissible;
this.owner = source.getPlugin();
@@ -144,7 +145,7 @@ public class LPPermissionAttachment extends PermissionAttachment {
}
@Override
public @NonNull LPPermissible getPermissible() {
public @NonNull LuckPermsPermissible getPermissible() {
return this.permissible;
}
@@ -167,7 +168,7 @@ public class LPPermissionAttachment extends PermissionAttachment {
*/
public void hook() {
this.hooked = true;
this.permissible.lpAttachments.add(this);
this.permissible.hookedAttachments.add(this);
for (Map.Entry<String, Boolean> entry : this.perms.entrySet()) {
if (entry.getKey() == null || entry.getKey().isEmpty()) {
continue;
@@ -182,16 +183,19 @@ public class LPPermissionAttachment extends PermissionAttachment {
}
// construct a node for the permission being set
// we use the servers static context to *try* to ensure that the node will apply
Node node = NodeBuilders.determineMostApplicable(name)
NodeBuilder<?, ?> node = NodeBuilders.determineMostApplicable(name)
.value(value)
.withContext(this.permissible.getPlugin().getContextManager().getStaticContext())
.withMetadata(TRANSIENT_SOURCE_KEY, this)
.build();
.withMetadata(TRANSIENT_SOURCE_KEY, this);
// apply with the servers static context to *try* to ensure that the node will apply if INCLUDE_NODES_WITHOUT_SERVER_CONTEXT is not set
QueryOptions globalQueryOptions = this.permissible.getPlugin().getConfiguration().get(ConfigKeys.GLOBAL_QUERY_OPTIONS);
if (!globalQueryOptions.flag(Flag.INCLUDE_NODES_WITHOUT_SERVER_CONTEXT)) {
node.withContext(this.permissible.getPlugin().getContextManager().getStaticContext());
}
// set the transient node
User user = this.permissible.getUser();
user.setNode(DataType.TRANSIENT, node, true);
user.setNode(DataType.TRANSIENT, node.build(), true);
}
private void unsetPermissionInternal(String name) {
@@ -226,7 +230,7 @@ public class LPPermissionAttachment extends PermissionAttachment {
// unhook from the permissible
this.hooked = false;
this.permissible.lpAttachments.remove(this);
this.permissible.hookedAttachments.remove(this);
return true;
}
@@ -311,7 +315,7 @@ public class LPPermissionAttachment extends PermissionAttachment {
@Override
public Boolean put(String key, Boolean value) {
// grab the previous result, so we can still satisfy the method signature of Map
Boolean previous = LPPermissionAttachment.this.perms.get(key);
Boolean previous = LuckPermsPermissionAttachment.this.perms.get(key);
// proxy the call back through the PermissionAttachment instance
setPermission(key, value);
@@ -330,7 +334,7 @@ public class LPPermissionAttachment extends PermissionAttachment {
String permission = ((String) key);
// grab the previous result, so we can still satisfy the method signature of Map
Boolean previous = LPPermissionAttachment.this.perms.get(permission);
Boolean previous = LuckPermsPermissionAttachment.this.perms.get(permission);
// proxy the call back through the PermissionAttachment instance
unsetPermission(permission);
@@ -349,71 +353,71 @@ public class LPPermissionAttachment extends PermissionAttachment {
@Override
public void clear() {
// remove the permissions which have already been applied
if (LPPermissionAttachment.this.hooked) {
if (LuckPermsPermissionAttachment.this.hooked) {
clearInternal();
}
// clear the backing map
LPPermissionAttachment.this.perms.clear();
LuckPermsPermissionAttachment.this.perms.clear();
}
@Override
public int size() {
// return the size of the permissions map - probably the most accurate value we have
return LPPermissionAttachment.this.perms.size();
return LuckPermsPermissionAttachment.this.perms.size();
}
@Override
public boolean isEmpty() {
// return if the permissions map is empty - again probably the most accurate thing
// we can return
return LPPermissionAttachment.this.perms.isEmpty();
return LuckPermsPermissionAttachment.this.perms.isEmpty();
}
@Override
public boolean containsKey(Object key) {
// just proxy
return LPPermissionAttachment.this.perms.containsKey(key);
return LuckPermsPermissionAttachment.this.perms.containsKey(key);
}
@Override
public boolean containsValue(Object value) {
// just proxy
return LPPermissionAttachment.this.perms.containsValue(value);
return LuckPermsPermissionAttachment.this.perms.containsValue(value);
}
@Override
public Boolean get(Object key) {
// just proxy
return LPPermissionAttachment.this.perms.get(key);
return LuckPermsPermissionAttachment.this.perms.get(key);
}
@Override
public Set<String> keySet() {
// just proxy
return Collections.unmodifiableSet(LPPermissionAttachment.this.perms.keySet());
return Collections.unmodifiableSet(LuckPermsPermissionAttachment.this.perms.keySet());
}
@Override
public Collection<Boolean> values() {
// just proxy
return Collections.unmodifiableCollection(LPPermissionAttachment.this.perms.values());
return Collections.unmodifiableCollection(LuckPermsPermissionAttachment.this.perms.values());
}
@Override
public Set<Entry<String, Boolean>> entrySet() {
// just proxy
return Collections.unmodifiableSet(LPPermissionAttachment.this.perms.entrySet());
return Collections.unmodifiableSet(LuckPermsPermissionAttachment.this.perms.entrySet());
}
@Override
public boolean equals(Object obj) {
return obj instanceof Map<?, ?> && LPPermissionAttachment.this.perms.equals(obj);
return obj instanceof Map<?, ?> && LuckPermsPermissionAttachment.this.perms.equals(obj);
}
@Override
public int hashCode() {
return LPPermissionAttachment.this.perms.hashCode();
return LuckPermsPermissionAttachment.this.perms.hashCode();
}
}
}
@@ -25,14 +25,13 @@
package me.lucko.luckperms.bukkit.inject.permissible;
import me.lucko.luckperms.bukkit.inject.dummy.DummyPermissibleBase;
import me.lucko.luckperms.common.calculator.result.TristateResult;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.query.QueryOptionsImpl;
import me.lucko.luckperms.common.verbose.VerboseHandler;
import me.lucko.luckperms.common.verbose.event.PermissionCheckEvent;
import net.luckperms.api.util.Tristate;
import net.luckperms.api.query.QueryOptions;
import org.bukkit.permissions.PermissibleBase;
import org.bukkit.permissions.Permission;
@@ -70,7 +69,7 @@ public class MonitoredPermissibleBase extends PermissibleBase {
}
private void logCheck(PermissionCheckEvent.Origin origin, String permission, boolean result) {
this.plugin.getVerboseHandler().offerPermissionCheckEvent(origin, this.name, QueryOptions.defaultContextualOptions(), permission, TristateResult.of(Tristate.of(result)));
this.plugin.getVerboseHandler().offerPermissionCheckEvent(origin, this.name, QueryOptionsImpl.DEFAULT_CONTEXTUAL, permission, TristateResult.of(Tristate.of(result)));
this.plugin.getPermissionRegistry().offer(permission);
}
@@ -25,8 +25,7 @@
package me.lucko.luckperms.bukkit.inject.permissible;
import me.lucko.luckperms.bukkit.compat.CraftBukkitUtil;
import me.lucko.luckperms.bukkit.inject.dummy.DummyPermissibleBase;
import me.lucko.luckperms.bukkit.util.CraftBukkitImplementation;
import org.bukkit.entity.Player;
import org.bukkit.permissions.PermissibleBase;
@@ -36,7 +35,7 @@ import java.lang.reflect.Field;
import java.util.List;
/**
* Injects a {@link LPPermissible} into a {@link Player}.
* Injects a {@link LuckPermsPermissible} into a {@link Player}.
*
* This allows LuckPerms to directly intercept permission checks and take over all handling of
* checks made by plugins.
@@ -63,7 +62,7 @@ public final class PermissibleInjector {
Field humanEntityPermissibleField;
try {
// craftbukkit
humanEntityPermissibleField = CraftBukkitUtil.obcClass("entity.CraftHumanEntity").getDeclaredField("perm");
humanEntityPermissibleField = CraftBukkitImplementation.obcClass("entity.CraftHumanEntity").getDeclaredField("perm");
humanEntityPermissibleField.setAccessible(true);
} catch (Exception e) {
// glowstone
@@ -81,19 +80,19 @@ public final class PermissibleInjector {
}
/**
* Injects a {@link LPPermissible} into a {@link Player}.
* Injects a {@link LuckPermsPermissible} into a {@link Player}.
*
* @param player the player to inject into
* @param newPermissible the permissible to inject
* @throws Exception propagates any exceptions which were thrown during injection
*/
public static void inject(Player player, LPPermissible newPermissible) throws Exception {
public static void inject(Player player, LuckPermsPermissible newPermissible) throws Exception {
// get the existing PermissibleBase held by the player
PermissibleBase oldPermissible = (PermissibleBase) HUMAN_ENTITY_PERMISSIBLE_FIELD.get(player);
// seems we have already injected into this player.
if (oldPermissible instanceof LPPermissible) {
if (oldPermissible instanceof LuckPermsPermissible) {
throw new IllegalStateException("LPPermissible already injected into player " + player.toString());
}
@@ -115,7 +114,7 @@ public final class PermissibleInjector {
}
/**
* Uninjects a {@link LPPermissible} from a {@link Player}.
* Uninjects a {@link LuckPermsPermissible} from a {@link Player}.
*
* @param player the player to uninject from
* @param dummy if the replacement permissible should be a dummy.
@@ -127,8 +126,8 @@ public final class PermissibleInjector {
PermissibleBase permissible = (PermissibleBase) HUMAN_ENTITY_PERMISSIBLE_FIELD.get(player);
// only uninject if the permissible was a luckperms one.
if (permissible instanceof LPPermissible) {
LPPermissible lpPermissible = ((LPPermissible) permissible);
if (permissible instanceof LuckPermsPermissible) {
LuckPermsPermissible lpPermissible = ((LuckPermsPermissible) permissible);
// clear all permissions
lpPermissible.clearPermissions();
@@ -26,7 +26,7 @@
package me.lucko.luckperms.bukkit.inject.permissible;
import me.lucko.luckperms.bukkit.LPBukkitPlugin;
import me.lucko.luckperms.bukkit.compat.CraftBukkitUtil;
import me.lucko.luckperms.bukkit.util.CraftBukkitImplementation;
import org.bukkit.command.ConsoleCommandSender;
import org.bukkit.permissions.PermissibleBase;
@@ -101,7 +101,7 @@ public class PermissibleMonitoringInjector implements Runnable {
ConsoleCommandSender consoleSender = this.plugin.getBootstrap().getServer().getConsoleSender();
// get the ServerCommandSender class
Class<?> serverCommandSenderClass = CraftBukkitUtil.obcClass("command.ServerCommandSender");
Class<?> serverCommandSenderClass = CraftBukkitImplementation.obcClass("command.ServerCommandSender");
// get the perm field
Field permField = serverCommandSenderClass.getDeclaredField("perm");
@@ -122,7 +122,7 @@ public class PermissibleMonitoringInjector implements Runnable {
private void injectCommandBlock() throws Exception {
// get the ServerCommandSender class
Class<?> serverCommandSenderClass = CraftBukkitUtil.obcClass("command.ServerCommandSender");
Class<?> serverCommandSenderClass = CraftBukkitImplementation.obcClass("command.ServerCommandSender");
// get the blockPermInst field
Field permField = serverCommandSenderClass.getDeclaredField("blockPermInst");
@@ -149,7 +149,7 @@ public class PermissibleMonitoringInjector implements Runnable {
private void injectEntity() throws Exception {
// get the CraftEntity class
Class<?> entityClass = CraftBukkitUtil.obcClass("entity.CraftEntity");
Class<?> entityClass = CraftBukkitImplementation.obcClass("entity.CraftEntity");
// get the method used to obtain a PermissibleBase
// this method will initialise a new PB instance if one doesn't yet exist
@@ -27,7 +27,6 @@ package me.lucko.luckperms.bukkit.inject.server;
import me.lucko.luckperms.bukkit.LPBukkitPlugin;
import org.bukkit.Bukkit;
import org.bukkit.permissions.Permission;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.SimplePluginManager;
@@ -39,9 +38,9 @@ import java.util.Objects;
import java.util.Set;
/**
* Injects a {@link LPDefaultsMap} info the {@link PluginManager}.
* Injects a {@link LuckPermsDefaultsMap} info the {@link PluginManager}.
*/
public class InjectorDefaultsMap implements Runnable {
public class InjectorDefaultsMap {
private static final Field DEFAULT_PERMISSIONS_FIELD;
static {
@@ -61,12 +60,11 @@ public class InjectorDefaultsMap implements Runnable {
this.plugin = plugin;
}
@Override
public void run() {
public void inject() {
try {
LPDefaultsMap ret = inject();
if (ret != null) {
this.plugin.setDefaultPermissionMap(ret);
LuckPermsDefaultsMap defaultsMap = tryInject();
if (defaultsMap != null) {
this.plugin.setDefaultPermissionMap(defaultsMap);
}
} catch (Exception e) {
this.plugin.getLogger().severe("Exception occurred whilst injecting LuckPerms Default Permission map.");
@@ -74,7 +72,7 @@ public class InjectorDefaultsMap implements Runnable {
}
}
private LPDefaultsMap inject() throws Exception {
private LuckPermsDefaultsMap tryInject() throws Exception {
Objects.requireNonNull(DEFAULT_PERMISSIONS_FIELD, "DEFAULT_PERMISSIONS_FIELD");
PluginManager pluginManager = this.plugin.getBootstrap().getServer().getPluginManager();
@@ -85,7 +83,7 @@ public class InjectorDefaultsMap implements Runnable {
}
Object map = DEFAULT_PERMISSIONS_FIELD.get(pluginManager);
if (map instanceof LPDefaultsMap && ((LPDefaultsMap) map).plugin == this.plugin) {
if (map instanceof LuckPermsDefaultsMap && ((LuckPermsDefaultsMap) map).plugin == this.plugin) {
return null;
}
@@ -93,23 +91,23 @@ public class InjectorDefaultsMap implements Runnable {
Map<Boolean, Set<Permission>> castedMap = (Map<Boolean, Set<Permission>>) map;
// make a new map & inject it
LPDefaultsMap newMap = new LPDefaultsMap(this.plugin, castedMap);
LuckPermsDefaultsMap newMap = new LuckPermsDefaultsMap(this.plugin, castedMap);
DEFAULT_PERMISSIONS_FIELD.set(pluginManager, newMap);
return newMap;
}
public static void uninject() {
public void uninject() {
try {
Objects.requireNonNull(DEFAULT_PERMISSIONS_FIELD, "DEFAULT_PERMISSIONS_FIELD");
PluginManager pluginManager = Bukkit.getServer().getPluginManager();
PluginManager pluginManager = this.plugin.getBootstrap().getServer().getPluginManager();
if (!(pluginManager instanceof SimplePluginManager)) {
return;
}
Object map = DEFAULT_PERMISSIONS_FIELD.get(pluginManager);
if (map instanceof LPDefaultsMap) {
LPDefaultsMap lpMap = (LPDefaultsMap) map;
if (map instanceof LuckPermsDefaultsMap) {
LuckPermsDefaultsMap lpMap = (LuckPermsDefaultsMap) map;
DEFAULT_PERMISSIONS_FIELD.set(pluginManager, new HashMap<>(lpMap));
}
} catch (Exception e) {
@@ -27,7 +27,6 @@ package me.lucko.luckperms.bukkit.inject.server;
import me.lucko.luckperms.bukkit.LPBukkitPlugin;
import org.bukkit.Bukkit;
import org.bukkit.permissions.Permission;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.SimplePluginManager;
@@ -38,9 +37,9 @@ import java.util.Map;
import java.util.Objects;
/**
* Injects a {@link LPPermissionMap} into the {@link PluginManager}.
* Injects a {@link LuckPermsPermissionMap} into the {@link PluginManager}.
*/
public class InjectorPermissionMap implements Runnable {
public class InjectorPermissionMap {
private static final Field PERMISSIONS_FIELD;
static {
@@ -60,12 +59,11 @@ public class InjectorPermissionMap implements Runnable {
this.plugin = plugin;
}
@Override
public void run() {
public void inject() {
try {
LPPermissionMap ret = inject();
if (ret != null) {
this.plugin.setPermissionMap(ret);
LuckPermsPermissionMap permissionMap = tryInject();
if (permissionMap != null) {
this.plugin.setPermissionMap(permissionMap);
}
} catch (Exception e) {
this.plugin.getLogger().severe("Exception occurred whilst injecting LuckPerms Permission map.");
@@ -73,7 +71,7 @@ public class InjectorPermissionMap implements Runnable {
}
}
private LPPermissionMap inject() throws Exception {
private LuckPermsPermissionMap tryInject() throws Exception {
Objects.requireNonNull(PERMISSIONS_FIELD, "PERMISSIONS_FIELD");
PluginManager pluginManager = this.plugin.getBootstrap().getServer().getPluginManager();
@@ -84,7 +82,7 @@ public class InjectorPermissionMap implements Runnable {
}
Object map = PERMISSIONS_FIELD.get(pluginManager);
if (map instanceof LPPermissionMap && ((LPPermissionMap) map).plugin == this.plugin) {
if (map instanceof LuckPermsPermissionMap && ((LuckPermsPermissionMap) map).plugin == this.plugin) {
return null;
}
@@ -92,23 +90,23 @@ public class InjectorPermissionMap implements Runnable {
Map<String, Permission> castedMap = (Map<String, Permission>) map;
// make a new map & inject it
LPPermissionMap newMap = new LPPermissionMap(this.plugin, castedMap);
LuckPermsPermissionMap newMap = new LuckPermsPermissionMap(this.plugin, castedMap);
PERMISSIONS_FIELD.set(pluginManager, newMap);
return newMap;
}
public static void uninject() {
public void uninject() {
try {
Objects.requireNonNull(PERMISSIONS_FIELD, "PERMISSIONS_FIELD");
PluginManager pluginManager = Bukkit.getServer().getPluginManager();
PluginManager pluginManager = this.plugin.getBootstrap().getServer().getPluginManager();
if (!(pluginManager instanceof SimplePluginManager)) {
return;
}
Object map = PERMISSIONS_FIELD.get(pluginManager);
if (map instanceof LPPermissionMap) {
LPPermissionMap lpMap = (LPPermissionMap) map;
if (map instanceof LuckPermsPermissionMap) {
LuckPermsPermissionMap lpMap = (LuckPermsPermissionMap) map;
PERMISSIONS_FIELD.set(pluginManager, new HashMap<>(lpMap));
}
} catch (Exception e) {
@@ -27,7 +27,6 @@ package me.lucko.luckperms.bukkit.inject.server;
import me.lucko.luckperms.bukkit.LPBukkitPlugin;
import org.bukkit.Bukkit;
import org.bukkit.permissions.Permissible;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.SimplePluginManager;
@@ -37,9 +36,9 @@ import java.util.Map;
import java.util.Objects;
/**
* Injects a {@link LPSubscriptionMap} into the {@link PluginManager}.
* Injects a {@link LuckPermsSubscriptionMap} into the {@link PluginManager}.
*/
public class InjectorSubscriptionMap implements Runnable {
public class InjectorSubscriptionMap {
private static final Field PERM_SUBS_FIELD;
static {
@@ -59,12 +58,11 @@ public class InjectorSubscriptionMap implements Runnable {
this.plugin = plugin;
}
@Override
public void run() {
public void inject() {
try {
LPSubscriptionMap ret = inject();
if (ret != null) {
this.plugin.setSubscriptionMap(ret);
LuckPermsSubscriptionMap subscriptionMap = tryInject();
if (subscriptionMap != null) {
this.plugin.setSubscriptionMap(subscriptionMap);
}
} catch (Exception e) {
this.plugin.getLogger().severe("Exception occurred whilst injecting LuckPerms Permission Subscription map.");
@@ -72,7 +70,7 @@ public class InjectorSubscriptionMap implements Runnable {
}
}
private LPSubscriptionMap inject() throws Exception {
private LuckPermsSubscriptionMap tryInject() throws Exception {
Objects.requireNonNull(PERM_SUBS_FIELD, "PERM_SUBS_FIELD");
PluginManager pluginManager = this.plugin.getBootstrap().getServer().getPluginManager();
@@ -83,35 +81,35 @@ public class InjectorSubscriptionMap implements Runnable {
}
Object map = PERM_SUBS_FIELD.get(pluginManager);
if (map instanceof LPSubscriptionMap) {
if (((LPSubscriptionMap) map).plugin == this.plugin) {
if (map instanceof LuckPermsSubscriptionMap) {
if (((LuckPermsSubscriptionMap) map).plugin == this.plugin) {
return null;
}
map = ((LPSubscriptionMap) map).detach();
map = ((LuckPermsSubscriptionMap) map).detach();
}
//noinspection unchecked
Map<String, Map<Permissible, Boolean>> castedMap = (Map<String, Map<Permissible, Boolean>>) map;
// make a new subscription map & inject it
LPSubscriptionMap newMap = new LPSubscriptionMap(this.plugin, castedMap);
LuckPermsSubscriptionMap newMap = new LuckPermsSubscriptionMap(this.plugin, castedMap);
PERM_SUBS_FIELD.set(pluginManager, newMap);
return newMap;
}
public static void uninject() {
public void uninject() {
try {
Objects.requireNonNull(PERM_SUBS_FIELD, "PERM_SUBS_FIELD");
PluginManager pluginManager = Bukkit.getServer().getPluginManager();
PluginManager pluginManager = this.plugin.getBootstrap().getServer().getPluginManager();
if (!(pluginManager instanceof SimplePluginManager)) {
return;
}
Object map = PERM_SUBS_FIELD.get(pluginManager);
if (map instanceof LPSubscriptionMap) {
LPSubscriptionMap lpMap = (LPSubscriptionMap) map;
if (map instanceof LuckPermsSubscriptionMap) {
LuckPermsSubscriptionMap lpMap = (LuckPermsSubscriptionMap) map;
PERM_SUBS_FIELD.set(pluginManager, lpMap.detach());
}
} catch (Exception e) {
@@ -56,7 +56,7 @@ import java.util.concurrent.ConcurrentHashMap;
*
* Injected by {@link InjectorDefaultsMap}.
*/
public final class LPDefaultsMap implements Map<Boolean, Set<Permission>> {
public final class LuckPermsDefaultsMap implements Map<Boolean, Set<Permission>> {
// keyset for all instances
private static final Set<Boolean> KEY_SET = ImmutableSet.of(Boolean.TRUE, Boolean.FALSE);
@@ -78,7 +78,7 @@ public final class LPDefaultsMap implements Map<Boolean, Set<Permission>> {
Maps.immutableEntry(Boolean.FALSE, this.nonOpSet)
);
public LPDefaultsMap(LPBukkitPlugin plugin, Map<Boolean, Set<Permission>> existingData) {
public LuckPermsDefaultsMap(LPBukkitPlugin plugin, Map<Boolean, Set<Permission>> existingData) {
this.plugin = plugin;
this.opSet.addAll(existingData.getOrDefault(Boolean.TRUE, Collections.emptySet()));
this.nonOpSet.addAll(existingData.getOrDefault(Boolean.FALSE, Collections.emptySet()));
@@ -113,12 +113,9 @@ public final class LPDefaultsMap implements Map<Boolean, Set<Permission>> {
}
// return wrappers around this map impl
@Override
public @NonNull Collection<Set<Permission>> values() { return this.values; }
@Override
public @NonNull Set<Entry<Boolean, Set<Permission>>> entrySet() { return this.entrySet; }
@Override
public @NonNull Set<Boolean> keySet() { return KEY_SET; }
@Override public @NonNull Collection<Set<Permission>> values() { return this.values; }
@Override public @NonNull Set<Entry<Boolean, Set<Permission>>> entrySet() { return this.entrySet; }
@Override public @NonNull Set<Boolean> keySet() { return KEY_SET; }
// return accurate results for the Map spec
@Override public int size() { return 2; }
@@ -142,10 +139,10 @@ public final class LPDefaultsMap implements Map<Boolean, Set<Permission>> {
@Override
protected @NonNull Map<String, Boolean> supply() {
Map<String, Boolean> builder = new HashMap<>();
for (Permission perm : LPDefaultsMap.this.get(this.op)) {
for (Permission perm : LuckPermsDefaultsMap.this.get(this.op)) {
String name = perm.getName().toLowerCase();
builder.put(name, true);
for (Map.Entry<String, Boolean> child : LPDefaultsMap.this.plugin.getPermissionMap().getChildPermissions(name, true).entrySet()) {
for (Map.Entry<String, Boolean> child : LuckPermsDefaultsMap.this.plugin.getPermissionMap().getChildPermissions(name, true).entrySet()) {
builder.putIfAbsent(child.getKey(), child.getValue());
}
}
@@ -57,7 +57,7 @@ import java.util.function.Function;
*
* Injected by {@link InjectorPermissionMap}.
*/
public final class LPPermissionMap extends ForwardingMap<String, Permission> {
public final class LuckPermsPermissionMap extends ForwardingMap<String, Permission> {
private static final Field PERMISSION_CHILDREN_FIELD;
@@ -82,7 +82,7 @@ public final class LPPermissionMap extends ForwardingMap<String, Permission> {
*/
final LuckPermsPlugin plugin;
public LPPermissionMap(LuckPermsPlugin plugin, Map<String, Permission> existingData) {
public LuckPermsPermissionMap(LuckPermsPlugin plugin, Map<String, Permission> existingData) {
this.plugin = plugin;
putAll(existingData);
}
@@ -116,9 +116,11 @@ public final class LPPermissionMap extends ForwardingMap<String, Permission> {
@Override
public void putAll(@NonNull Map<? extends String, ? extends Permission> m) {
for (Map.Entry<? extends String, ? extends Permission> ent : m.entrySet()) {
put(ent.getKey(), ent.getValue());
for (Map.Entry<? extends String, ? extends Permission> e : m.entrySet()) {
this.plugin.getPermissionRegistry().insert(e.getKey());
super.put(e.getKey(), inject(e.getValue()));
}
update();
}
@Override
@@ -174,6 +176,10 @@ public final class LPPermissionMap extends ForwardingMap<String, Permission> {
// iterate through the current known children.
// the first time this method is called for a given permission, the children map will contain only the permission itself.
for (Map.Entry<String, Boolean> e : children.entrySet()) {
if (e == null || e.getKey() == null || e.getValue() == null) {
continue;
}
if (accumulator.containsKey(e.getKey())) {
continue; // Prevent infinite loops
}
@@ -198,11 +204,11 @@ public final class LPPermissionMap extends ForwardingMap<String, Permission> {
try {
//noinspection unchecked
Map<String, Boolean> children = (Map<String, Boolean>) PERMISSION_CHILDREN_FIELD.get(permission);
while (children instanceof PermissionNotifyingChildrenMap) {
children = ((PermissionNotifyingChildrenMap) children).delegate;
while (children instanceof NotifyingChildrenMap) {
children = ((NotifyingChildrenMap) children).delegate;
}
PermissionNotifyingChildrenMap notifyingChildren = new PermissionNotifyingChildrenMap(children);
NotifyingChildrenMap notifyingChildren = new NotifyingChildrenMap(children);
PERMISSION_CHILDREN_FIELD.set(permission, notifyingChildren);
} catch (Exception e) {
e.printStackTrace();
@@ -218,8 +224,8 @@ public final class LPPermissionMap extends ForwardingMap<String, Permission> {
try {
//noinspection unchecked
Map<String, Boolean> children = (Map<String, Boolean>) PERMISSION_CHILDREN_FIELD.get(permission);
while (children instanceof PermissionNotifyingChildrenMap) {
children = ((PermissionNotifyingChildrenMap) children).delegate;
while (children instanceof NotifyingChildrenMap) {
children = ((NotifyingChildrenMap) children).delegate;
}
PERMISSION_CHILDREN_FIELD.set(permission, children);
} catch (Exception e) {
@@ -228,10 +234,10 @@ public final class LPPermissionMap extends ForwardingMap<String, Permission> {
return permission;
}
private final class PermissionNotifyingChildrenMap extends ForwardingMap<String, Boolean> {
private final class NotifyingChildrenMap extends ForwardingMap<String, Boolean> {
private final Map<String, Boolean> delegate;
PermissionNotifyingChildrenMap(Map<String, Boolean> delegate) {
NotifyingChildrenMap(Map<String, Boolean> delegate) {
this.delegate = delegate;
}
@@ -243,27 +249,27 @@ public final class LPPermissionMap extends ForwardingMap<String, Permission> {
@Override
public Boolean put(@NonNull String key, @NonNull Boolean value) {
Boolean ret = super.put(key, value);
LPPermissionMap.this.update();
LuckPermsPermissionMap.this.update();
return ret;
}
@Override
public void putAll(@NonNull Map<? extends String, ? extends Boolean> map) {
super.putAll(map);
LPPermissionMap.this.update();
LuckPermsPermissionMap.this.update();
}
@Override
public Boolean remove(@NonNull Object object) {
Boolean ret = super.remove(object);
LPPermissionMap.this.update();
LuckPermsPermissionMap.this.update();
return ret;
}
@Override
public void clear() {
super.clear();
LPPermissionMap.this.update();
LuckPermsPermissionMap.this.update();
}
}
@@ -26,7 +26,6 @@
package me.lucko.luckperms.bukkit.inject.server;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import me.lucko.luckperms.bukkit.LPBukkitPlugin;
import me.lucko.luckperms.common.util.ImmutableCollectors;
@@ -37,12 +36,14 @@ import org.bukkit.plugin.PluginManager;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.stream.Collectors;
import java.util.function.Function;
/**
* A replacement map for the 'permSubs' instance in Bukkit's SimplePluginManager.
@@ -66,14 +67,16 @@ import java.util.stream.Collectors;
*
* Injected by {@link InjectorSubscriptionMap}.
*/
public final class LPSubscriptionMap extends HashMap<String, Map<Permissible, Boolean>> {
public final class LuckPermsSubscriptionMap extends HashMap<String, Map<Permissible, Boolean>> {
// the plugin instance
final LPBukkitPlugin plugin;
public LPSubscriptionMap(LPBukkitPlugin plugin, Map<String, Map<Permissible, Boolean>> existingData) {
super(existingData);
public LuckPermsSubscriptionMap(LPBukkitPlugin plugin, Map<String, Map<Permissible, Boolean>> existingData) {
this.plugin = plugin;
for (Entry<String, Map<Permissible, Boolean>> entry : existingData.entrySet()) {
super.put(entry.getKey(), new LPSubscriptionValueMap(entry.getKey(), entry.getValue()));
}
}
/*
@@ -94,17 +97,13 @@ public final class LPSubscriptionMap extends HashMap<String, Map<Permissible, Bo
String permission = ((String) key);
Map<Permissible, Boolean> result = super.get(key);
LPSubscriptionValueMap result = (LPSubscriptionValueMap) super.get(key);
if (result == null) {
// calculate a new map - always!
result = new LPSubscriptionValueMap(permission);
super.put(permission, result);
} else if (!(result instanceof LPSubscriptionValueMap)) {
// ensure return type is a LPSubscriptionMap
result = new LPSubscriptionValueMap(permission, result);
super.put(permission, result);
}
return result;
}
@@ -121,6 +120,21 @@ public final class LPSubscriptionMap extends HashMap<String, Map<Permissible, Bo
return super.put(key, value);
}
@Override
public void putAll(Map<? extends String, ? extends Map<Permissible, Boolean>> m) {
m.forEach(this::put);
}
@Override
public Map<Permissible, Boolean> putIfAbsent(String key, Map<Permissible, Boolean> value) {
return get(key);
}
@Override
public Map<Permissible, Boolean> computeIfAbsent(String key, Function<? super String, ? extends Map<Permissible, Boolean>> mappingFunction) {
return get(key);
}
// if the key isn't null and is a string, #get will always return a value for it
@Override
public boolean containsKey(Object key) {
@@ -133,17 +147,11 @@ public final class LPSubscriptionMap extends HashMap<String, Map<Permissible, Bo
* @return a standard representation of this map
*/
public Map<String, Map<Permissible, Boolean>> detach() {
Map<String, Map<Permissible, Boolean>> ret = new HashMap<>();
Map<String, Map<Permissible, Boolean>> map = new HashMap<>();
for (Map.Entry<String, Map<Permissible, Boolean>> ent : entrySet()) {
if (ent.getValue() instanceof LPSubscriptionValueMap) {
ret.put(ent.getKey(), ((LPSubscriptionValueMap) ent.getValue()).backing);
} else {
ret.put(ent.getKey(), ent.getValue());
}
map.put(ent.getKey(), new WeakHashMap<>(((LPSubscriptionValueMap) ent.getValue()).backing));
}
return ret;
return map;
}
/**
@@ -159,7 +167,7 @@ public final class LPSubscriptionMap extends HashMap<String, Map<Permissible, Bo
private LPSubscriptionValueMap(String permission, Map<Permissible, Boolean> backing) {
this.permission = permission;
this.backing = new WeakHashMap<>(backing);
this.backing = Collections.synchronizedMap(new WeakHashMap<>(backing));
// remove all players from the map
this.backing.keySet().removeIf(p -> p instanceof Player);
@@ -167,7 +175,7 @@ public final class LPSubscriptionMap extends HashMap<String, Map<Permissible, Bo
public LPSubscriptionValueMap(String permission) {
this.permission = permission;
this.backing = new WeakHashMap<>();
this.backing = Collections.synchronizedMap(new WeakHashMap<>());
}
@Override
@@ -218,21 +226,28 @@ public final class LPSubscriptionMap extends HashMap<String, Map<Permissible, Bo
@Override
public @NonNull Set<Permissible> keySet() {
// gather players (LPPermissibles)
Set<Permissible> players = LPSubscriptionMap.this.plugin.getBootstrap().getServer().getOnlinePlayers().stream()
.filter(player -> player.hasPermission(this.permission) || player.isPermissionSet(this.permission))
.collect(Collectors.toSet());
// start with the backing set
Set<Permissible> set;
synchronized (this.backing) {
set = new HashSet<>(this.backing.keySet());
}
// then combine the players with the backing map
return Sets.union(players, this.backing.keySet());
// add any online players who meet requirements
for (Player player : LuckPermsSubscriptionMap.this.plugin.getBootstrap().getServer().getOnlinePlayers()) {
if (player.hasPermission(this.permission) || player.isPermissionSet(this.permission)) {
set.add(player);
}
}
return set;
}
@Override
public @NonNull Set<Entry<Permissible, Boolean>> entrySet() {
return keySet().stream()
.map(p -> {
Boolean ret = get(p);
return ret != null ? Maps.immutableEntry(p, ret) : null;
.map(key -> {
Boolean value = get(key);
return value != null ? Maps.immutableEntry(key, value) : null;
})
.filter(Objects::nonNull)
.collect(ImmutableCollectors.toSet());
@@ -0,0 +1,92 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.bukkit.listeners;
import me.lucko.luckperms.bukkit.LPBukkitPlugin;
import me.lucko.luckperms.common.api.implementation.ApiUser;
import me.lucko.luckperms.common.event.LuckPermsEventListener;
import me.lucko.luckperms.common.model.User;
import net.luckperms.api.event.EventBus;
import net.luckperms.api.event.context.ContextUpdateEvent;
import net.luckperms.api.event.user.UserDataRecalculateEvent;
import net.luckperms.api.query.QueryOptions;
import org.bukkit.entity.Player;
import java.util.Map;
/**
* Implements the LuckPerms auto op feature.
*/
public class BukkitAutoOpListener implements LuckPermsEventListener {
private static final String NODE = "luckperms.autoop";
private final LPBukkitPlugin plugin;
public BukkitAutoOpListener(LPBukkitPlugin plugin) {
this.plugin = plugin;
}
@Override
public void bind(EventBus bus) {
bus.subscribe(UserDataRecalculateEvent.class, this::onUserDataRecalculate);
bus.subscribe(ContextUpdateEvent.class, this::onContextUpdate);
}
private void onUserDataRecalculate(UserDataRecalculateEvent e) {
User user = ApiUser.cast(e.getUser());
this.plugin.getBootstrap().getPlayer(user.getUniqueId()).ifPresent(p -> refreshAutoOp(p, false));
}
private void onContextUpdate(ContextUpdateEvent e) {
e.getSubject(Player.class).ifPresent(p -> refreshAutoOp(p, true));
}
private void refreshAutoOp(Player player, boolean callerIsSync) {
if (!callerIsSync && this.plugin.getBootstrap().isServerStopping()) {
return;
}
User user = this.plugin.getUserManager().getIfLoaded(player.getUniqueId());
boolean value;
if (user != null) {
QueryOptions queryOptions = this.plugin.getContextManager().getQueryOptions(player);
Map<String, Boolean> permData = user.getCachedData().getPermissionData(queryOptions).getPermissionMap();
value = permData.getOrDefault(NODE, false);
} else {
value = false;
}
if (callerIsSync) {
player.setOp(value);
} else {
this.plugin.getBootstrap().getScheduler().executeSync(() -> player.setOp(value));
}
}
}
@@ -0,0 +1,118 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.bukkit.listeners;
import com.github.benmanes.caffeine.cache.LoadingCache;
import me.lucko.luckperms.bukkit.LPBukkitPlugin;
import me.lucko.luckperms.common.cache.BufferedRequest;
import me.lucko.luckperms.common.event.LuckPermsEventListener;
import me.lucko.luckperms.common.util.CaffeineFactory;
import net.luckperms.api.event.EventBus;
import net.luckperms.api.event.context.ContextUpdateEvent;
import net.luckperms.api.event.user.UserDataRecalculateEvent;
import org.bukkit.entity.Player;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
/**
* Calls {@link Player#updateCommands()} when a players permissions change.
*/
public class BukkitCommandListUpdater implements LuckPermsEventListener {
public static boolean isSupported() {
try {
Player.class.getMethod("updateCommands");
return true;
} catch (NoSuchMethodException e) {
return false;
}
}
private final LPBukkitPlugin plugin;
private final LoadingCache<UUID, SendBuffer> sendingBuffers = CaffeineFactory.newBuilder()
.expireAfterAccess(10, TimeUnit.SECONDS)
.build(SendBuffer::new);
public BukkitCommandListUpdater(LPBukkitPlugin plugin) {
this.plugin = plugin;
}
@Override
public void bind(EventBus bus) {
bus.subscribe(UserDataRecalculateEvent.class, this::onUserDataRecalculate);
bus.subscribe(ContextUpdateEvent.class, this::onContextUpdate);
}
private void onUserDataRecalculate(UserDataRecalculateEvent e) {
requestUpdate(e.getUser().getUniqueId());
}
private void onContextUpdate(ContextUpdateEvent e) {
e.getSubject(Player.class).ifPresent(p -> requestUpdate(p.getUniqueId()));
}
private void requestUpdate(UUID uniqueId) {
if (this.plugin.getBootstrap().isServerStopping()) {
return;
}
if (!this.plugin.getBootstrap().isPlayerOnline(uniqueId)) {
return;
}
// Buffer the request to send a commands update.
this.sendingBuffers.get(uniqueId).request();
}
// Called when the buffer times out.
private void sendUpdate(UUID uniqueId) {
if (this.plugin.getBootstrap().isServerStopping()) {
return;
}
this.plugin.getBootstrap().getScheduler().sync()
.execute(() -> this.plugin.getBootstrap().getPlayer(uniqueId).ifPresent(Player::updateCommands));
}
private final class SendBuffer extends BufferedRequest<Void> {
private final UUID uniqueId;
SendBuffer(UUID uniqueId) {
super(500, TimeUnit.MILLISECONDS, BukkitCommandListUpdater.this.plugin.getBootstrap().getScheduler());
this.uniqueId = uniqueId;
}
@Override
protected Void perform() {
sendUpdate(this.uniqueId);
return null;
}
}
}
@@ -26,7 +26,7 @@
package me.lucko.luckperms.bukkit.listeners;
import me.lucko.luckperms.bukkit.LPBukkitPlugin;
import me.lucko.luckperms.bukkit.inject.permissible.LPPermissible;
import me.lucko.luckperms.bukkit.inject.permissible.LuckPermsPermissible;
import me.lucko.luckperms.bukkit.inject.permissible.PermissibleInjector;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.locale.message.Message;
@@ -81,7 +81,8 @@ public class BukkitConnectionListener extends AbstractConnectionListener impleme
@EventHandler(priority = EventPriority.LOW)
public void onPlayerPreLogin(AsyncPlayerPreLoginEvent e) {
/* Called when the player first attempts a connection with the server.
Listening on LOW priority to allow plugins to modify username / UUID data here. (auth plugins) */
Listening on LOW priority to allow plugins to modify username / UUID data here. (auth plugins)
Also, give other plugins a chance to cancel the event. */
/* wait for the plugin to enable. because these events are fired async, they can be called before
the plugin has enabled. */
@@ -95,6 +96,13 @@ public class BukkitConnectionListener extends AbstractConnectionListener impleme
this.plugin.getLogger().info("Processing pre-login for " + e.getUniqueId() + " - " + e.getName());
}
if (e.getLoginResult() != AsyncPlayerPreLoginEvent.Result.ALLOWED) {
// another plugin has disallowed the login.
this.plugin.getLogger().info("Another plugin has cancelled the connection for " + e.getUniqueId() + " - " + e.getName() + ". No permissions data will be loaded.");
this.deniedAsyncLogin.add(e.getUniqueId());
return;
}
/* Actually process the login for the connection.
We do this here to delay the login until the data is ready.
If the login gets cancelled later on, then this will be cleaned up.
@@ -107,7 +115,7 @@ public class BukkitConnectionListener extends AbstractConnectionListener impleme
try {
User user = loadUser(e.getUniqueId(), e.getName());
recordConnection(e.getUniqueId());
this.plugin.getEventFactory().handlePlayerLoginProcess(e.getUniqueId(), e.getName(), user);
this.plugin.getEventDispatcher().dispatchPlayerLoginProcess(e.getUniqueId(), e.getName(), user);
} catch (Exception ex) {
this.plugin.getLogger().severe("Exception occurred whilst loading data for " + e.getUniqueId() + " - " + e.getName());
ex.printStackTrace();
@@ -115,7 +123,7 @@ public class BukkitConnectionListener extends AbstractConnectionListener impleme
// deny the connection
this.deniedAsyncLogin.add(e.getUniqueId());
e.disallow(AsyncPlayerPreLoginEvent.Result.KICK_OTHER, Message.LOADING_DATABASE_ERROR.asString(this.plugin.getLocaleManager()));
this.plugin.getEventFactory().handlePlayerLoginProcess(e.getUniqueId(), e.getName(), null);
this.plugin.getEventDispatcher().dispatchPlayerLoginProcess(e.getUniqueId(), e.getName(), null);
}
}
@@ -179,7 +187,7 @@ public class BukkitConnectionListener extends AbstractConnectionListener impleme
// Care should be taken at this stage to ensure that async tasks which manipulate bukkit data check that the player is still online.
try {
// Make a new permissible for the user
LPPermissible lpPermissible = new LPPermissible(player, user, this.plugin);
LuckPermsPermissible lpPermissible = new LuckPermsPermissible(player, user, this.plugin);
// Inject into the player
PermissibleInjector.inject(player, lpPermissible);
@@ -193,7 +201,7 @@ public class BukkitConnectionListener extends AbstractConnectionListener impleme
return;
}
this.plugin.refreshAutoOp(player, true);
this.plugin.getContextManager().signalContextUpdate(player);
}
@EventHandler(priority = EventPriority.MONITOR)
@@ -219,7 +227,7 @@ public class BukkitConnectionListener extends AbstractConnectionListener impleme
// perform unhooking from bukkit objects 1 tick later.
// this allows plugins listening after us on MONITOR to still have intact permissions data
this.plugin.getBootstrap().getServer().getScheduler().runTaskLaterAsynchronously(this.plugin.getBootstrap(), () -> {
this.plugin.getBootstrap().getServer().getScheduler().runTaskLater(this.plugin.getBootstrap(), () -> {
// Remove the custom permissible
try {
PermissibleInjector.uninject(player, true);
@@ -32,38 +32,40 @@ import me.lucko.luckperms.common.locale.message.Message;
import org.bukkit.command.CommandSender;
import org.bukkit.event.Cancellable;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerChangedWorldEvent;
import org.bukkit.event.player.PlayerCommandPreprocessEvent;
import org.bukkit.event.server.PluginEnableEvent;
import org.bukkit.event.server.RemoteServerCommandEvent;
import org.bukkit.event.server.ServerCommandEvent;
import java.util.regex.Pattern;
public class BukkitPlatformListener implements Listener {
private static final Pattern OP_COMMAND_PATTERN = Pattern.compile("^/?(\\w+:)?(deop|op)( .*)?$");
private final LPBukkitPlugin plugin;
public BukkitPlatformListener(LPBukkitPlugin plugin) {
this.plugin = plugin;
}
@EventHandler
@EventHandler(ignoreCancelled = true)
public void onPlayerCommand(PlayerCommandPreprocessEvent e) {
handleCommand(e.getPlayer(), e.getMessage().toLowerCase(), e);
}
@EventHandler
@EventHandler(ignoreCancelled = true)
public void onServerCommand(ServerCommandEvent e) {
handleCommand(e.getSender(), e.getCommand().toLowerCase(), e);
}
@EventHandler
@EventHandler(ignoreCancelled = true)
public void onRemoteServerCommand(RemoteServerCommandEvent e) {
handleCommand(e.getSender(), e.getCommand().toLowerCase(), e);
}
private void handleCommand(CommandSender sender, String s, Cancellable event) {
if (s.isEmpty()) {
private void handleCommand(CommandSender sender, String cmdLine, Cancellable event) {
if (cmdLine.isEmpty()) {
return;
}
@@ -71,15 +73,7 @@ public class BukkitPlatformListener implements Listener {
return;
}
if (s.charAt(0) == '/') {
s = s.substring(1);
}
if (s.contains(":")) {
s = s.substring(s.indexOf(':') + 1);
}
if (s.equals("op") || s.startsWith("op ") || s.equals("deop") || s.startsWith("deop ")) {
if (OP_COMMAND_PATTERN.matcher(cmdLine).matches()) {
event.setCancelled(true);
sender.sendMessage(Message.OP_DISABLED.asString(this.plugin.getLocaleManager()));
}
@@ -92,10 +86,4 @@ public class BukkitPlatformListener implements Listener {
}
}
@EventHandler(priority = EventPriority.LOWEST)
public void onWorldChange(PlayerChangedWorldEvent e) {
this.plugin.getContextManager().invalidateCache(e.getPlayer());
this.plugin.refreshAutoOp(e.getPlayer(), true);
}
}
@@ -32,8 +32,9 @@ import de.bananaco.bpermissions.api.World;
import de.bananaco.bpermissions.api.WorldManager;
import me.lucko.luckperms.common.command.CommandResult;
import me.lucko.luckperms.common.command.abstraction.SubCommand;
import me.lucko.luckperms.common.command.abstraction.ChildCommand;
import me.lucko.luckperms.common.command.access.CommandPermission;
import me.lucko.luckperms.common.command.utils.ArgumentList;
import me.lucko.luckperms.common.commands.migration.MigrationUtils;
import me.lucko.luckperms.common.locale.LocaleManager;
import me.lucko.luckperms.common.locale.command.CommandSpec;
@@ -61,13 +62,12 @@ import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
public class MigrationBPermissions extends SubCommand<Object> {
public class MigrationBPermissions extends ChildCommand<Object> {
private static final Field UCONFIG_FIELD;
static {
try {
@@ -83,7 +83,7 @@ public class MigrationBPermissions extends SubCommand<Object> {
}
@Override
public CommandResult execute(LuckPermsPlugin plugin, Sender sender, Object o, List<String> args, String label) {
public CommandResult execute(LuckPermsPlugin plugin, Sender sender, Object ignored, ArgumentList args, String label) {
ProgressLogger log = new ProgressLogger(Message.MIGRATION_LOG, Message.MIGRATION_LOG_PROGRESS, "bPermissions");
log.addListener(plugin.getConsoleSender());
log.addListener(sender);
@@ -182,6 +182,8 @@ public class MigrationBPermissions extends SubCommand<Object> {
});
log.log("Success! Migration complete.");
log.log("Don't forget to remove the bPermissions jar from your plugins folder & restart the server. " +
"LuckPerms may not take over as the server permission handler until this is done.");
return CommandResult.SUCCESS;
}
@@ -26,8 +26,9 @@
package me.lucko.luckperms.bukkit.migration;
import me.lucko.luckperms.common.command.CommandResult;
import me.lucko.luckperms.common.command.abstraction.SubCommand;
import me.lucko.luckperms.common.command.abstraction.ChildCommand;
import me.lucko.luckperms.common.command.access.CommandPermission;
import me.lucko.luckperms.common.command.utils.ArgumentList;
import me.lucko.luckperms.common.commands.migration.MigrationUtils;
import me.lucko.luckperms.common.locale.LocaleManager;
import me.lucko.luckperms.common.locale.command.CommandSpec;
@@ -68,13 +69,13 @@ import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.stream.Collectors;
public class MigrationGroupManager extends SubCommand<Object> {
public class MigrationGroupManager extends ChildCommand<Object> {
public MigrationGroupManager(LocaleManager locale) {
super(CommandSpec.MIGRATION_GROUPMANAGER.localize(locale), "groupmanager", CommandPermission.MIGRATION, Predicates.is(0));
}
@Override
public CommandResult execute(LuckPermsPlugin plugin, Sender sender, Object o, List<String> args, String label) {
public CommandResult execute(LuckPermsPlugin plugin, Sender sender, Object ignored, ArgumentList args, String label) {
ProgressLogger log = new ProgressLogger(Message.MIGRATION_LOG, Message.MIGRATION_LOG_PROGRESS, "GroupManager");
log.addListener(plugin.getConsoleSender());
log.addListener(sender);
@@ -86,7 +87,7 @@ public class MigrationGroupManager extends SubCommand<Object> {
return CommandResult.STATE_ERROR;
}
final boolean migrateAsGlobal = Boolean.parseBoolean(args.get(0));
final Function<String, String> worldMappingFunc = s -> migrateAsGlobal ? null : s;
final Function<String, String> worldMappingFunc = s -> migrateAsGlobal || s == null ? "global" : s;
if (!Bukkit.getPluginManager().isPluginEnabled("GroupManager")) {
log.logError("Plugin not loaded.");
@@ -145,7 +146,7 @@ public class MigrationGroupManager extends SubCommand<Object> {
}
for (String s : group.getInherits()) {
if (s.isEmpty()) continue;
groups.get(groupName).add(Inheritance.builder(MigrationUtils.standardizeName(s)).value(true).withContext(DefaultContextKeys.SERVER_KEY, null).withContext(DefaultContextKeys.WORLD_KEY, worldMappingFunc.apply(world)).build());
groups.get(groupName).add(Inheritance.builder(MigrationUtils.standardizeName(s)).value(true).withContext(DefaultContextKeys.WORLD_KEY, worldMappingFunc.apply(world)).build());
}
String[] metaKeys = group.getVariables().getVarKeyList();
@@ -262,6 +263,8 @@ public class MigrationGroupManager extends SubCommand<Object> {
log.log("Migrated " + userCount.get() + " users.");
log.log("Success! Migration complete.");
log.log("Don't forget to remove the GroupManager jar from your plugins folder & restart the server. " +
"LuckPerms may not take over as the server permission handler until this is done.");
return CommandResult.SUCCESS;
}
}
@@ -28,8 +28,9 @@ package me.lucko.luckperms.bukkit.migration;
import com.platymuus.bukkit.permissions.PermissionsPlugin;
import me.lucko.luckperms.common.command.CommandResult;
import me.lucko.luckperms.common.command.abstraction.SubCommand;
import me.lucko.luckperms.common.command.abstraction.ChildCommand;
import me.lucko.luckperms.common.command.access.CommandPermission;
import me.lucko.luckperms.common.command.utils.ArgumentList;
import me.lucko.luckperms.common.commands.migration.MigrationUtils;
import me.lucko.luckperms.common.locale.LocaleManager;
import me.lucko.luckperms.common.locale.command.CommandSpec;
@@ -56,13 +57,13 @@ import java.util.List;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
public class MigrationPermissionsBukkit extends SubCommand<Object> {
public class MigrationPermissionsBukkit extends ChildCommand<Object> {
public MigrationPermissionsBukkit(LocaleManager locale) {
super(CommandSpec.MIGRATION_COMMAND.localize(locale), "permissionsbukkit", CommandPermission.MIGRATION, Predicates.alwaysFalse());
}
@Override
public CommandResult execute(LuckPermsPlugin plugin, Sender sender, Object o, List<String> args, String label) {
public CommandResult execute(LuckPermsPlugin plugin, Sender sender, Object ignored, ArgumentList args, String label) {
ProgressLogger log = new ProgressLogger(Message.MIGRATION_LOG, Message.MIGRATION_LOG_PROGRESS, "PermissionsBukkit");
log.addListener(plugin.getConsoleSender());
log.addListener(sender);
@@ -123,6 +124,8 @@ public class MigrationPermissionsBukkit extends SubCommand<Object> {
log.log("Migrated " + userCount.get() + " users.");
log.log("Success! Migration complete.");
log.log("Don't forget to remove the PermissionsBukkit jar from your plugins folder & restart the server. " +
"LuckPerms may not take over as the server permission handler until this is done.");
return CommandResult.SUCCESS;
}
@@ -28,8 +28,9 @@ package me.lucko.luckperms.bukkit.migration;
import com.google.common.base.Strings;
import me.lucko.luckperms.common.command.CommandResult;
import me.lucko.luckperms.common.command.abstraction.SubCommand;
import me.lucko.luckperms.common.command.abstraction.ChildCommand;
import me.lucko.luckperms.common.command.access.CommandPermission;
import me.lucko.luckperms.common.command.utils.ArgumentList;
import me.lucko.luckperms.common.commands.migration.MigrationUtils;
import me.lucko.luckperms.common.locale.LocaleManager;
import me.lucko.luckperms.common.locale.command.CommandSpec;
@@ -52,19 +53,24 @@ import me.lucko.luckperms.common.util.ProgressLogger;
import net.luckperms.api.context.DefaultContextKeys;
import net.luckperms.api.event.cause.CreationCause;
import net.luckperms.api.model.data.DataType;
import net.luckperms.api.node.Node;
import net.luckperms.api.node.types.InheritanceNode;
import org.bukkit.Bukkit;
import ru.tehkode.permissions.NativeInterface;
import ru.tehkode.permissions.PermissionEntity;
import ru.tehkode.permissions.PermissionGroup;
import ru.tehkode.permissions.PermissionManager;
import ru.tehkode.permissions.PermissionUser;
import ru.tehkode.permissions.PermissionsData;
import ru.tehkode.permissions.bukkit.PermissionsEx;
import ru.tehkode.permissions.events.PermissionEvent;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
@@ -74,10 +80,11 @@ import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
public class MigrationPermissionsEx extends SubCommand<Object> {
public class MigrationPermissionsEx extends ChildCommand<Object> {
private static final Method GET_DATA_METHOD;
private static final Field TIMED_PERMISSIONS_FIELD;
private static final Field TIMED_PERMISSIONS_TIME_FIELD;
private static final Field NATIVE_INTERFACE_FIELD;
static {
try {
GET_DATA_METHOD = PermissionEntity.class.getDeclaredMethod("getData");
@@ -88,6 +95,9 @@ public class MigrationPermissionsEx extends SubCommand<Object> {
TIMED_PERMISSIONS_TIME_FIELD = PermissionEntity.class.getDeclaredField("timedPermissionsTime");
TIMED_PERMISSIONS_TIME_FIELD.setAccessible(true);
NATIVE_INTERFACE_FIELD = PermissionManager.class.getDeclaredField("nativeI");
NATIVE_INTERFACE_FIELD.setAccessible(true);
} catch (NoSuchMethodException | NoSuchFieldException e) {
throw new ExceptionInInitializerError(e);
}
@@ -98,7 +108,7 @@ public class MigrationPermissionsEx extends SubCommand<Object> {
}
@Override
public CommandResult execute(LuckPermsPlugin plugin, Sender sender, Object o, List<String> args, String label) {
public CommandResult execute(LuckPermsPlugin plugin, Sender sender, Object ignored, ArgumentList args, String label) {
ProgressLogger log = new ProgressLogger(Message.MIGRATION_LOG, Message.MIGRATION_LOG_PROGRESS, "PermissionsEx");
log.addListener(plugin.getConsoleSender());
log.addListener(sender);
@@ -113,6 +123,13 @@ public class MigrationPermissionsEx extends SubCommand<Object> {
PermissionsEx pex = (PermissionsEx) Bukkit.getPluginManager().getPlugin("PermissionsEx");
PermissionManager manager = pex.getPermissionsManager();
// hack to work around accessing pex async
try {
disablePexEvents(manager);
} catch (ReflectiveOperationException e) {
e.printStackTrace();
}
log.log("Calculating group weightings.");
int i = 0;
for (PermissionGroup group : manager.getGroupList()) {
@@ -168,8 +185,14 @@ public class MigrationPermissionsEx extends SubCommand<Object> {
// Increment the max weight from the group migrations. All user meta should override.
int userWeight = maxWeight + 5;
Iterators.tryIterate(manager.getUsers(), user -> {
UUID u = BukkitUuids.lookupUuid(log, user.getIdentifier());
Collection<String> userIdentifiers = manager.getBackend().getUserIdentifiers();
Iterators.tryIterate(userIdentifiers, id -> {
PermissionUser user = new PermissionUser(id, manager.getBackend().getUserData(id), manager);
if (isUserEmpty(user)) {
return;
}
UUID u = BukkitUuids.lookupUuid(log, id);
if (u == null) {
return;
}
@@ -185,8 +208,17 @@ public class MigrationPermissionsEx extends SubCommand<Object> {
log.logProgress("Migrated {} users so far.", userCount.incrementAndGet(), ProgressLogger.DEFAULT_NOTIFY_FREQUENCY);
});
// re-enable events
try {
enablePexEvents(manager);
} catch (ReflectiveOperationException e) {
e.printStackTrace();
}
log.log("Migrated " + userCount.get() + " users.");
log.log("Success! Migration complete.");
log.log("Don't forget to remove the PermissionsEx jar from your plugins folder & restart the server. " +
"LuckPerms may not take over as the server permission handler until this is done.");
return CommandResult.SUCCESS;
}
@@ -199,6 +231,28 @@ public class MigrationPermissionsEx extends SubCommand<Object> {
}
}
private static boolean isUserEmpty(PermissionUser user) {
for (List<String> permissions : user.getAllPermissions().values()) {
if (!permissions.isEmpty()) {
return false;
}
}
for (List<PermissionGroup> parents : user.getAllParents().values()) {
if (!parents.isEmpty()) {
return false;
}
}
for (Map<String, String> options : user.getAllOptions().values()) {
if (!options.isEmpty()) {
return false;
}
}
return true;
}
private static void migrateEntity(PermissionEntity entity, PermissionHolder holder, int weight) {
// migrate permanent permissions
for (Map.Entry<String, List<String>> worldData : getPermanentPermissions(entity).entrySet()) {
@@ -227,7 +281,10 @@ public class MigrationPermissionsEx extends SubCommand<Object> {
for (String node : worldData.getValue()) {
if (node.isEmpty()) continue;
long expiry = timedPermissionsTime.getOrDefault(Strings.nullToEmpty(world) + ":" + node, 0L);
holder.setNode(DataType.NORMAL, MigrationUtils.parseNode(node, true).withContext(DefaultContextKeys.WORLD_KEY, world).expiry(expiry).build(), true);
Node n = MigrationUtils.parseNode(node, true).withContext(DefaultContextKeys.WORLD_KEY, world).expiry(expiry).build();
if (!n.hasExpired()) {
holder.setNode(DataType.NORMAL, n, true);
}
}
}
@@ -255,10 +312,15 @@ public class MigrationPermissionsEx extends SubCommand<Object> {
}
}
holder.setNode(DataType.NORMAL, Inheritance.builder(MigrationUtils.standardizeName(parentName)).withContext(DefaultContextKeys.WORLD_KEY, world).expiry(expiry).build(), true);
InheritanceNode n = Inheritance.builder(MigrationUtils.standardizeName(parentName)).withContext(DefaultContextKeys.WORLD_KEY, world).expiry(expiry).build();
if (n.hasExpired()) {
continue;
}
holder.setNode(DataType.NORMAL, n, true);
// migrate primary groups
if (world == null && holder instanceof User && expiry == 0) {
if (world.equals("global") && holder instanceof User && expiry == 0) {
if (parent.getRank() < primaryWeight) {
primary = parent.getName();
primaryWeight = parent.getRank();
@@ -313,12 +375,45 @@ public class MigrationPermissionsEx extends SubCommand<Object> {
}
private static String standardizeWorld(String world) {
if (world != null && (world.isEmpty() || world.equals("*"))) {
world = null;
if (world == null || world.isEmpty() || world.equals("*")) {
world = "global";
}
if (world != null) {
world = world.toLowerCase();
return world.toLowerCase();
}
/*
* Hack to workaround issue with accessing PEX async.
* See: https://github.com/lucko/LuckPerms/issues/2102
*/
private static void disablePexEvents(PermissionManager manager) throws ReflectiveOperationException {
NativeInterface nativeInterface = (NativeInterface) NATIVE_INTERFACE_FIELD.get(manager);
NATIVE_INTERFACE_FIELD.set(manager, new DisabledEventsNativeInterface(nativeInterface));
}
private static void enablePexEvents(PermissionManager manager) throws ReflectiveOperationException {
NativeInterface nativeInterface = (NativeInterface) NATIVE_INTERFACE_FIELD.get(manager);
while (nativeInterface instanceof DisabledEventsNativeInterface) {
nativeInterface = ((DisabledEventsNativeInterface) nativeInterface).delegate;
NATIVE_INTERFACE_FIELD.set(manager, nativeInterface);
}
return world;
}
private static final class DisabledEventsNativeInterface implements NativeInterface {
private final NativeInterface delegate;
private DisabledEventsNativeInterface(NativeInterface delegate) {
this.delegate = delegate;
}
@Override
public void callEvent(PermissionEvent permissionEvent) {
// do nothing!
}
@Override public String UUIDToName(UUID uuid) { return this.delegate.UUIDToName(uuid); }
@Override public UUID nameToUUID(String s) { return this.delegate.nameToUUID(s); }
@Override public boolean isOnline(UUID uuid) { return this.delegate.isOnline(uuid); }
@Override public UUID getServerUUID() { return this.delegate.getServerUUID(); }
}
}
@@ -34,8 +34,9 @@ import com.google.common.collect.ImmutableSet;
import com.zaxxer.hikari.HikariDataSource;
import me.lucko.luckperms.common.command.CommandResult;
import me.lucko.luckperms.common.command.abstraction.SubCommand;
import me.lucko.luckperms.common.command.abstraction.ChildCommand;
import me.lucko.luckperms.common.command.access.CommandPermission;
import me.lucko.luckperms.common.command.utils.ArgumentList;
import me.lucko.luckperms.common.commands.migration.MigrationUtils;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.locale.LocaleManager;
@@ -78,13 +79,13 @@ import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicInteger;
// Only supports the latest versions of the PP API. (it seems to change randomly almost every release)
public class MigrationPowerfulPerms extends SubCommand<Object> {
public class MigrationPowerfulPerms extends ChildCommand<Object> {
public MigrationPowerfulPerms(LocaleManager locale) {
super(CommandSpec.MIGRATION_POWERFULPERMS.localize(locale), "powerfulperms", CommandPermission.MIGRATION, Predicates.not(5));
}
@Override
public CommandResult execute(LuckPermsPlugin plugin, Sender sender, Object o, List<String> args, String label) {
public CommandResult execute(LuckPermsPlugin plugin, Sender sender, Object ignored, ArgumentList args, String label) {
ProgressLogger log = new ProgressLogger(Message.MIGRATION_LOG, Message.MIGRATION_LOG_PROGRESS, "PowerfulPerms");
log.addListener(plugin.getConsoleSender());
log.addListener(sender);
@@ -275,6 +276,8 @@ public class MigrationPowerfulPerms extends SubCommand<Object> {
log.log("Migrated " + userCount.get() + " users.");
log.log("Success! Migration complete.");
log.log("Don't forget to remove the PowerfulPerms jar from your plugins folder & restart the server. " +
"LuckPerms may not take over as the server permission handler until this is done.");
return CommandResult.SUCCESS;
}
@@ -291,12 +294,12 @@ public class MigrationPowerfulPerms extends SubCommand<Object> {
}
String server = p.getServer();
if (server != null && (server.equalsIgnoreCase("all") || server.equalsIgnoreCase("*"))) {
if (server != null && (server.equalsIgnoreCase("all") || server.equalsIgnoreCase("*") || server.isEmpty())) {
server = null;
}
String world = p.getWorld();
if (world != null && (world.equalsIgnoreCase("all") || world.equalsIgnoreCase("*"))) {
if (world != null && (world.equalsIgnoreCase("all") || world.equalsIgnoreCase("*") || world.isEmpty())) {
world = null;
}
@@ -309,7 +312,7 @@ public class MigrationPowerfulPerms extends SubCommand<Object> {
server = "global";
}
NodeBuilder nb = NodeBuilders.determineMostApplicable(node).value(value);
NodeBuilder<?, ?> nb = NodeBuilders.determineMostApplicable(node).value(value);
if (expireAt != 0) nb.expiry(expireAt);
if (server != null) nb.withContext(DefaultContextKeys.SERVER_KEY, server);
if (world != null) nb.withContext(DefaultContextKeys.WORLD_KEY, world);
@@ -325,7 +328,7 @@ public class MigrationPowerfulPerms extends SubCommand<Object> {
expireAt = g.getExpirationDate().getTime() / 1000L;
}
NodeBuilder nb = Inheritance.builder(MigrationUtils.standardizeName(group.getName()));
NodeBuilder<?, ?> nb = Inheritance.builder(MigrationUtils.standardizeName(group.getName()));
if (expireAt != 0) {
nb.expiry(expireAt);
@@ -26,8 +26,9 @@
package me.lucko.luckperms.bukkit.migration;
import me.lucko.luckperms.common.command.CommandResult;
import me.lucko.luckperms.common.command.abstraction.SubCommand;
import me.lucko.luckperms.common.command.abstraction.ChildCommand;
import me.lucko.luckperms.common.command.access.CommandPermission;
import me.lucko.luckperms.common.command.utils.ArgumentList;
import me.lucko.luckperms.common.commands.migration.MigrationUtils;
import me.lucko.luckperms.common.locale.LocaleManager;
import me.lucko.luckperms.common.locale.command.CommandSpec;
@@ -63,19 +64,18 @@ import org.tyrannyofheaven.bukkit.zPermissions.model.PermissionEntity;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
public class MigrationZPermissions extends SubCommand<Object> {
public class MigrationZPermissions extends ChildCommand<Object> {
public MigrationZPermissions(LocaleManager locale) {
super(CommandSpec.MIGRATION_COMMAND.localize(locale), "zpermissions", CommandPermission.MIGRATION, Predicates.alwaysFalse());
}
@Override
public CommandResult execute(LuckPermsPlugin plugin, Sender sender, Object o, List<String> args, String label) {
public CommandResult execute(LuckPermsPlugin plugin, Sender sender, Object ignored, ArgumentList args, String label) {
ProgressLogger log = new ProgressLogger(Message.MIGRATION_LOG, Message.MIGRATION_LOG_PROGRESS, "zPermissions");
log.addListener(plugin.getConsoleSender());
log.addListener(sender);
@@ -193,6 +193,8 @@ public class MigrationZPermissions extends SubCommand<Object> {
log.log("Migrated " + userCount.get() + " users.");
log.log("Success! Migration complete.");
log.log("Don't forget to remove the zPermissions jar from your plugins folder & restart the server. " +
"LuckPerms may not take over as the server permission handler until this is done.");
return CommandResult.SUCCESS;
}
@@ -0,0 +1,56 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.bukkit.util;
import org.bukkit.Server;
import org.bukkit.command.CommandMap;
import org.bukkit.plugin.SimplePluginManager;
import java.lang.reflect.Field;
public final class CommandMapUtil {
private CommandMapUtil() {}
private static final Field COMMAND_MAP_FIELD;
static {
try {
COMMAND_MAP_FIELD = SimplePluginManager.class.getDeclaredField("commandMap");
COMMAND_MAP_FIELD.setAccessible(true);
} catch (NoSuchFieldException e) {
throw new ExceptionInInitializerError(e);
}
}
public static CommandMap getCommandMap(Server server) {
try {
return (CommandMap) COMMAND_MAP_FIELD.get(server.getPluginManager());
} catch (Exception e) {
throw new RuntimeException("Could not get CommandMap", e);
}
}
}
@@ -23,42 +23,28 @@
* SOFTWARE.
*/
package me.lucko.luckperms.bukkit.compat;
package me.lucko.luckperms.bukkit.util;
import org.bukkit.Bukkit;
public final class CraftBukkitUtil {
private CraftBukkitUtil() {}
import java.util.regex.Matcher;
import java.util.regex.Pattern;
private static final String SERVER_PACKAGE_VERSION = getServerPackageVersion();
private static final boolean CHAT_COMPATIBLE = !SERVER_PACKAGE_VERSION.startsWith(".v1_7_");
public final class CraftBukkitImplementation {
private CraftBukkitImplementation() {}
private static String getServerPackageVersion() {
private static final String SERVER_PACKAGE_VERSION;
static {
Class<?> server = Bukkit.getServer().getClass();
if (!server.getSimpleName().equals("CraftServer")) {
return ".";
}
if (server.getName().equals("org.bukkit.craftbukkit.CraftServer")) {
// Non versioned class
return ".";
Matcher matcher = Pattern.compile("^org\\.bukkit\\.craftbukkit\\.(\\w+)\\.CraftServer$").matcher(server.getName());
if (matcher.matches()) {
SERVER_PACKAGE_VERSION = '.' + matcher.group(1) + '.';
} else {
String version = server.getName().substring("org.bukkit.craftbukkit".length());
return version.substring(0, version.length() - "CraftServer".length());
SERVER_PACKAGE_VERSION = ".";
}
}
public static boolean isChatCompatible() {
return CHAT_COMPATIBLE;
}
public static String nms(String className) {
return "net.minecraft.server" + SERVER_PACKAGE_VERSION + className;
}
public static Class<?> nmsClass(String className) throws ClassNotFoundException {
return Class.forName(nms(className));
}
public static String obc(String className) {
return "org.bukkit.craftbukkit" + SERVER_PACKAGE_VERSION + className;
}
@@ -23,7 +23,7 @@
* SOFTWARE.
*/
package me.lucko.luckperms.bukkit.compat;
package me.lucko.luckperms.bukkit.util;
import org.bukkit.ChatColor;
import org.bukkit.Server;
@@ -0,0 +1,71 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.bukkit.util;
import com.google.common.graph.MutableGraph;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.SimplePluginManager;
import java.lang.reflect.Field;
public final class PluginManagerUtil {
private PluginManagerUtil() {}
private static final Field DEPENDENCY_GRAPH_FIELD;
static {
Field dependencyGraphField = null;
try {
dependencyGraphField = SimplePluginManager.class.getDeclaredField("dependencyGraph");
dependencyGraphField.setAccessible(true);
} catch (Exception e) {
// ignore
}
DEPENDENCY_GRAPH_FIELD = dependencyGraphField;
}
/**
* Injects a dependency relationship into the plugin manager.
*
* @param plugin the plugin
* @param depend the plugin being depended on
*/
@SuppressWarnings("unchecked")
public static void injectDependency(PluginManager pluginManager, String plugin, String depend) {
if (DEPENDENCY_GRAPH_FIELD == null || !(pluginManager instanceof SimplePluginManager)) {
return; // fail silently
}
try {
MutableGraph<String> graph = (MutableGraph<String>) DEPENDENCY_GRAPH_FIELD.get(pluginManager);
graph.putEdge(plugin, depend);
} catch (Exception e) {
// ignore
}
}
}
@@ -33,7 +33,6 @@ import org.bukkit.entity.Player;
import java.util.Objects;
import java.util.UUID;
import java.util.function.Function;
/**
* An extended abstraction of the Vault {@link Chat} API.
@@ -45,7 +44,7 @@ import java.util.function.Function;
* to get a result using the players username.
*
* Whilst this meant the old plugins would still be supported, it made the whole
* API a total mess. This class reverses this decision, and instead upgrades
* API a total mess. This class reverses this action, and instead upgrades
* requests to use UUIDs. This makes implementing Vault significantly easier for modern
* plugins, and because requests are upgraded instead of being downgraded then upgraded,
* much faster for plugins querying data.
@@ -53,10 +52,6 @@ import java.util.function.Function;
@SuppressWarnings("deprecation")
public abstract class AbstractVaultChat extends Chat {
// when upgrading and forwarding requests, all world strings are passed through this function.
// it lets the overriding class define some custom behaviour for world handling.
protected Function<String, String> worldMappingFunction = Function.identity();
// the permission api instance
private final AbstractVaultPermission permissionApi;
@@ -87,14 +82,14 @@ public abstract class AbstractVaultChat extends Chat {
// utility methods for parsing metadata values from strings
private static String strConvert(String s, String def) {
if (s != null) {
return s;
private static String parseString(String s, String def) {
if (s == null) {
return def;
}
return def;
return s;
}
private static int intConvert(String s, int def) {
private static int parseInt(String s, int def) {
if (s == null) {
return def;
}
@@ -105,7 +100,7 @@ public abstract class AbstractVaultChat extends Chat {
}
}
private static double doubleConvert(String s, double def) {
private static double parseDouble(String s, double def) {
if (s == null) {
return def;
}
@@ -116,7 +111,7 @@ public abstract class AbstractVaultChat extends Chat {
}
}
private static boolean booleanConvert(String s, boolean def) {
private static boolean parseBoolean(String s, boolean def) {
if (s == null) {
return def;
}
@@ -124,506 +119,508 @@ public abstract class AbstractVaultChat extends Chat {
return true;
} else if (s.equalsIgnoreCase("false")) {
return false;
} else {
return def;
}
return def;
}
private String world(String world) {
return this.worldMappingFunction.apply(world);
/* allow implementations to override and implement custom world remapping / handling rules */
protected String convertWorld(String world) {
return world;
}
private String world(Player player) {
if (player == null) {
return null;
}
return world(player.getWorld());
}
private String world(World world) {
private String convertWorld(World world) {
if (world == null) {
return null;
}
return world(world.getName());
return convertWorld(world.getName());
}
private String convertWorld(Player player) {
if (player == null) {
return null;
}
return convertWorld(player.getWorld());
}
@Override
public final String getPlayerPrefix(String world, String player) {
Objects.requireNonNull(player, "player");
return getUserChatPrefix(world(world), this.permissionApi.lookupUuid(player));
return getUserChatPrefix(convertWorld(world), this.permissionApi.lookupUuid(player));
}
@Override
public final String getPlayerPrefix(String world, OfflinePlayer player) {
Objects.requireNonNull(player, "player");
return getUserChatPrefix(world(world), player.getUniqueId());
return getUserChatPrefix(convertWorld(world), player.getUniqueId());
}
@Override
public final String getPlayerPrefix(World world, String player) {
Objects.requireNonNull(player, "player");
return getUserChatPrefix(world(world), this.permissionApi.lookupUuid(player));
return getUserChatPrefix(convertWorld(world), this.permissionApi.lookupUuid(player));
}
@Override
public final String getPlayerPrefix(Player player) {
Objects.requireNonNull(player, "player");
return getUserChatPrefix(world(player), ((OfflinePlayer) player).getUniqueId());
return getUserChatPrefix(convertWorld(player), ((OfflinePlayer) player).getUniqueId());
}
@Override
public final void setPlayerPrefix(String world, String player, String prefix) {
Objects.requireNonNull(player, "player");
setUserChatPrefix(world(world), this.permissionApi.lookupUuid(player), prefix);
setUserChatPrefix(convertWorld(world), this.permissionApi.lookupUuid(player), prefix);
}
@Override
public final void setPlayerPrefix(String world, OfflinePlayer player, String prefix) {
Objects.requireNonNull(player, "player");
setUserChatPrefix(world(world), player.getUniqueId(), prefix);
setUserChatPrefix(convertWorld(world), player.getUniqueId(), prefix);
}
@Override
public final void setPlayerPrefix(World world, String player, String prefix) {
Objects.requireNonNull(player, "player");
setUserChatPrefix(world(world), this.permissionApi.lookupUuid(player), prefix);
setUserChatPrefix(convertWorld(world), this.permissionApi.lookupUuid(player), prefix);
}
@Override
public final void setPlayerPrefix(Player player, String prefix) {
Objects.requireNonNull(player, "player");
setUserChatPrefix(world(player), ((OfflinePlayer) player).getUniqueId(), prefix);
setUserChatPrefix(convertWorld(player), ((OfflinePlayer) player).getUniqueId(), prefix);
}
@Override
public final String getPlayerSuffix(String world, String player) {
Objects.requireNonNull(player, "player");
return getUserChatSuffix(world(world), this.permissionApi.lookupUuid(player));
return getUserChatSuffix(convertWorld(world), this.permissionApi.lookupUuid(player));
}
@Override
public final String getPlayerSuffix(String world, OfflinePlayer player) {
Objects.requireNonNull(player, "player");
return getUserChatSuffix(world(world), player.getUniqueId());
return getUserChatSuffix(convertWorld(world), player.getUniqueId());
}
@Override
public final String getPlayerSuffix(World world, String player) {
Objects.requireNonNull(player, "player");
return getUserChatSuffix(world(world), this.permissionApi.lookupUuid(player));
return getUserChatSuffix(convertWorld(world), this.permissionApi.lookupUuid(player));
}
@Override
public final String getPlayerSuffix(Player player) {
Objects.requireNonNull(player, "player");
return getUserChatSuffix(world(player), ((OfflinePlayer) player).getUniqueId());
return getUserChatSuffix(convertWorld(player), ((OfflinePlayer) player).getUniqueId());
}
@Override
public final void setPlayerSuffix(String world, String player, String suffix) {
Objects.requireNonNull(player, "player");
setUserChatSuffix(world(world), this.permissionApi.lookupUuid(player), suffix);
setUserChatSuffix(convertWorld(world), this.permissionApi.lookupUuid(player), suffix);
}
@Override
public final void setPlayerSuffix(String world, OfflinePlayer player, String suffix) {
Objects.requireNonNull(player, "player");
setUserChatSuffix(world(world), player.getUniqueId(), suffix);
setUserChatSuffix(convertWorld(world), player.getUniqueId(), suffix);
}
@Override
public final void setPlayerSuffix(World world, String player, String suffix) {
Objects.requireNonNull(player, "player");
setUserChatSuffix(world(world), this.permissionApi.lookupUuid(player), suffix);
setUserChatSuffix(convertWorld(world), this.permissionApi.lookupUuid(player), suffix);
}
@Override
public final void setPlayerSuffix(Player player, String suffix) {
Objects.requireNonNull(player, "player");
setUserChatSuffix(world(player), ((OfflinePlayer) player).getUniqueId(), suffix);
setUserChatSuffix(convertWorld(player), ((OfflinePlayer) player).getUniqueId(), suffix);
}
@Override
public final String getGroupPrefix(String world, String group) {
Objects.requireNonNull(group, "group");
return getGroupChatPrefix(world(world), group);
return getGroupChatPrefix(convertWorld(world), group);
}
@Override
public final String getGroupPrefix(World world, String group) {
Objects.requireNonNull(group, "group");
return getGroupChatPrefix(world(world), group);
return getGroupChatPrefix(convertWorld(world), group);
}
@Override
public final void setGroupPrefix(String world, String group, String prefix) {
Objects.requireNonNull(group, "group");
setGroupChatPrefix(world(world), group, prefix);
setGroupChatPrefix(convertWorld(world), group, prefix);
}
@Override
public final void setGroupPrefix(World world, String group, String prefix) {
Objects.requireNonNull(group, "group");
setGroupChatPrefix(world(world), group, prefix);
setGroupChatPrefix(convertWorld(world), group, prefix);
}
@Override
public final String getGroupSuffix(String world, String group) {
Objects.requireNonNull(group, "group");
return getGroupChatSuffix(world(world), group);
return getGroupChatSuffix(convertWorld(world), group);
}
@Override
public final String getGroupSuffix(World world, String group) {
Objects.requireNonNull(group, "group");
return getGroupChatSuffix(world(world), group);
return getGroupChatSuffix(convertWorld(world), group);
}
@Override
public final void setGroupSuffix(String world, String group, String suffix) {
Objects.requireNonNull(group, "group");
setGroupChatSuffix(world(world), group, suffix);
setGroupChatSuffix(convertWorld(world), group, suffix);
}
@Override
public final void setGroupSuffix(World world, String group, String suffix) {
Objects.requireNonNull(group, "group");
setGroupChatSuffix(world(world), group, suffix);
setGroupChatSuffix(convertWorld(world), group, suffix);
}
@Override
public final int getPlayerInfoInteger(String world, OfflinePlayer player, String node, int defaultValue) {
Objects.requireNonNull(player, "player");
Objects.requireNonNull(node, "node");
return intConvert(getUserMeta(world(world), player.getUniqueId(), node), defaultValue);
return parseInt(getUserMeta(convertWorld(world), player.getUniqueId(), node), defaultValue);
}
@Override
public final int getPlayerInfoInteger(String world, String player, String node, int defaultValue) {
Objects.requireNonNull(player, "player");
Objects.requireNonNull(node, "node");
return intConvert(getUserMeta(world(world), this.permissionApi.lookupUuid(player), node), defaultValue);
return parseInt(getUserMeta(convertWorld(world), this.permissionApi.lookupUuid(player), node), defaultValue);
}
@Override
public final int getPlayerInfoInteger(World world, String player, String node, int defaultValue) {
Objects.requireNonNull(player, "player");
Objects.requireNonNull(node, "node");
return intConvert(getUserMeta(world(world), this.permissionApi.lookupUuid(player), node), defaultValue);
return parseInt(getUserMeta(convertWorld(world), this.permissionApi.lookupUuid(player), node), defaultValue);
}
@Override
public final int getPlayerInfoInteger(Player player, String node, int defaultValue) {
Objects.requireNonNull(player, "player");
Objects.requireNonNull(node, "node");
return intConvert(getUserMeta(world(player), ((OfflinePlayer) player).getUniqueId(), node), defaultValue);
return parseInt(getUserMeta(convertWorld(player), ((OfflinePlayer) player).getUniqueId(), node), defaultValue);
}
@Override
public final void setPlayerInfoInteger(String world, OfflinePlayer player, String node, int value) {
Objects.requireNonNull(player, "player");
Objects.requireNonNull(node, "node");
setUserMeta(world(world), player.getUniqueId(), node, value);
setUserMeta(convertWorld(world), player.getUniqueId(), node, value);
}
@Override
public final void setPlayerInfoInteger(String world, String player, String node, int value) {
Objects.requireNonNull(player, "player");
Objects.requireNonNull(node, "node");
setUserMeta(world(world), this.permissionApi.lookupUuid(player), node, value);
setUserMeta(convertWorld(world), this.permissionApi.lookupUuid(player), node, value);
}
@Override
public final void setPlayerInfoInteger(World world, String player, String node, int value) {
Objects.requireNonNull(player, "player");
Objects.requireNonNull(node, "node");
setUserMeta(world(world), this.permissionApi.lookupUuid(player), node, value);
setUserMeta(convertWorld(world), this.permissionApi.lookupUuid(player), node, value);
}
@Override
public final void setPlayerInfoInteger(Player player, String node, int value) {
Objects.requireNonNull(player, "player");
Objects.requireNonNull(node, "node");
setUserMeta(world(player), ((OfflinePlayer) player).getUniqueId(), node, value);
setUserMeta(convertWorld(player), ((OfflinePlayer) player).getUniqueId(), node, value);
}
@Override
public final int getGroupInfoInteger(String world, String group, String node, int defaultValue) {
Objects.requireNonNull(group, "group");
Objects.requireNonNull(node, "node");
return intConvert(getGroupMeta(world(world), group, node), defaultValue);
return parseInt(getGroupMeta(convertWorld(world), group, node), defaultValue);
}
@Override
public final int getGroupInfoInteger(World world, String group, String node, int defaultValue) {
Objects.requireNonNull(group, "group");
Objects.requireNonNull(node, "node");
return intConvert(getGroupMeta(world(world), group, node), defaultValue);
return parseInt(getGroupMeta(convertWorld(world), group, node), defaultValue);
}
@Override
public final void setGroupInfoInteger(String world, String group, String node, int value) {
Objects.requireNonNull(group, "group");
Objects.requireNonNull(node, "node");
setGroupMeta(world(world), group, node, value);
setGroupMeta(convertWorld(world), group, node, value);
}
@Override
public final void setGroupInfoInteger(World world, String group, String node, int value) {
Objects.requireNonNull(group, "group");
Objects.requireNonNull(node, "node");
setGroupMeta(world(world), group, node, value);
setGroupMeta(convertWorld(world), group, node, value);
}
@Override
public final double getPlayerInfoDouble(String world, OfflinePlayer player, String node, double defaultValue) {
Objects.requireNonNull(player, "player");
Objects.requireNonNull(node, "node");
return doubleConvert(getUserMeta(world(world), player.getUniqueId(), node), defaultValue);
return parseDouble(getUserMeta(convertWorld(world), player.getUniqueId(), node), defaultValue);
}
@Override
public final double getPlayerInfoDouble(String world, String player, String node, double defaultValue) {
Objects.requireNonNull(player, "player");
Objects.requireNonNull(node, "node");
return doubleConvert(getUserMeta(world(world), this.permissionApi.lookupUuid(player), node), defaultValue);
return parseDouble(getUserMeta(convertWorld(world), this.permissionApi.lookupUuid(player), node), defaultValue);
}
@Override
public final double getPlayerInfoDouble(World world, String player, String node, double defaultValue) {
Objects.requireNonNull(player, "player");
Objects.requireNonNull(node, "node");
return doubleConvert(getUserMeta(world(world), this.permissionApi.lookupUuid(player), node), defaultValue);
return parseDouble(getUserMeta(convertWorld(world), this.permissionApi.lookupUuid(player), node), defaultValue);
}
@Override
public final double getPlayerInfoDouble(Player player, String node, double defaultValue) {
Objects.requireNonNull(player, "player");
Objects.requireNonNull(node, "node");
return doubleConvert(getUserMeta(world(player), ((OfflinePlayer) player).getUniqueId(), node), defaultValue);
return parseDouble(getUserMeta(convertWorld(player), ((OfflinePlayer) player).getUniqueId(), node), defaultValue);
}
@Override
public final void setPlayerInfoDouble(String world, OfflinePlayer player, String node, double value) {
Objects.requireNonNull(player, "player");
Objects.requireNonNull(node, "node");
setUserMeta(world(world), player.getUniqueId(), node, value);
setUserMeta(convertWorld(world), player.getUniqueId(), node, value);
}
@Override
public final void setPlayerInfoDouble(String world, String player, String node, double value) {
Objects.requireNonNull(player, "player");
Objects.requireNonNull(node, "node");
setUserMeta(world(world), this.permissionApi.lookupUuid(player), node, value);
setUserMeta(convertWorld(world), this.permissionApi.lookupUuid(player), node, value);
}
@Override
public final void setPlayerInfoDouble(World world, String player, String node, double value) {
Objects.requireNonNull(player, "player");
Objects.requireNonNull(node, "node");
setUserMeta(world(world), this.permissionApi.lookupUuid(player), node, value);
setUserMeta(convertWorld(world), this.permissionApi.lookupUuid(player), node, value);
}
@Override
public final void setPlayerInfoDouble(Player player, String node, double value) {
Objects.requireNonNull(player, "player");
Objects.requireNonNull(node, "node");
setUserMeta(world(player), ((OfflinePlayer) player).getUniqueId(), node, value);
setUserMeta(convertWorld(player), ((OfflinePlayer) player).getUniqueId(), node, value);
}
@Override
public final double getGroupInfoDouble(String world, String group, String node, double defaultValue) {
Objects.requireNonNull(group, "group");
Objects.requireNonNull(node, "node");
return doubleConvert(getGroupMeta(world(world), group, node), defaultValue);
return parseDouble(getGroupMeta(convertWorld(world), group, node), defaultValue);
}
@Override
public final double getGroupInfoDouble(World world, String group, String node, double defaultValue) {
Objects.requireNonNull(group, "group");
Objects.requireNonNull(node, "node");
return doubleConvert(getGroupMeta(world(world), group, node), defaultValue);
return parseDouble(getGroupMeta(convertWorld(world), group, node), defaultValue);
}
@Override
public final void setGroupInfoDouble(String world, String group, String node, double value) {
Objects.requireNonNull(group, "group");
Objects.requireNonNull(node, "node");
setGroupMeta(world(world), group, node, value);
setGroupMeta(convertWorld(world), group, node, value);
}
@Override
public final void setGroupInfoDouble(World world, String group, String node, double value) {
Objects.requireNonNull(group, "group");
Objects.requireNonNull(node, "node");
setGroupMeta(world(world), group, node, value);
setGroupMeta(convertWorld(world), group, node, value);
}
@Override
public final boolean getPlayerInfoBoolean(String world, OfflinePlayer player, String node, boolean defaultValue) {
Objects.requireNonNull(player, "player");
Objects.requireNonNull(node, "node");
return booleanConvert(getUserMeta(world(world), player.getUniqueId(), node), defaultValue);
return parseBoolean(getUserMeta(convertWorld(world), player.getUniqueId(), node), defaultValue);
}
@Override
public final boolean getPlayerInfoBoolean(String world, String player, String node, boolean defaultValue) {
Objects.requireNonNull(player, "player");
Objects.requireNonNull(node, "node");
return booleanConvert(getUserMeta(world(world), this.permissionApi.lookupUuid(player), node), defaultValue);
return parseBoolean(getUserMeta(convertWorld(world), this.permissionApi.lookupUuid(player), node), defaultValue);
}
@Override
public final boolean getPlayerInfoBoolean(World world, String player, String node, boolean defaultValue) {
Objects.requireNonNull(player, "player");
Objects.requireNonNull(node, "node");
return booleanConvert(getUserMeta(world(world), this.permissionApi.lookupUuid(player), node), defaultValue);
return parseBoolean(getUserMeta(convertWorld(world), this.permissionApi.lookupUuid(player), node), defaultValue);
}
@Override
public final boolean getPlayerInfoBoolean(Player player, String node, boolean defaultValue) {
Objects.requireNonNull(player, "player");
Objects.requireNonNull(node, "node");
return booleanConvert(getUserMeta(world(player), ((OfflinePlayer) player).getUniqueId(), node), defaultValue);
return parseBoolean(getUserMeta(convertWorld(player), ((OfflinePlayer) player).getUniqueId(), node), defaultValue);
}
@Override
public final void setPlayerInfoBoolean(String world, OfflinePlayer player, String node, boolean value) {
Objects.requireNonNull(player, "player");
Objects.requireNonNull(node, "node");
setUserMeta(world(world), player.getUniqueId(), node, value);
setUserMeta(convertWorld(world), player.getUniqueId(), node, value);
}
@Override
public final void setPlayerInfoBoolean(String world, String player, String node, boolean value) {
Objects.requireNonNull(player, "player");
Objects.requireNonNull(node, "node");
setUserMeta(world(world), this.permissionApi.lookupUuid(player), node, value);
setUserMeta(convertWorld(world), this.permissionApi.lookupUuid(player), node, value);
}
@Override
public final void setPlayerInfoBoolean(World world, String player, String node, boolean value) {
Objects.requireNonNull(player, "player");
Objects.requireNonNull(node, "node");
setUserMeta(world(world), this.permissionApi.lookupUuid(player), node, value);
setUserMeta(convertWorld(world), this.permissionApi.lookupUuid(player), node, value);
}
@Override
public final void setPlayerInfoBoolean(Player player, String node, boolean value) {
Objects.requireNonNull(player, "player");
Objects.requireNonNull(node, "node");
setUserMeta(world(player), ((OfflinePlayer) player).getUniqueId(), node, value);
setUserMeta(convertWorld(player), ((OfflinePlayer) player).getUniqueId(), node, value);
}
@Override
public final boolean getGroupInfoBoolean(String world, String group, String node, boolean defaultValue) {
Objects.requireNonNull(group, "group");
Objects.requireNonNull(node, "node");
return booleanConvert(getGroupMeta(world(world), group, node), defaultValue);
return parseBoolean(getGroupMeta(convertWorld(world), group, node), defaultValue);
}
@Override
public final boolean getGroupInfoBoolean(World world, String group, String node, boolean defaultValue) {
Objects.requireNonNull(group, "group");
Objects.requireNonNull(node, "node");
return booleanConvert(getGroupMeta(world(world), group, node), defaultValue);
return parseBoolean(getGroupMeta(convertWorld(world), group, node), defaultValue);
}
@Override
public final void setGroupInfoBoolean(String world, String group, String node, boolean value) {
Objects.requireNonNull(group, "group");
Objects.requireNonNull(node, "node");
setGroupMeta(world(world), group, node, value);
setGroupMeta(convertWorld(world), group, node, value);
}
@Override
public final void setGroupInfoBoolean(World world, String group, String node, boolean value) {
Objects.requireNonNull(group, "group");
Objects.requireNonNull(node, "node");
setGroupMeta(world(world), group, node, value);
setGroupMeta(convertWorld(world), group, node, value);
}
@Override
public final String getPlayerInfoString(String world, OfflinePlayer player, String node, String defaultValue) {
Objects.requireNonNull(player, "player");
Objects.requireNonNull(node, "node");
return strConvert(getUserMeta(world(world), player.getUniqueId(), node), defaultValue);
return parseString(getUserMeta(convertWorld(world), player.getUniqueId(), node), defaultValue);
}
@Override
public final String getPlayerInfoString(String world, String player, String node, String defaultValue) {
Objects.requireNonNull(player, "player");
Objects.requireNonNull(node, "node");
return strConvert(getUserMeta(world(world), this.permissionApi.lookupUuid(player), node), defaultValue);
return parseString(getUserMeta(convertWorld(world), this.permissionApi.lookupUuid(player), node), defaultValue);
}
@Override
public final String getPlayerInfoString(World world, String player, String node, String defaultValue) {
Objects.requireNonNull(player, "player");
Objects.requireNonNull(node, "node");
return strConvert(getUserMeta(world(world), this.permissionApi.lookupUuid(player), node), defaultValue);
return parseString(getUserMeta(convertWorld(world), this.permissionApi.lookupUuid(player), node), defaultValue);
}
@Override
public final String getPlayerInfoString(Player player, String node, String defaultValue) {
Objects.requireNonNull(player, "player");
Objects.requireNonNull(node, "node");
return strConvert(getUserMeta(world(player), ((OfflinePlayer) player).getUniqueId(), node), defaultValue);
return parseString(getUserMeta(convertWorld(player), ((OfflinePlayer) player).getUniqueId(), node), defaultValue);
}
@Override
public final void setPlayerInfoString(String world, OfflinePlayer player, String node, String value) {
Objects.requireNonNull(player, "player");
Objects.requireNonNull(node, "node");
setUserMeta(world(world), player.getUniqueId(), node, value);
setUserMeta(convertWorld(world), player.getUniqueId(), node, value);
}
@Override
public final void setPlayerInfoString(String world, String player, String node, String value) {
Objects.requireNonNull(player, "player");
Objects.requireNonNull(node, "node");
setUserMeta(world(world), this.permissionApi.lookupUuid(player), node, value);
setUserMeta(convertWorld(world), this.permissionApi.lookupUuid(player), node, value);
}
@Override
public final void setPlayerInfoString(World world, String player, String node, String value) {
Objects.requireNonNull(player, "player");
Objects.requireNonNull(node, "node");
setUserMeta(world(world), this.permissionApi.lookupUuid(player), node, value);
setUserMeta(convertWorld(world), this.permissionApi.lookupUuid(player), node, value);
}
@Override
public final void setPlayerInfoString(Player player, String node, String value) {
Objects.requireNonNull(player, "player");
Objects.requireNonNull(node, "node");
setUserMeta(world(player), ((OfflinePlayer) player).getUniqueId(), node, value);
setUserMeta(convertWorld(player), ((OfflinePlayer) player).getUniqueId(), node, value);
}
@Override
public final String getGroupInfoString(String world, String group, String node, String defaultValue) {
Objects.requireNonNull(group, "group");
Objects.requireNonNull(node, "node");
return strConvert(getGroupMeta(world(world), group, node), defaultValue);
return parseString(getGroupMeta(convertWorld(world), group, node), defaultValue);
}
@Override
public final String getGroupInfoString(World world, String group, String node, String defaultValue) {
Objects.requireNonNull(group, "group");
Objects.requireNonNull(node, "node");
return strConvert(getGroupMeta(world(world), group, node), defaultValue);
return parseString(getGroupMeta(convertWorld(world), group, node), defaultValue);
}
@Override
public final void setGroupInfoString(String world, String group, String node, String value) {
Objects.requireNonNull(group, "group");
Objects.requireNonNull(node, "node");
setGroupMeta(world(world), group, node, value);
setGroupMeta(convertWorld(world), group, node, value);
}
@Override
public final void setGroupInfoString(World world, String group, String node, String value) {
Objects.requireNonNull(group, "group");
Objects.requireNonNull(node, "node");
setGroupMeta(world(world), group, node, value);
setGroupMeta(convertWorld(world), group, node, value);
}
@Override
@@ -34,7 +34,6 @@ import org.bukkit.plugin.java.JavaPlugin;
import java.util.Objects;
import java.util.UUID;
import java.util.function.Function;
/**
* An extended abstraction of the Vault {@link Permission} API.
@@ -46,18 +45,13 @@ import java.util.function.Function;
* to get a result using the players username.
*
* Whilst this meant the old plugins would still be supported, it made the whole
* API a total mess. This class reverses this decision, and instead upgrades
* API a total mess. This class reverses this action, and instead upgrades
* requests to use UUIDs. This makes implementing Vault significantly easier for modern
* plugins, and because requests are upgraded instead of being downgraded then upgraded,
* much faster for plugins querying data.
*/
@SuppressWarnings("deprecation")
public abstract class AbstractVaultPermission extends Permission {
// when upgrading and forwarding requests, all world strings are passed through this function.
// it lets the overriding class define some custom behaviour for world handling.
protected Function<String, String> worldMappingFunction = Function.identity();
public AbstractVaultPermission() {
super.plugin = JavaPlugin.getProvidingPlugin(Permission.class);
}
@@ -94,36 +88,37 @@ public abstract class AbstractVaultPermission extends Permission {
public abstract boolean groupAddPermission(String world, String name, String permission);
public abstract boolean groupRemovePermission(String world, String name, String permission);
private String world(String world) {
return this.worldMappingFunction.apply(world);
/* allow implementations to override and implement custom world remapping / handling rules */
protected String convertWorld(String world) {
return world;
}
private String world(Player player) {
if (player == null) {
return null;
}
return world(player.getWorld());
}
private String world(World world) {
private String convertWorld(World world) {
if (world == null) {
return null;
}
return world(world.getName());
return convertWorld(world.getName());
}
private String convertWorld(Player player) {
if (player == null) {
return null;
}
return convertWorld(player.getWorld());
}
@Override
public final boolean has(String world, String player, String permission) {
Objects.requireNonNull(player, "player");
Objects.requireNonNull(permission, "permission");
return userHasPermission(world(world), lookupUuid(player), permission);
return userHasPermission(convertWorld(world), lookupUuid(player), permission);
}
@Override
public final boolean has(World world, String player, String permission) {
Objects.requireNonNull(player, "player");
Objects.requireNonNull(permission, "permission");
return userHasPermission(world(world), lookupUuid(player), permission);
return userHasPermission(convertWorld(world), lookupUuid(player), permission);
}
@Override
@@ -137,21 +132,21 @@ public abstract class AbstractVaultPermission extends Permission {
public final boolean playerHas(String world, String player, String permission) {
Objects.requireNonNull(player, "player");
Objects.requireNonNull(permission, "permission");
return userHasPermission(world(world), lookupUuid(player), permission);
return userHasPermission(convertWorld(world), lookupUuid(player), permission);
}
@Override
public final boolean playerHas(World world, String player, String permission) {
Objects.requireNonNull(player, "player");
Objects.requireNonNull(permission, "permission");
return userHasPermission(world(world), lookupUuid(player), permission);
return userHasPermission(convertWorld(world), lookupUuid(player), permission);
}
@Override
public final boolean playerHas(String world, OfflinePlayer player, String permission) {
Objects.requireNonNull(player, "player");
Objects.requireNonNull(permission, "permission");
return userHasPermission(world(world), player.getUniqueId(), permission);
return userHasPermission(convertWorld(world), player.getUniqueId(), permission);
}
@Override
@@ -165,229 +160,229 @@ public abstract class AbstractVaultPermission extends Permission {
public final boolean playerAdd(String world, String player, String permission) {
Objects.requireNonNull(player, "player");
Objects.requireNonNull(permission, "permission");
return userAddPermission(world(world), lookupUuid(player), permission);
return userAddPermission(convertWorld(world), lookupUuid(player), permission);
}
@Override
public final boolean playerAdd(World world, String player, String permission) {
Objects.requireNonNull(player, "player");
Objects.requireNonNull(permission, "permission");
return userAddPermission(world(world), lookupUuid(player), permission);
return userAddPermission(convertWorld(world), lookupUuid(player), permission);
}
@Override
public final boolean playerAdd(String world, OfflinePlayer player, String permission) {
Objects.requireNonNull(player, "player");
Objects.requireNonNull(permission, "permission");
return userAddPermission(world(world), player.getUniqueId(), permission);
return userAddPermission(convertWorld(world), player.getUniqueId(), permission);
}
@Override
public final boolean playerAdd(Player player, String permission) {
Objects.requireNonNull(player, "player");
Objects.requireNonNull(permission, "permission");
return userAddPermission(world(player), ((OfflinePlayer) player).getUniqueId(), permission);
return userAddPermission(convertWorld(player), ((OfflinePlayer) player).getUniqueId(), permission);
}
@Override
public final boolean playerRemove(String world, String player, String permission) {
Objects.requireNonNull(player, "player");
Objects.requireNonNull(permission, "permission");
return userRemovePermission(world(world), lookupUuid(player), permission);
return userRemovePermission(convertWorld(world), lookupUuid(player), permission);
}
@Override
public final boolean playerRemove(String world, OfflinePlayer player, String permission) {
Objects.requireNonNull(player, "player");
Objects.requireNonNull(permission, "permission");
return userRemovePermission(world(world), player.getUniqueId(), permission);
return userRemovePermission(convertWorld(world), player.getUniqueId(), permission);
}
@Override
public final boolean playerRemove(World world, String player, String permission) {
Objects.requireNonNull(player, "player");
Objects.requireNonNull(permission, "permission");
return userRemovePermission(world(world), lookupUuid(player), permission);
return userRemovePermission(convertWorld(world), lookupUuid(player), permission);
}
@Override
public final boolean playerRemove(Player player, String permission) {
Objects.requireNonNull(player, "player");
Objects.requireNonNull(permission, "permission");
return userRemovePermission(world(player), ((OfflinePlayer) player).getUniqueId(), permission);
return userRemovePermission(convertWorld(player), ((OfflinePlayer) player).getUniqueId(), permission);
}
@Override
public final boolean groupHas(String world, String group, String permission) {
Objects.requireNonNull(group, "group");
Objects.requireNonNull(permission, "permission");
return groupHasPermission(world(world), group, permission);
return groupHasPermission(convertWorld(world), group, permission);
}
@Override
public final boolean groupHas(World world, String group, String permission) {
Objects.requireNonNull(group, "group");
Objects.requireNonNull(permission, "permission");
return groupHasPermission(world(world), group, permission);
return groupHasPermission(convertWorld(world), group, permission);
}
@Override
public final boolean groupAdd(String world, String group, String permission) {
Objects.requireNonNull(group, "group");
Objects.requireNonNull(permission, "permission");
return groupAddPermission(world(world), group, permission);
return groupAddPermission(convertWorld(world), group, permission);
}
@Override
public final boolean groupAdd(World world, String group, String permission) {
Objects.requireNonNull(group, "group");
Objects.requireNonNull(permission, "permission");
return groupAddPermission(world(world), group, permission);
return groupAddPermission(convertWorld(world), group, permission);
}
@Override
public final boolean groupRemove(String world, String group, String permission) {
Objects.requireNonNull(group, "group");
Objects.requireNonNull(permission, "permission");
return groupRemovePermission(world(world), group, permission);
return groupRemovePermission(convertWorld(world), group, permission);
}
@Override
public final boolean groupRemove(World world, String group, String permission) {
Objects.requireNonNull(group, "group");
Objects.requireNonNull(permission, "permission");
return groupRemovePermission(world(world), group, permission);
return groupRemovePermission(convertWorld(world), group, permission);
}
@Override
public final boolean playerInGroup(String world, String player, String group) {
Objects.requireNonNull(player, "player");
Objects.requireNonNull(group, "group");
return userInGroup(world(world), lookupUuid(player), group);
return userInGroup(convertWorld(world), lookupUuid(player), group);
}
@Override
public final boolean playerInGroup(World world, String player, String group) {
Objects.requireNonNull(player, "player");
Objects.requireNonNull(group, "group");
return userInGroup(world(world), lookupUuid(player), group);
return userInGroup(convertWorld(world), lookupUuid(player), group);
}
@Override
public final boolean playerInGroup(String world, OfflinePlayer player, String group) {
Objects.requireNonNull(player, "player");
Objects.requireNonNull(group, "group");
return userInGroup(world(world), player.getUniqueId(), group);
return userInGroup(convertWorld(world), player.getUniqueId(), group);
}
@Override
public final boolean playerInGroup(Player player, String group) {
Objects.requireNonNull(player, "player");
Objects.requireNonNull(group, "group");
return userInGroup(world(player), ((OfflinePlayer) player).getUniqueId(), group);
return userInGroup(convertWorld(player), ((OfflinePlayer) player).getUniqueId(), group);
}
@Override
public final boolean playerAddGroup(String world, String player, String group) {
Objects.requireNonNull(player, "player");
Objects.requireNonNull(group, "group");
return userAddGroup(world(world), lookupUuid(player), group);
return userAddGroup(convertWorld(world), lookupUuid(player), group);
}
@Override
public final boolean playerAddGroup(World world, String player, String group) {
Objects.requireNonNull(player, "player");
Objects.requireNonNull(group, "group");
return userAddGroup(world(world), lookupUuid(player), group);
return userAddGroup(convertWorld(world), lookupUuid(player), group);
}
@Override
public final boolean playerAddGroup(String world, OfflinePlayer player, String group) {
Objects.requireNonNull(player, "player");
Objects.requireNonNull(group, "group");
return userAddGroup(world(world), player.getUniqueId(), group);
return userAddGroup(convertWorld(world), player.getUniqueId(), group);
}
@Override
public final boolean playerAddGroup(Player player, String group) {
Objects.requireNonNull(player, "player");
Objects.requireNonNull(group, "group");
return userAddGroup(world(player), ((OfflinePlayer) player).getUniqueId(), group);
return userAddGroup(convertWorld(player), ((OfflinePlayer) player).getUniqueId(), group);
}
@Override
public final boolean playerRemoveGroup(String world, String player, String group) {
Objects.requireNonNull(player, "player");
Objects.requireNonNull(group, "group");
return userRemoveGroup(world(world), lookupUuid(player), group);
return userRemoveGroup(convertWorld(world), lookupUuid(player), group);
}
@Override
public final boolean playerRemoveGroup(World world, String player, String group) {
Objects.requireNonNull(player, "player");
Objects.requireNonNull(group, "group");
return userRemoveGroup(world(world), lookupUuid(player), group);
return userRemoveGroup(convertWorld(world), lookupUuid(player), group);
}
@Override
public final boolean playerRemoveGroup(String world, OfflinePlayer player, String group) {
Objects.requireNonNull(player, "player");
Objects.requireNonNull(group, "group");
return userRemoveGroup(world(world), player.getUniqueId(), group);
return userRemoveGroup(convertWorld(world), player.getUniqueId(), group);
}
@Override
public final boolean playerRemoveGroup(Player player, String group) {
Objects.requireNonNull(player, "player");
return userRemoveGroup(world(player), ((OfflinePlayer) player).getUniqueId(), group);
return userRemoveGroup(convertWorld(player), ((OfflinePlayer) player).getUniqueId(), group);
}
@Override
public final String[] getPlayerGroups(String world, String player) {
Objects.requireNonNull(player, "player");
return userGetGroups(world(world), lookupUuid(player));
return userGetGroups(convertWorld(world), lookupUuid(player));
}
@Override
public final String[] getPlayerGroups(World world, String player) {
Objects.requireNonNull(player, "player");
return userGetGroups(world(world), lookupUuid(player));
return userGetGroups(convertWorld(world), lookupUuid(player));
}
@Override
public final String[] getPlayerGroups(String world, OfflinePlayer player) {
Objects.requireNonNull(player, "player");
return userGetGroups(world(world), player.getUniqueId());
return userGetGroups(convertWorld(world), player.getUniqueId());
}
@Override
public final String[] getPlayerGroups(Player player) {
Objects.requireNonNull(player, "player");
return userGetGroups(world(player), ((OfflinePlayer) player).getUniqueId());
return userGetGroups(convertWorld(player), ((OfflinePlayer) player).getUniqueId());
}
@Override
public final String getPrimaryGroup(String world, String player) {
Objects.requireNonNull(player, "player");
return userGetPrimaryGroup(world(world), lookupUuid(player));
return userGetPrimaryGroup(convertWorld(world), lookupUuid(player));
}
@Override
public final String getPrimaryGroup(World world, String player) {
Objects.requireNonNull(player, "player");
return userGetPrimaryGroup(world(world), lookupUuid(player));
return userGetPrimaryGroup(convertWorld(world), lookupUuid(player));
}
@Override
public final String getPrimaryGroup(String world, OfflinePlayer player) {
Objects.requireNonNull(player, "player");
return userGetPrimaryGroup(world(world), player.getUniqueId());
return userGetPrimaryGroup(convertWorld(world), player.getUniqueId());
}
@Override
public final String getPrimaryGroup(Player player) {
Objects.requireNonNull(player, "player");
return userGetPrimaryGroup(world(player), ((OfflinePlayer) player).getUniqueId());
return userGetPrimaryGroup(convertWorld(player), ((OfflinePlayer) player).getUniqueId());
}
}
@@ -30,21 +30,20 @@ import com.google.common.base.Strings;
import me.lucko.luckperms.bukkit.LPBukkitPlugin;
import me.lucko.luckperms.common.cacheddata.type.MetaAccumulator;
import me.lucko.luckperms.common.cacheddata.type.MetaCache;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.context.contextset.ImmutableContextSetImpl;
import me.lucko.luckperms.common.model.Group;
import me.lucko.luckperms.common.model.PermissionHolder;
import me.lucko.luckperms.common.node.types.Meta;
import me.lucko.luckperms.common.node.types.Prefix;
import me.lucko.luckperms.common.node.types.Suffix;
import me.lucko.luckperms.common.util.TextUtils;
import me.lucko.luckperms.common.query.QueryOptionsImpl;
import me.lucko.luckperms.common.verbose.event.MetaCheckEvent;
import net.luckperms.api.context.DefaultContextKeys;
import net.luckperms.api.context.ImmutableContextSet;
import net.luckperms.api.model.data.DataType;
import net.luckperms.api.node.ChatMetaType;
import net.luckperms.api.node.NodeBuilder;
import net.luckperms.api.node.Node;
import net.luckperms.api.node.NodeType;
import net.luckperms.api.query.Flag;
import net.luckperms.api.query.QueryOptions;
@@ -78,7 +77,6 @@ public class LuckPermsVaultChat extends AbstractVaultChat {
super(vaultPermission);
this.plugin = plugin;
this.vaultPermission = vaultPermission;
this.worldMappingFunction = world -> vaultPermission.isIgnoreWorld() ? null : world;
}
@Override
@@ -86,6 +84,11 @@ public class LuckPermsVaultChat extends AbstractVaultChat {
return "LuckPerms";
}
@Override
protected String convertWorld(String world) {
return this.vaultPermission.isIgnoreWorld() ? null : super.convertWorld(world);
}
@Override
public String getUserChatPrefix(String world, UUID uuid) {
Objects.requireNonNull(uuid, "uuid");
@@ -93,11 +96,7 @@ public class LuckPermsVaultChat extends AbstractVaultChat {
PermissionHolder user = this.vaultPermission.lookupUser(uuid);
QueryOptions queryOptions = this.vaultPermission.getQueryOptions(uuid, world);
MetaCache metaData = user.getCachedData().getMetaData(queryOptions);
String ret = metaData.getPrefix(MetaCheckEvent.Origin.THIRD_PARTY_API);
if (log()) {
logMsg("#getUserChatPrefix: %s - %s - %s", user.getPlainDisplayName(), queryOptions.context(), ret);
}
return Strings.nullToEmpty(ret);
return Strings.nullToEmpty(metaData.getPrefix(MetaCheckEvent.Origin.THIRD_PARTY_API));
}
@Override
@@ -107,11 +106,7 @@ public class LuckPermsVaultChat extends AbstractVaultChat {
PermissionHolder user = this.vaultPermission.lookupUser(uuid);
QueryOptions queryOptions = this.vaultPermission.getQueryOptions(uuid, world);
MetaCache metaData = user.getCachedData().getMetaData(queryOptions);
String ret = metaData.getSuffix(MetaCheckEvent.Origin.THIRD_PARTY_API);
if (log()) {
logMsg("#getUserChatSuffix: %s - %s - %s", user.getPlainDisplayName(), queryOptions.context(), ret);
}
return Strings.nullToEmpty(ret);
return Strings.nullToEmpty(metaData.getSuffix(MetaCheckEvent.Origin.THIRD_PARTY_API));
}
@Override
@@ -144,11 +139,7 @@ public class LuckPermsVaultChat extends AbstractVaultChat {
PermissionHolder user = this.vaultPermission.lookupUser(uuid);
QueryOptions queryOptions = this.vaultPermission.getQueryOptions(uuid, world);
MetaCache metaData = user.getCachedData().getMetaData(queryOptions);
String ret = metaData.getMetaValue(key, MetaCheckEvent.Origin.THIRD_PARTY_API);
if (log()) {
logMsg("#getUserMeta: %s - %s - %s - %s", user.getPlainDisplayName(), queryOptions.context(), key, ret);
}
return ret;
return metaData.getMetaValue(key, MetaCheckEvent.Origin.THIRD_PARTY_API);
}
@Override
@@ -172,11 +163,7 @@ public class LuckPermsVaultChat extends AbstractVaultChat {
}
QueryOptions queryOptions = this.vaultPermission.getQueryOptions(null, world);
MetaCache metaData = group.getCachedData().getMetaData(queryOptions);
String ret = metaData.getPrefix(MetaCheckEvent.Origin.THIRD_PARTY_API);
if (log()) {
logMsg("#getGroupPrefix: %s - %s - %s", group.getName(), queryOptions.context(), ret);
}
return Strings.nullToEmpty(ret);
return Strings.nullToEmpty(metaData.getPrefix(MetaCheckEvent.Origin.THIRD_PARTY_API));
}
@Override
@@ -188,11 +175,7 @@ public class LuckPermsVaultChat extends AbstractVaultChat {
}
QueryOptions queryOptions = this.vaultPermission.getQueryOptions(null, world);
MetaCache metaData = group.getCachedData().getMetaData(queryOptions);
String ret = metaData.getSuffix(MetaCheckEvent.Origin.THIRD_PARTY_API);
if (log()) {
logMsg("#getGroupSuffix: %s - %s - %s", group.getName(), queryOptions.context(), ret);
}
return Strings.nullToEmpty(ret);
return Strings.nullToEmpty(metaData.getSuffix(MetaCheckEvent.Origin.THIRD_PARTY_API));
}
@Override
@@ -225,11 +208,7 @@ public class LuckPermsVaultChat extends AbstractVaultChat {
}
QueryOptions queryOptions = this.vaultPermission.getQueryOptions(null, world);
MetaCache metaData = group.getCachedData().getMetaData(queryOptions);
String ret = metaData.getMetaValue(key, MetaCheckEvent.Origin.THIRD_PARTY_API);
if (log()) {
logMsg("#getGroupMeta: %s - %s - %s - %s", group.getName(), queryOptions.context(), key, ret);
}
return ret;
return metaData.getMetaValue(key, MetaCheckEvent.Origin.THIRD_PARTY_API);
}
@Override
@@ -249,22 +228,7 @@ public class LuckPermsVaultChat extends AbstractVaultChat {
return this.plugin.getGroupManager().getByDisplayName(name);
}
// logging
private boolean log() {
return this.plugin.getConfiguration().get(ConfigKeys.VAULT_DEBUG);
}
private void logMsg(String format, Object... args) {
this.plugin.getLogger().info("[VAULT-CHAT] " + String.format(format, args)
.replace(TextUtils.SECTION_CHAR, '$')
.replace(TextUtils.AMPERSAND_CHAR, '$')
);
}
private void setChatMeta(PermissionHolder holder, ChatMetaType type, String value, String world) {
if (log()) {
logMsg("#setChatMeta: %s - %s - %s - %s", holder.getPlainDisplayName(), type, value, world);
}
// remove all prefixes/suffixes directly set on the user/group
holder.removeIf(DataType.NORMAL, null, type.nodeType()::matches, false);
@@ -274,22 +238,21 @@ public class LuckPermsVaultChat extends AbstractVaultChat {
}
// find the max inherited priority & add 10
MetaAccumulator metaAccumulator = holder.accumulateMeta(null, createQueryOptionsForWorldSet(world));
metaAccumulator.complete();
MetaAccumulator metaAccumulator = holder.accumulateMeta(createQueryOptionsForWorldSet(world));
int priority = metaAccumulator.getChatMeta(type).keySet().stream().mapToInt(e -> e).max().orElse(0) + 10;
NodeBuilder chatMetaNode = type == ChatMetaType.PREFIX ? Prefix.builder(value, priority) : Suffix.builder(value, priority);
chatMetaNode.withContext(DefaultContextKeys.SERVER_KEY, this.vaultPermission.getVaultServer());
chatMetaNode.withContext(DefaultContextKeys.WORLD_KEY, world);
Node node = type.builder(value, priority)
.withContext(DefaultContextKeys.SERVER_KEY, this.vaultPermission.getVaultServer())
.withContext(DefaultContextKeys.WORLD_KEY, world == null ? "global" : world).build();
// assume success
holder.setNode(DataType.NORMAL, chatMetaNode.build(), true);
holder.setNode(DataType.NORMAL, node, true);
this.vaultPermission.holderSave(holder);
}
private void setMeta(PermissionHolder holder, String key, Object value, String world) {
if (log()) {
logMsg("#setMeta: %s - %s - %s - %s", holder.getPlainDisplayName(), key, value, world);
if (key.equalsIgnoreCase(Prefix.NODE_KEY) || key.equalsIgnoreCase(Suffix.NODE_KEY)) {
setChatMeta(holder, ChatMetaType.valueOf(key.toUpperCase()), value == null ? null : value.toString(), world);
return;
}
holder.removeIf(DataType.NORMAL, null, NodeType.META.predicate(n -> n.getMetaKey().equals(key)), false);
@@ -299,20 +262,12 @@ public class LuckPermsVaultChat extends AbstractVaultChat {
return;
}
NodeBuilder metaNode;
if (key.equalsIgnoreCase("prefix")) {
metaNode = Prefix.builder(value.toString(), 100);
} else if (key.equalsIgnoreCase("suffix")) {
metaNode = Suffix.builder(value.toString(), 100);
} else {
metaNode = Meta.builder(key, value.toString());
}
Node node = Meta.builder(key, value.toString())
.withContext(DefaultContextKeys.SERVER_KEY, this.vaultPermission.getVaultServer())
.withContext(DefaultContextKeys.WORLD_KEY, world == null ? "global" : world)
.build();
metaNode.withContext(DefaultContextKeys.SERVER_KEY, this.vaultPermission.getVaultServer());
metaNode.withContext(DefaultContextKeys.WORLD_KEY, world);
// assume success
holder.setNode(DataType.NORMAL, metaNode.build(), true);
holder.setNode(DataType.NORMAL, node, true);
this.vaultPermission.holderSave(holder);
}
@@ -323,7 +278,7 @@ public class LuckPermsVaultChat extends AbstractVaultChat {
}
context.add(DefaultContextKeys.SERVER_KEY, this.vaultPermission.getVaultServer());
QueryOptions.Builder builder = QueryOptions.defaultContextualOptions().toBuilder();
QueryOptions.Builder builder = QueryOptionsImpl.DEFAULT_CONTEXTUAL.toBuilder();
builder.context(context.build());
builder.flag(Flag.INCLUDE_NODES_WITHOUT_SERVER_CONTEXT, this.vaultPermission.isIncludeGlobal());
return builder.build();
@@ -29,6 +29,7 @@ import com.google.common.base.Preconditions;
import me.lucko.luckperms.bukkit.LPBukkitPlugin;
import me.lucko.luckperms.bukkit.context.BukkitContextManager;
import me.lucko.luckperms.common.cacheddata.type.MetaCache;
import me.lucko.luckperms.common.cacheddata.type.PermissionCache;
import me.lucko.luckperms.common.calculator.processor.MapProcessor;
import me.lucko.luckperms.common.calculator.result.TristateResult;
@@ -40,26 +41,23 @@ import me.lucko.luckperms.common.model.User;
import me.lucko.luckperms.common.model.manager.group.GroupManager;
import me.lucko.luckperms.common.node.factory.NodeBuilders;
import me.lucko.luckperms.common.node.types.Inheritance;
import me.lucko.luckperms.common.util.TextUtils;
import me.lucko.luckperms.common.query.QueryOptionsImpl;
import me.lucko.luckperms.common.util.Uuids;
import me.lucko.luckperms.common.verbose.event.MetaCheckEvent;
import me.lucko.luckperms.common.verbose.event.PermissionCheckEvent;
import net.luckperms.api.context.ContextSet;
import net.luckperms.api.context.DefaultContextKeys;
import net.luckperms.api.context.MutableContextSet;
import net.luckperms.api.model.data.DataType;
import net.luckperms.api.util.Tristate;
import net.luckperms.api.node.Node;
import net.luckperms.api.node.NodeType;
import net.luckperms.api.query.Flag;
import net.luckperms.api.query.QueryOptions;
import net.luckperms.api.util.Result;
import net.milkbowl.vault.permission.Permission;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.Arrays;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
@@ -84,11 +82,6 @@ public class LuckPermsVaultPermission extends AbstractVaultPermission {
public LuckPermsVaultPermission(LPBukkitPlugin plugin) {
this.plugin = plugin;
this.worldMappingFunction = world -> isIgnoreWorld() ? null : world;
}
public LPBukkitPlugin getPlugin() {
return this.plugin;
}
@Override
@@ -96,12 +89,17 @@ public class LuckPermsVaultPermission extends AbstractVaultPermission {
return "LuckPerms";
}
@Override
protected String convertWorld(String world) {
return isIgnoreWorld() ? null : super.convertWorld(world);
}
@Override
public UUID lookupUuid(String player) {
Objects.requireNonNull(player, "player");
// are they online?
Player onlinePlayer = Bukkit.getPlayerExact(player);
Player onlinePlayer = this.plugin.getBootstrap().getServer().getPlayerExact(player);
if (onlinePlayer != null) {
return onlinePlayer.getUniqueId();
}
@@ -113,7 +111,7 @@ public class LuckPermsVaultPermission extends AbstractVaultPermission {
}
// are we on the main thread?
if (!this.plugin.getBootstrap().isServerStarting() && Bukkit.isPrimaryThread() && !this.plugin.getConfiguration().get(ConfigKeys.VAULT_UNSAFE_LOOKUPS)) {
if (!this.plugin.getBootstrap().isServerStarting() && this.plugin.getBootstrap().getServer().isPrimaryThread() && !this.plugin.getConfiguration().get(ConfigKeys.VAULT_UNSAFE_LOOKUPS)) {
throw new RuntimeException(
"The operation to lookup a UUID for '" + player + "' was cancelled by LuckPerms. This is NOT a bug. \n" +
"The lookup request was made on the main server thread. It is not safe to execute a request to \n" +
@@ -164,7 +162,7 @@ public class LuckPermsVaultPermission extends AbstractVaultPermission {
}
// are we on the main thread?
if (!this.plugin.getBootstrap().isServerStarting() && Bukkit.isPrimaryThread() && !this.plugin.getConfiguration().get(ConfigKeys.VAULT_UNSAFE_LOOKUPS)) {
if (!this.plugin.getBootstrap().isServerStarting() && this.plugin.getBootstrap().getServer().isPrimaryThread() && !this.plugin.getConfiguration().get(ConfigKeys.VAULT_UNSAFE_LOOKUPS)) {
throw new RuntimeException(
"The operation to load user data for '" + uuid + "' was cancelled by LuckPerms. This is NOT a bug. \n" +
"The lookup request was made on the main server thread. It is not safe to execute a request to \n" +
@@ -194,12 +192,7 @@ public class LuckPermsVaultPermission extends AbstractVaultPermission {
PermissionHolder user = lookupUser(uuid);
QueryOptions queryOptions = getQueryOptions(uuid, world);
PermissionCache permissionData = user.getCachedData().getPermissionData(queryOptions);
Tristate result = permissionData.checkPermission(permission, PermissionCheckEvent.Origin.THIRD_PARTY_API).result();
if (log()) {
logMsg("#userHasPermission: %s - %s - %s - %s", user.getPlainDisplayName(), queryOptions.context(), permission, result);
}
return result != Tristate.UNDEFINED ? result.asBoolean() : org.bukkit.permissions.Permission.DEFAULT_PERMISSION.getValue(queryOptions.option(BukkitContextManager.OP_OPTION).orElse(false));
return permissionData.checkPermission(permission, PermissionCheckEvent.Origin.THIRD_PARTY_API).result().asBoolean();
}
@Override
@@ -236,9 +229,6 @@ public class LuckPermsVaultPermission extends AbstractVaultPermission {
PermissionCache permissionData = user.getCachedData().getPermissionData(queryOptions);
TristateResult result = permissionData.checkPermission(Inheritance.key(rewriteGroupName(group)), PermissionCheckEvent.Origin.THIRD_PARTY_API);
if (log()) {
logMsg("#userInGroup: %s - %s - %s - %s", user.getPlainDisplayName(), queryOptions.context(), group, result);
}
return result.processorClass() == MapProcessor.class && result.result().asBoolean();
}
@@ -261,10 +251,9 @@ public class LuckPermsVaultPermission extends AbstractVaultPermission {
Objects.requireNonNull(uuid, "uuid");
PermissionHolder user = lookupUser(uuid);
ContextSet contexts = getQueryOptions(uuid, world).context();
QueryOptions queryOptions = getQueryOptions(uuid, world);
String[] ret = user.normalData().immutableInheritance().values().stream()
.filter(n -> n.getContexts().isSatisfiedBy(contexts))
return user.getOwnNodes(NodeType.INHERITANCE, queryOptions).stream()
.map(n -> {
Group group = this.plugin.getGroupManager().getIfLoaded(n.getGroupName());
if (group != null) {
@@ -273,12 +262,6 @@ public class LuckPermsVaultPermission extends AbstractVaultPermission {
return n.getGroupName();
})
.toArray(String[]::new);
if (log()) {
logMsg("#userGetGroups: %s - %s - %s", user.getPlainDisplayName(), contexts, Arrays.toString(ret));
}
return ret;
}
@Override
@@ -289,16 +272,14 @@ public class LuckPermsVaultPermission extends AbstractVaultPermission {
if (user instanceof Group) { // npc
return this.plugin.getConfiguration().get(ConfigKeys.VAULT_NPC_GROUP);
}
String value = ((User) user).getPrimaryGroup().getValue();
QueryOptions queryOptions = getQueryOptions(uuid, world);
MetaCache metaData = user.getCachedData().getMetaData(queryOptions);
String value = metaData.getPrimaryGroup(MetaCheckEvent.Origin.THIRD_PARTY_API);
Group group = getGroup(value);
if (group != null) {
value = group.getPlainDisplayName();
}
this.plugin.getVerboseHandler().offerMetaCheckEvent(MetaCheckEvent.Origin.THIRD_PARTY_API, user.getPlainDisplayName(), QueryOptions.defaultContextualOptions(), "primarygroup", value);
if (log()) {
logMsg("#userGetPrimaryGroup: %s - %s - %s", user.getPlainDisplayName(), world, value);
return group.getPlainDisplayName();
}
return value;
@@ -316,12 +297,7 @@ public class LuckPermsVaultPermission extends AbstractVaultPermission {
QueryOptions queryOptions = getQueryOptions(null, world);
PermissionCache permissionData = group.getCachedData().getPermissionData(queryOptions);
Tristate result = permissionData.checkPermission(permission, PermissionCheckEvent.Origin.THIRD_PARTY_API).result();
if (log()) {
logMsg("#groupHasPermission: %s - %s - %s - %s", group.getName(), queryOptions.context(), permission, result);
}
return result.asBoolean();
return permissionData.checkPermission(permission, PermissionCheckEvent.Origin.THIRD_PARTY_API).result().asBoolean();
}
@Override
@@ -368,17 +344,6 @@ public class LuckPermsVaultPermission extends AbstractVaultPermission {
return name;
}
// logging
private boolean log() {
return this.plugin.getConfiguration().get(ConfigKeys.VAULT_DEBUG);
}
private void logMsg(String format, Object... args) {
this.plugin.getLogger().info("[VAULT-PERMS] " + String.format(format, args)
.replace(TextUtils.SECTION_CHAR, '$')
.replace(TextUtils.AMPERSAND_CHAR, '$')
);
}
// utility method for getting a contexts instance for a given vault lookup.
QueryOptions getQueryOptions(@Nullable UUID uuid, @Nullable String world) {
MutableContextSet context;
@@ -419,7 +384,7 @@ public class LuckPermsVaultPermission extends AbstractVaultPermission {
op = this.plugin.getConfiguration().get(ConfigKeys.VAULT_NPC_OP_STATUS);
}
QueryOptions.Builder builder = QueryOptions.defaultContextualOptions().toBuilder();
QueryOptions.Builder builder = QueryOptionsImpl.DEFAULT_CONTEXTUAL.toBuilder();
builder.context(context);
builder.flag(Flag.INCLUDE_NODES_WITHOUT_SERVER_CONTEXT, isIncludeGlobal());
if (op) {
@@ -434,11 +399,12 @@ public class LuckPermsVaultPermission extends AbstractVaultPermission {
Objects.requireNonNull(permission, "permission is null");
Preconditions.checkArgument(!permission.isEmpty(), "permission is an empty string");
if (log()) {
logMsg("#holderAddPermission: %s - %s - %s", holder.getPlainDisplayName(), permission, world);
}
Node node = NodeBuilders.determineMostApplicable(permission)
.withContext(DefaultContextKeys.SERVER_KEY, getVaultServer())
.withContext(DefaultContextKeys.WORLD_KEY, world == null ? "global" : world)
.build();
if (((Result) holder.setNode(DataType.NORMAL, NodeBuilders.determineMostApplicable(permission).value(true).withContext(DefaultContextKeys.SERVER_KEY, getVaultServer()).withContext(DefaultContextKeys.WORLD_KEY, world).build(), true)).wasSuccessful()) {
if (holder.setNode(DataType.NORMAL, node, true).wasSuccessful()) {
return holderSave(holder);
}
return false;
@@ -448,11 +414,12 @@ public class LuckPermsVaultPermission extends AbstractVaultPermission {
Objects.requireNonNull(permission, "permission is null");
Preconditions.checkArgument(!permission.isEmpty(), "permission is an empty string");
if (log()) {
logMsg("#holderRemovePermission: %s - %s - %s", holder.getPlainDisplayName(), permission, world);
}
Node node = NodeBuilders.determineMostApplicable(permission)
.withContext(DefaultContextKeys.SERVER_KEY, getVaultServer())
.withContext(DefaultContextKeys.WORLD_KEY, world == null ? "global" : world)
.build();
if (holder.unsetNode(DataType.NORMAL, NodeBuilders.determineMostApplicable(permission).withContext(DefaultContextKeys.SERVER_KEY, getVaultServer()).withContext(DefaultContextKeys.WORLD_KEY, world).build()).wasSuccessful()) {
if (holder.unsetNode(DataType.NORMAL, node).wasSuccessful()) {
return holderSave(holder);
}
return false;
+75 -9
View File
@@ -57,7 +57,7 @@ use-server-uuid-cache: false
# How the plugin should store data
#
# - The various options are explained in more detail on the wiki:
# https://github.com/lucko/LuckPerms/wiki/Choosing-a-Storage-type
# https://github.com/lucko/LuckPerms/wiki/Storage-types
#
# - Possible options:
#
@@ -133,21 +133,31 @@ data:
connection-timeout: 5000 # 5 seconds
# This setting allows you to define extra properties for connections.
#
# By default, the following options are set to enable utf8 encoding. (you may need to remove
# these if you are using PostgreSQL)
# useUnicode: true
# characterEncoding: utf8
#
# You can also use this section to disable SSL connections, by uncommenting the 'useSSL' and
# 'verifyServerCertificate' options below.
properties:
useUnicode: true
characterEncoding: utf8
#useSSL: false
#verifyServerCertificate: false
# The prefix for all LuckPerms SQL tables.
# - Change this is you want to use different tables for different servers.
table_prefix: 'luckperms_'
# - Change this if you want to use different tables for different servers.
table-prefix: 'luckperms_'
# The prefix to use for all LuckPerms collections. Change this if you want to use different
# collections for different servers. The default is no prefix.
mongodb_collection_prefix: ''
mongodb-collection-prefix: ''
# MongoDB ClientConnectionURI for use with replica sets and custom connection options
# - See https://docs.mongodb.com/manual/reference/connection-string/
mongodb_connection_URI: ''
mongodb-connection-uri: ''
# Define settings for a "split" storage setup.
#
@@ -205,7 +215,7 @@ watch-files: true
# - Possible options:
# => sql Uses the SQL database to form a queue system for communication. Will only work when
# 'storage-method' is set to MySQL or MariaDB. This is chosen by default if the
# option is set to 'none' and SQL storage is in use. Set to 'notsql' to disable this.
# option is set to 'auto' and SQL storage is in use. Set to 'notsql' to disable this.
# => pluginmsg Uses the plugin messaging channels to communicate with the proxy.
# LuckPerms must be installed on your proxy & all connected servers backend servers.
# Won't work if you have more than one proxy.
@@ -294,6 +304,12 @@ require-sender-group-membership-to-modify: false
# '/lp log notify <on|off>'
log-notify: true
# Defines a list of log entries which should not be sent as notifications to users.
#
# - Each entry in the list is a RegEx expression which is matched against the log entry description.
log-notify-filtered-descriptions:
# - "parent add example"
# Defines the options for prefix and suffix stacking.
#
# - The feature allows you to display multiple prefixes or suffixes alongside a players username in
@@ -332,6 +348,16 @@ log-notify: true
# value was inherited from a group not on the given track.
#
# => lowest_not_on_track_<track> Same as above, except takes the value with the lowest weight.
#
# => highest_from_group_<group> Selects the value with the highest weight, but only if the
# value was inherited from the given group.
#
# => lowest_from_group_<group> Same as above, except takes the value with the lowest weight.
#
# => highest_not_from_group_<group> Selects the value with the highest weight, but only if the
# value was not inherited from the given group.
#
# => lowest_not_from_group_<group> Same as above, except takes the value with the lowest weight.
meta-formatting:
prefix:
format:
@@ -380,6 +406,15 @@ inheritance-traversal-algorithm: depth-first-pre-order
# and when this setting is 'false':, the rules are just applied during each step of the traversal.
post-traversal-inheritance-sort: false
# Defines the mode used to determine whether a set of contexts are satisfied.
#
# - Possible options:
# => at-least-one-value-per-key Set A will be satisfied by another set B, if at least one of the
# key-value entries per key in A are also in B.
# => all-values-per-key Set A will be satisfied by another set B, if all key-value
# entries in A are also in B.
context-satisfy-mode: at-least-one-value-per-key
# +----------------------------------------------------------------------------------------------+ #
# | Permission resolution settings | #
# +----------------------------------------------------------------------------------------------+ #
@@ -398,6 +433,22 @@ apply-global-groups: true
# If users on this server should have global (non-world specific) groups applied
apply-global-world-groups: true
# +----------------------------------------------------------------------------------------------+ #
# | Meta lookup settings | #
# +----------------------------------------------------------------------------------------------+ #
# Defines how meta values should be selected.
#
# - Possible options:
# => inheritance Selects the meta value that was inherited first
# => highest-number Selects the highest numerical meta value
# => lowest-number Selects the lowest numerical meta value
meta-value-selection-default: inheritance
# Defines how meta values should be selected per key.
meta-value-selection:
# max-homes: highest-number
# +----------------------------------------------------------------------------------------------+ #
# | Inheritance settings | #
# +----------------------------------------------------------------------------------------------+ #
@@ -408,6 +459,14 @@ apply-global-world-groups: true
# permissions matching the wildcard.
apply-wildcards: true
# If LuckPerms should resolve and apply permissions according to the Sponge style implicit wildcard
# inheritance system.
#
# - That being: If a user has been granted "example", then the player should have also be
# automatically granted "example.function", "example.another", "example.deeper.nesting",
# and so on.
apply-sponge-implicit-wildcards: false
# If the plugin should parse regex permissions.
#
# - If set to true, LuckPerms will detect regex permissions, marked with "r=" at the start of the
@@ -544,9 +603,6 @@ vault-include-global: true
# If Vault operations should ignore any world arguments if supplied.
vault-ignore-world: false
# If LuckPerms should print debugging info to console when a plugin uses a Vault function
vault-debug: false
# +----------------------------------------------------------------------------------------------+ #
# | Miscellaneous (and rarely used) settings | #
# +----------------------------------------------------------------------------------------------+ #
@@ -566,3 +622,13 @@ allow-invalid-usernames: false
#
# - When this happens, the plugin will set their primary group back to default.
prevent-primary-group-removal: false
# If LuckPerms should update the list of commands sent to the client when permissions are changed.
update-client-command-list: true
# If LuckPerms should attempt to register "Brigadier" command list data for its commands.
register-command-list-data: true
# If LuckPerms should attempt to resolve Vanilla command target selectors for LP commands.
# See here for more info: https://minecraft.gamepedia.com/Commands#Target_selectors
resolve-command-selectors: false
@@ -0,0 +1,631 @@
luckperms {
sync;
info;
editor {
all;
groups;
users;
}
debug;
verbose {
off;
on {
filter brigadier:string greedy_phrase;
}
paste;
record {
filter brigadier:string greedy_phrase;
}
command {
player brigadier:string single_word {
command brigadier:string greedy_phrase;
}
}
}
tree {
scope brigadier:string single_word {
player brigadier:string single_word;
}
}
search {
permission brigadier:string quotable_phrase;
}
check {
user brigadier:string single_word {
permission brigadier:string quotable_phrase;
}
}
networksync;
import {
file brigadier:string quotable_phrase;
}
export {
file brigadier:string quotable_phrase;
}
reloadconfig;
bulkupdate {
datatype brigadier:string single_word {
delete {
constraint brigadier:string greedy_phrase;
}
update {
actionfield brigadier:string single_word {
actionvalue brigadier:string quotable_phrase {
constraint brigadier:string greedy_phrase;
}
}
}
}
}
migration {
plugin brigadier:string single_word;
}
creategroup {
name brigadier:string single_word;
}
deletegroup {
name brigadier:string single_word;
}
listgroups;
createtrack {
name brigadier:string single_word;
}
deletetrack {
name brigadier:string single_word;
}
listtracks;
user {
user brigadier:string single_word {
info;
permission {
info;
set {
node brigadier:string quotable_phrase {
value brigadier:bool {
context brigadier:string greedy_phrase;
}
}
}
unset {
node brigadier:string quotable_phrase {
context brigadier:string greedy_phrase;
}
}
settemp {
node brigadier:string quotable_phrase {
value brigadier:bool {
duration brigadier:string single_word {
context brigadier:string greedy_phrase;
}
}
}
}
unsettemp {
node brigadier:string quotable_phrase {
context brigadier:string greedy_phrase;
duration brigadier:string single_word {
context brigadier:string greedy_phrase;
}
}
}
check {
node brigadier:string quotable_phrase {
context brigadier:string greedy_phrase;
}
}
checkinherits {
node brigadier:string quotable_phrase {
context brigadier:string greedy_phrase;
}
}
clear {
context brigadier:string greedy_phrase;
}
}
parent {
info;
set {
group brigadier:string single_word {
context brigadier:string greedy_phrase;
}
}
add {
group brigadier:string single_word {
context brigadier:string greedy_phrase;
}
}
remove {
group brigadier:string single_word {
context brigadier:string greedy_phrase;
}
}
settrack {
track brigadier:string single_word {
group brigadier:string single_word {
context brigadier:string greedy_phrase;
}
}
}
addtemp {
group brigadier:string quotable_phrase {
duration brigadier:string single_word {
context brigadier:string greedy_phrase;
}
}
}
removetemp {
group brigadier:string single_word {
context brigadier:string greedy_phrase;
duration brigadier:string single_word {
context brigadier:string greedy_phrase;
}
}
}
clear {
context brigadier:string greedy_phrase;
}
cleartrack {
track brigadier:string single_word {
context brigadier:string greedy_phrase;
}
}
switchprimarygroup {
group brigadier:string quotable_phrase;
}
}
meta {
info;
set {
key brigadier:string quotable_phrase {
value brigadier:string quotable_phrase {
context brigadier:string greedy_phrase;
}
}
}
unset {
key brigadier:string quotable_phrase {
context brigadier:string greedy_phrase;
}
}
settemp {
key brigadier:string quotable_phrase {
value brigadier:string quotable_phrase {
duration brigadier:string single_word {
context brigadier:string greedy_phrase;
}
}
}
}
unsettemp {
key brigadier:string quotable_phrase {
context brigadier:string greedy_phrase;
}
}
addprefix {
priority brigadier:integer {
prefix brigadier:string quotable_phrase {
context brigadier:string greedy_phrase;
}
}
}
addsuffix {
priority brigadier:integer {
suffix brigadier:string quotable_phrase {
context brigadier:string greedy_phrase;
}
}
}
setprefix {
priority brigadier:integer {
prefix brigadier:string quotable_phrase {
context brigadier:string greedy_phrase;
}
}
}
setsuffix {
priority brigadier:integer {
suffix brigadier:string quotable_phrase {
context brigadier:string greedy_phrase;
}
}
}
removeprefix {
priority brigadier:integer {
prefix brigadier:string quotable_phrase {
context brigadier:string greedy_phrase;
}
}
}
removesuffix {
priority brigadier:integer {
suffix brigadier:string quotable_phrase {
context brigadier:string greedy_phrase;
}
}
}
addtempprefix {
priority brigadier:integer {
prefix brigadier:string quotable_phrase {
duration brigadier:string single_word {
context brigadier:string greedy_phrase;
}
}
}
}
addtempsuffix {
priority brigadier:integer {
suffix brigadier:string quotable_phrase {
duration brigadier:string single_word {
context brigadier:string greedy_phrase;
}
}
}
}
settempprefix {
priority brigadier:integer {
prefix brigadier:string quotable_phrase {
duration brigadier:string single_word {
context brigadier:string greedy_phrase;
}
}
}
}
settempsuffix {
priority brigadier:integer {
suffix brigadier:string quotable_phrase {
duration brigadier:string single_word {
context brigadier:string greedy_phrase;
}
}
}
}
removetempprefix {
priority brigadier:integer {
prefix brigadier:string quotable_phrase {
context brigadier:string greedy_phrase;
}
}
}
removetempsuffix {
priority brigadier:integer {
suffix brigadier:string quotable_phrase {
context brigadier:string greedy_phrase;
}
}
}
clear {
context brigadier:string greedy_phrase;
}
}
editor;
promote {
track brigadier:string single_word {
context brigadier:string greedy_phrase;
}
}
demote {
track brigadier:string single_word {
context brigadier:string greedy_phrase;
}
}
showtracks;
clear {
context brigadier:string greedy_phrase;
}
clone {
user brigadier:string single_word;
}
}
}
group {
group brigadier:string single_word {
info;
permission {
info;
set {
node brigadier:string quotable_phrase {
value brigadier:bool {
context brigadier:string greedy_phrase;
}
}
}
unset {
node brigadier:string quotable_phrase {
context brigadier:string greedy_phrase;
}
}
settemp {
node brigadier:string quotable_phrase {
value brigadier:bool {
duration brigadier:string single_word {
context brigadier:string greedy_phrase;
}
}
}
}
unsettemp {
node brigadier:string quotable_phrase {
context brigadier:string greedy_phrase;
duration brigadier:string single_word {
context brigadier:string greedy_phrase;
}
}
}
check {
node brigadier:string quotable_phrase {
context brigadier:string greedy_phrase;
}
}
checkinherits {
node brigadier:string quotable_phrase {
context brigadier:string greedy_phrase;
}
}
clear {
context brigadier:string greedy_phrase;
}
}
parent {
info;
set {
group brigadier:string single_word {
context brigadier:string greedy_phrase;
}
}
add {
group brigadier:string single_word {
context brigadier:string greedy_phrase;
}
}
remove {
group brigadier:string single_word {
context brigadier:string greedy_phrase;
}
}
settrack {
track brigadier:string single_word {
group brigadier:string single_word {
context brigadier:string greedy_phrase;
}
}
}
addtemp {
group brigadier:string quotable_phrase {
duration brigadier:string single_word {
context brigadier:string greedy_phrase;
}
}
}
removetemp {
group brigadier:string single_word {
context brigadier:string greedy_phrase;
duration brigadier:string single_word {
context brigadier:string greedy_phrase;
}
}
}
clear {
context brigadier:string greedy_phrase;
}
cleartrack {
track brigadier:string single_word {
context brigadier:string greedy_phrase;
}
}
switchprimarygroup {
group brigadier:string quotable_phrase;
}
}
meta {
info;
set {
key brigadier:string quotable_phrase {
value brigadier:string quotable_phrase {
context brigadier:string greedy_phrase;
}
}
}
unset {
key brigadier:string quotable_phrase {
context brigadier:string greedy_phrase;
}
}
settemp {
key brigadier:string quotable_phrase {
value brigadier:string quotable_phrase {
duration brigadier:string single_word {
context brigadier:string greedy_phrase;
}
}
}
}
unsettemp {
key brigadier:string quotable_phrase {
context brigadier:string greedy_phrase;
}
}
addprefix {
priority brigadier:integer {
prefix brigadier:string quotable_phrase {
context brigadier:string greedy_phrase;
}
}
}
addsuffix {
priority brigadier:integer {
suffix brigadier:string quotable_phrase {
context brigadier:string greedy_phrase;
}
}
}
setprefix {
priority brigadier:integer {
prefix brigadier:string quotable_phrase {
context brigadier:string greedy_phrase;
}
}
}
setsuffix {
priority brigadier:integer {
suffix brigadier:string quotable_phrase {
context brigadier:string greedy_phrase;
}
}
}
removeprefix {
priority brigadier:integer {
prefix brigadier:string quotable_phrase {
context brigadier:string greedy_phrase;
}
}
}
removesuffix {
priority brigadier:integer {
suffix brigadier:string quotable_phrase {
context brigadier:string greedy_phrase;
}
}
}
addtempprefix {
priority brigadier:integer {
prefix brigadier:string quotable_phrase {
duration brigadier:string single_word {
context brigadier:string greedy_phrase;
}
}
}
}
addtempsuffix {
priority brigadier:integer {
suffix brigadier:string quotable_phrase {
duration brigadier:string single_word {
context brigadier:string greedy_phrase;
}
}
}
}
settempprefix {
priority brigadier:integer {
prefix brigadier:string quotable_phrase {
duration brigadier:string single_word {
context brigadier:string greedy_phrase;
}
}
}
}
settempsuffix {
priority brigadier:integer {
suffix brigadier:string quotable_phrase {
duration brigadier:string single_word {
context brigadier:string greedy_phrase;
}
}
}
}
removetempprefix {
priority brigadier:integer {
prefix brigadier:string quotable_phrase {
context brigadier:string greedy_phrase;
}
}
}
removetempsuffix {
priority brigadier:integer {
suffix brigadier:string quotable_phrase {
context brigadier:string greedy_phrase;
}
}
}
clear {
context brigadier:string greedy_phrase;
}
}
editor;
listmembers {
page brigadier:integer;
}
setweight {
weight brigadier:integer;
}
setdisplayname {
name brigadier:string single_word;
}
showtracks;
clear {
context brigadier:string greedy_phrase;
}
rename {
newname brigadier:string single_word;
}
clone {
newname brigadier:string single_word;
}
}
}
track {
track brigadier:string single_word {
info;
append {
group brigadier:string single_word;
}
insert {
group brigadier:string single_word {
position brigadier:integer;
}
}
remove {
group brigadier:string single_word;
}
clear;
rename {
newname brigadier:string single_word;
}
clone {
newname brigadier:string single_word;
}
}
}
log {
recent {
user brigadier:string single_word {
page brigadier:integer;
}
}
search {
query brigadier:string quotable_phrase {
page brigadier:integer;
}
}
notify {
off;
on;
}
userhistory {
user brigadier:string single_word {
page brigadier:integer;
}
}
grouphistory {
group brigadier:string single_word {
page brigadier:integer;
}
}
trackhistory {
track brigadier:string single_word {
page brigadier:integer;
}
}
}
}
+3 -2
View File
@@ -5,8 +5,8 @@ plugins {
dependencies {
compile project(':common')
compileOnly 'net.md-5:bungeecord-api:1.14-SNAPSHOT'
compileOnly 'net.kyori:text-adapter-bungeecord:3.0.2'
compileOnly 'net.md-5:bungeecord-api:1.15-SNAPSHOT'
compileOnly 'net.kyori:text-adapter-bungeecord:3.0.4'
compileOnly 'com.imaginarycode.minecraft:RedisBungee:0.4'
// migration plugins
@@ -41,6 +41,7 @@ shadowJar {
relocate 'com.github.benmanes.caffeine', 'me.lucko.luckperms.lib.caffeine'
relocate 'okio', 'me.lucko.luckperms.lib.okio'
relocate 'okhttp3', 'me.lucko.luckperms.lib.okhttp3'
relocate 'net.bytebuddy', 'me.lucko.luckperms.lib.bytebuddy'
relocate 'me.lucko.commodore', 'me.lucko.luckperms.lib.commodore'
relocate 'org.mariadb.jdbc', 'me.lucko.luckperms.lib.mariadb'
relocate 'com.mysql', 'me.lucko.luckperms.lib.mysql'
@@ -25,45 +25,71 @@
package me.lucko.luckperms.bungee;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import me.lucko.luckperms.common.command.CommandManager;
import me.lucko.luckperms.common.command.utils.ArgumentTokenizer;
import me.lucko.luckperms.common.sender.Sender;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.plugin.Command;
import net.md_5.bungee.api.plugin.TabExecutor;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
public class BungeeCommandExecutor extends Command implements TabExecutor {
private static final Splitter TAB_COMPLETE_ARGUMENT_SPLITTER = Splitter.on(CommandManager.COMMAND_SEPARATOR_PATTERN);
private static final Splitter ARGUMENT_SPLITTER = Splitter.on(CommandManager.COMMAND_SEPARATOR_PATTERN).omitEmptyStrings();
private static final Joiner ARGUMENT_JOINER = Joiner.on(' ');
/** The main command name */
private static final String NAME = "luckpermsbungee";
/** The command aliases */
private static final String[] ALIASES = {"lpb", "bperm", "bperms", "bpermission", "bpermissions"};
/** The main command name + aliases, prefixed with '/' */
private static final String[] SLASH_ALIASES = Stream.concat(
Stream.of(NAME),
Arrays.stream(ALIASES)
).map(s -> '/' + s).toArray(String[]::new);
/**
* The aliases to register, {@link #ALIASES} + {@link #SLASH_ALIASES}.
*
* <p>SLASH_ALIASES are registered too so the console can run '/lpb'
* in the same way as 'lpb'.</p>
*/
private static final String[] ALIASES_TO_REGISTER = Stream.concat(
Arrays.stream(ALIASES),
Arrays.stream(SLASH_ALIASES)
).toArray(String[]::new);
private final LPBungeePlugin plugin;
private final CommandManager manager;
public BungeeCommandExecutor(LPBungeePlugin plugin, CommandManager manager) {
super("luckpermsbungee", null, "lpb", "bperm", "bperms", "bpermission", "bpermissions");
super(NAME, null, ALIASES_TO_REGISTER);
this.plugin = plugin;
this.manager = manager;
}
public void register() {
ProxyServer proxy = this.plugin.getBootstrap().getProxy();
proxy.getPluginManager().registerCommand(this.plugin.getBootstrap(), this);
// don't allow players to execute the slash aliases - these are just for the console.
proxy.getDisabledCommands().addAll(Arrays.asList(SLASH_ALIASES));
}
@Override
public void execute(CommandSender sender, String[] args) {
Sender lpSender = this.plugin.getSenderFactory().wrap(sender);
List<String> arguments = CommandManager.stripQuotes(ARGUMENT_SPLITTER.splitToList(ARGUMENT_JOINER.join(args)));
this.manager.onCommand(lpSender, "lpb", arguments);
Sender wrapped = this.plugin.getSenderFactory().wrap(sender);
List<String> arguments = ArgumentTokenizer.EXECUTE.tokenizeInput(args);
this.manager.executeCommand(wrapped, "lpb", arguments);
}
@Override
public Iterable<String> onTabComplete(CommandSender sender, String[] args) {
Sender lpSender = this.plugin.getSenderFactory().wrap(sender);
List<String> arguments = CommandManager.stripQuotes(TAB_COMPLETE_ARGUMENT_SPLITTER.splitToList(ARGUMENT_JOINER.join(args)));
return this.manager.onTabComplete(lpSender, arguments);
Sender wrapped = this.plugin.getSenderFactory().wrap(sender);
List<String> arguments = ArgumentTokenizer.TAB_COMPLETE.tokenizeInput(args);
return this.manager.tabCompleteCommand(wrapped, arguments);
}
}
@@ -25,7 +25,7 @@
package me.lucko.luckperms.bungee;
import me.lucko.luckperms.common.config.adapter.ConfigurationAdapter;
import me.lucko.luckperms.common.config.generic.adapter.ConfigurationAdapter;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import net.md_5.bungee.config.Configuration;
@@ -73,8 +73,12 @@ public class BungeeSchedulerAdapter implements SchedulerAdapter {
}
@Override
public void shutdown() {
public void shutdownScheduler() {
Iterators.tryIterate(this.tasks, ScheduledTask::cancel);
}
@Override
public void shutdownExecutor() {
// do nothing
}
}
@@ -26,7 +26,6 @@
package me.lucko.luckperms.bungee;
import me.lucko.luckperms.bungee.event.TristateCheckEvent;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.sender.Sender;
import me.lucko.luckperms.common.sender.SenderFactory;
import me.lucko.luckperms.common.util.TextUtils;
@@ -39,8 +38,8 @@ import net.md_5.bungee.api.connection.ProxiedPlayer;
import java.util.UUID;
public class BungeeSenderFactory extends SenderFactory<CommandSender> {
public BungeeSenderFactory(LuckPermsPlugin plugin) {
public class BungeeSenderFactory extends SenderFactory<LPBungeePlugin, CommandSender> {
public BungeeSenderFactory(LPBungeePlugin plugin) {
super(plugin);
}
@@ -79,4 +78,9 @@ public class BungeeSenderFactory extends SenderFactory<CommandSender> {
protected boolean hasPermission(CommandSender sender, String node) {
return sender.hasPermission(node);
}
@Override
protected void performCommand(CommandSender sender, String command) {
getPlugin().getBootstrap().getProxy().getPluginManager().dispatchCommand(sender, command);
}
}
@@ -36,13 +36,19 @@ import me.lucko.luckperms.common.plugin.scheduler.SchedulerAdapter;
import net.luckperms.api.platform.Platform;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.plugin.Plugin;
import net.md_5.bungee.api.plugin.PluginDescription;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.io.InputStream;
import java.nio.file.Path;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.stream.Stream;
/**
* Bootstrap plugin for LuckPerms running on BungeeCord.
@@ -72,12 +78,15 @@ public class LPBungeeBootstrap extends Plugin implements LuckPermsBootstrap {
/**
* The time when the plugin was enabled
*/
private long startTime;
private Instant startTime;
// load/enable latches
private final CountDownLatch loadLatch = new CountDownLatch(1);
private final CountDownLatch enableLatch = new CountDownLatch(1);
// if the plugin has been loaded on an incompatible version
private boolean incompatibleVersion = false;
public LPBungeeBootstrap() {
this.schedulerAdapter = new BungeeSchedulerAdapter(this);
this.classLoader = new ReflectionClassLoader(this);
@@ -110,6 +119,11 @@ public class LPBungeeBootstrap extends Plugin implements LuckPermsBootstrap {
public void onLoad() {
this.logger = new JavaPluginLogger(getLogger());
if (checkIncompatibleVersion()) {
this.incompatibleVersion = true;
return;
}
try {
this.plugin.load();
} finally {
@@ -119,7 +133,22 @@ public class LPBungeeBootstrap extends Plugin implements LuckPermsBootstrap {
@Override
public void onEnable() {
this.startTime = System.currentTimeMillis();
if (this.incompatibleVersion) {
getLogger().severe("----------------------------------------------------------------------");
getLogger().severe("Your proxy version is not compatible with this build of LuckPerms. :(");
getLogger().severe("");
getLogger().severe("This is most likely because you are using an old/outdated version of BungeeCord.");
getLogger().severe("If you need 1.7 support, replace your BungeeCord.jar file with the latest build of");
getLogger().severe("'Travertine' from here:");
getLogger().severe("==> https://papermc.io/downloads#Travertine");
getLogger().severe("");
getLogger().severe("The proxy will now shutdown.");
getLogger().severe("----------------------------------------------------------------------");
getProxy().stop();
return;
}
this.startTime = Instant.now();
try {
this.plugin.enable();
} finally {
@@ -129,6 +158,10 @@ public class LPBungeeBootstrap extends Plugin implements LuckPermsBootstrap {
@Override
public void onDisable() {
if (this.incompatibleVersion) {
return;
}
this.plugin.disable();
}
@@ -150,7 +183,7 @@ public class LPBungeeBootstrap extends Plugin implements LuckPermsBootstrap {
}
@Override
public long getStartupTime() {
public Instant getStartupTime() {
return this.startTime;
}
@@ -218,18 +251,46 @@ public class LPBungeeBootstrap extends Plugin implements LuckPermsBootstrap {
}
@Override
public Stream<String> getPlayerList() {
return getProxy().getPlayers().stream().map(ProxiedPlayer::getName);
public Collection<String> getPlayerList() {
Collection<ProxiedPlayer> players = getProxy().getPlayers();
List<String> list = new ArrayList<>(players.size());
for (ProxiedPlayer player : players) {
list.add(player.getName());
}
return list;
}
@Override
public Stream<UUID> getOnlinePlayers() {
return getProxy().getPlayers().stream().map(ProxiedPlayer::getUniqueId);
public Collection<UUID> getOnlinePlayers() {
Collection<ProxiedPlayer> players = getProxy().getPlayers();
List<UUID> list = new ArrayList<>(players.size());
for (ProxiedPlayer player : players) {
list.add(player.getUniqueId());
}
return list;
}
@Override
public boolean isPlayerOnline(UUID uniqueId) {
ProxiedPlayer player = getProxy().getPlayer(uniqueId);
return player != null && player.isConnected();
return getProxy().getPlayer(uniqueId) != null;
}
@Override
public @Nullable String identifyClassLoader(ClassLoader classLoader) throws Exception {
Class<?> pluginClassLoader = Class.forName("net.md_5.bungee.api.plugin.PluginClassloader");
if (pluginClassLoader.isInstance(classLoader)) {
PluginDescription desc = (PluginDescription) pluginClassLoader.getDeclaredField("desc").get(classLoader);
return desc.getName();
}
return null;
}
private static boolean checkIncompatibleVersion() {
try {
Class.forName("com.google.gson.internal.bind.TreeTypeAdapter");
return false;
} catch (ClassNotFoundException e) {
return true;
}
}
}
Loaded 100 of 502 files, more files were not shown because too many files have changed in this diff. Show more