Compare commits

..
107 Commits
Author SHA1 Message Date
Luck 175a21c0e4 API 4.0 - this is a breaking change 2017-11-07 22:05:49 +00:00
Luck a2801bff7c Inject a fake map into the super PermissionAttachment instance to fix compat with plugins using reflection to change attachment data (#528) 2017-11-07 17:05:16 +00:00
Luck f1a50f433f Throw exception earlier if an invalid permission is added/removed via Vault (#526) 2017-11-07 15:22:31 +00:00
Luck 76bfde4d77 Don't prevent modification of group.<group name> nodes with permission subcommands 2017-11-04 19:12:46 +00:00
Luck d393a4c9ca Alias /lp player --> /lp user 2017-11-03 16:26:54 +00:00
Luck 9ea6b0b8bb Use "None" instead of "global" when no server name is specified 2017-11-02 19:32:13 +00:00
Luck c20b91d5ea Increase actor_name field length to account for changes to logging 2017-11-02 19:28:49 +00:00
Luck fad8a38bdf Use a string version of the full static context, as opposed to the server name 2017-11-02 19:25:35 +00:00
Luck 2440a38e82 add RedisBungee context calculator for the BungeeCord platform 2017-11-02 19:01:53 +00:00
Luck b7541c43c8 Add Sender#getNameWithLocation 2017-11-02 18:51:59 +00:00
Luck 0f10bb0bc9 Deprecate some unused methods in ImmutableNode, refactor #shouldApplyWithContext 2017-11-02 18:22:42 +00:00
Luck 357717886e Refactor the log creation process 2017-10-30 21:06:45 +00:00
Luck 94a4051d29 Add extra catch for null primary group in powerfulperms migration 2017-10-30 20:03:59 +00:00
Luck 0e581cbcf3 Increase actions actor_name column length (#525) 2017-10-30 20:03:03 +00:00
Luck 230d9725e8 cleanup & fix build 2017-10-29 20:37:29 +00:00
Luck dee41b315f Inject our own subscription map into the plugin manager instead of registering permissions individually for each player 2017-10-29 15:16:48 +00:00
Luck 72e6c75433 Cleanup / tidy up a number of classes 2017-10-29 12:58:49 +00:00
Luck ebe12f838c Fix user exports 2017-10-28 00:15:34 +01:00
Luck 7279fdc25f Fix changes not being saved for flatfile bulkupdate operations 2017-10-27 23:17:57 +01:00
Luck af5c859385 Fix temporary group display format in parent info command (#518) 2017-10-24 13:17:27 +01:00
Luck e8896fbdf7 Fix NPE on startup (#520) 2017-10-24 13:05:06 +01:00
Luck 54695b8046 Form more descriptive console & import usernames 2017-10-23 23:37:55 +01:00
Luck c573c755d2 Add option to cancel failed logins on BungeeCord variant 2017-10-23 16:59:07 +01:00
Luck b299301b69 Cleanup 2017-10-22 21:02:48 +01:00
Luck a3a7198777 More storage refactoring 2017-10-22 20:09:53 +01:00
Luck ca8cc91f37 Add group setdisplayname command (#493) 2017-10-22 16:58:53 +01:00
Luck 26c813c07b Mode storage refactoring - write group nodes into their own section in flatfile types (#502) 2017-10-22 14:22:52 +01:00
Luck f109cb684a Apply PermissionAttachment permissions at a higher priority than normal nodes using the transient system (#515) 2017-10-22 10:24:04 +01:00
Luck 1baefaade4 Try to find the most appropriate primary group before just adding a user to default - closes #506 2017-10-22 09:16:04 +01:00
Luck 5ac0f93214 Fix migrating null / non-existent usernames - reverts parts of 6bfeec6, fixes #514 2017-10-22 09:07:08 +01:00
Luck ecfbed00b1 Add config option to allow invalid usernames (#516) 2017-10-22 09:00:10 +01:00
Luck 6e429d6c78 Throw exception if directories cannot be created 2017-10-22 08:48:00 +01:00
Luck fa4bb0447a Rewrite flatfile storage (YAML & JSON) to use configurate, add HOCON storage method 2017-10-21 23:20:45 +01:00
Luck aa0be40124 Cleanup verbose handler & only send sponge OP command notification if the sender has permission 2017-10-16 18:55:17 +01:00
Luck 04d5310c04 Ensure users are a member of their primary group (#506) 2017-10-16 18:53:02 +01:00
Luck 74069af309 Throw an exception if we're unable to create the lib dir 2017-10-16 15:28:02 +01:00
Luck 6bfeec6d16 Fix updating primary groups for players who've never joined the server with SQL storage types (#500) 2017-10-16 14:43:58 +01:00
Luck 0f4c057395 Add highest_inherited and lowest_inherited meta stack elements 2017-10-15 15:13:23 +01:00
Luck 2fd74f3b7e fix MetaStackElement equality checks 2017-10-15 14:51:42 +01:00
Luck fb171a57c2 Ensure stored primary groups are always lowercase'd 2017-10-15 14:32:57 +01:00
Luck b26fc69e73 Remove usage of the now-redundant ExtractedContexts class, other misc cleanup 2017-10-15 14:23:51 +01:00
Luck 28961b1cfa Primary groups should come first, not last, when ordering groups for inheritance (#500) 2017-10-15 11:22:38 +01:00
Luck 597f2f7b1b Make parents-by-weight the default primary group calculation method 2017-10-14 22:54:54 +01:00
Luck 00cd8bb3f3 Prioritise primary groups when two inherited groups have the same weight (#500) 2017-10-14 22:50:50 +01:00
Luck 5732e60510 Remove cleanupUsers functionality
It was pretty useless (and wasn't implemented on SQL/Mongo storage types.) Data is naturally cleaned up over time as users are loaded/saved.
2017-10-12 20:38:08 +01:00
Luck 7b271e21e3 Update readme 2017-10-12 20:25:51 +01:00
Luck 1a5f9683c2 Fix build (whoops) 2017-10-12 20:21:49 +01:00
Luck 3b74d55442 Remove sponge comparison chart from readme 2017-10-12 20:19:53 +01:00
Luck 8920396360 Remove context pre-processing (mostly)
This approach isn't very effective when contexts are frequently changing, and it is hard to guess in advance which contexts are going to be in-use.

The Sponge version has proven that this whole system isn't really necessary.

Contexts for 'allow all' and 'global' are still pre-processed, however this should be significantly less work for the server. (even if it is being done async)
2017-10-12 20:17:52 +01:00
Luck 9c505e4402 Move Track methods away from checked exceptions, refactor ContextManager & primary group holders 2017-10-12 19:43:42 +01:00
Luck de4c6b384c Export using the /lp alias 2017-10-12 18:47:35 +01:00
Luck 6dec1ba8af Add hu_HU locale - thanks @montlikadani 2017-10-03 19:04:47 +01:00
Luck 9e6788de2f Refactor context manager 2017-10-03 19:02:14 +01:00
Luck 1bd7e5c9c4 Refactor sponge cache invalidation 2017-09-29 23:21:12 +01:00
Luck ec75fdddb5 Make verbose output more readable when a check is made against a lot of contexts 2017-09-27 20:11:17 +01:00
Luck f98aafb97c Fix world content on BungeeCord not being lowercase'd 2017-09-24 23:43:39 +01:00
Luck f4d2a0daf9 Bump dependency versions 2017-09-23 23:36:13 +01:00
Luck de8fb8de90 Only load slf4j if it's not already present on the server 2017-09-23 21:15:45 +01:00
Luck 44d137b8f9 Add project Code of Conduct 2017-09-22 16:09:50 +01:00
Luck 777c972bdc Cache SubjectReference instances, general cleanup 2017-09-21 21:59:27 +01:00
Luck f9efa15781 Re-add 'list' as an alias of 'permission info' - reverts #461, closes #473 2017-09-20 20:31:24 +01:00
Luck 1d1cd679dd Fix tab completion inconsistencies caused by argument rewrites - closes #481 2017-09-20 20:27:46 +01:00
Luck a9ed4686dd Avoid loading slf4j when running on Sponge 2017-09-19 22:13:02 +01:00
Luck 5b742eb5b2 Only schedule a user cleanup when LuckPermsApi#cleanupUser is called 2017-09-18 20:50:50 +01:00
Luck a3d78f4d0e Always load dependencies into the plugin classloader, and not it's parent - fixes #479 2017-09-17 19:30:43 +01:00
Luck 214929e5e5 Fix group expiry time not being exported (#474) 2017-09-14 19:10:18 +01:00
Luck 67602b8d85 Fix startup deadlock 2017-09-13 21:24:35 +01:00
Luck 2412edaa3a Cleanup subscription updates & UserManager#loadAllUsers 2017-09-13 20:18:48 +01:00
EuropeJing b1584aa2c3 Update zh-CN locale (#466) 2017-09-12 08:47:37 +01:00
Luck a14a416193 Make some ingame messages more consistent with the LP theme, general cleanup 2017-09-11 22:21:57 +01:00
Luck f3251e9407 Fix missing depend 2017-09-10 21:45:47 +01:00
Luck 096885d91f Add tracing to /lp verbose, API updates/cleanup, add login process event, and utilise string interning for faster context/node comparisons 2017-09-10 21:25:00 +01:00
Frank Zhang 368700156c Added uranium server support to DependencyManager (#464) 2017-09-08 14:25:49 +01:00
Luck 81cf50520b Fix NodeFactory#nodeAsCommand when unsetting meta nodes, fix meta unset command 2017-09-06 21:56:41 +01:00
Luck 649748c2d3 Bump caffeine and hikari versions (#462) 2017-09-05 23:56:45 +01:00
Luck bf7a708440 Send some extra data to the editor data object 2017-09-05 14:54:22 +01:00
Luck 8faa4d137b Fix some issues with the recent import/export changes 2017-09-04 23:20:21 +01:00
Luck 479bd91b74 Fix build 2017-09-04 22:32:02 +01:00
Luck 6bc7f1fca2 Refactor Importer/Exporter to use multiple threads (#317) 2017-09-04 22:24:07 +01:00
Luck dadd8e2c50 Don't publish/broadcast log entries from the import process - towards #317 2017-09-04 20:47:45 +01:00
Luck 03f342a21c Refactor command execution to use Locks per target instead of (effectively) one for all commands - towards #317 2017-09-04 20:42:21 +01:00
Luck 7fe5397e21 Fix incorrect usage of CompletableFuture#thenCombineAsync 2017-09-04 17:56:01 +01:00
Luck b6e1fd179e Add type argument to 'meta clear' command (#457) 2017-09-04 17:25:59 +01:00
Luck 754c7d8e3b Add config option to prevent primary group removal (#405) 2017-09-04 17:03:17 +01:00
Luck 925d3ccd1b Remove "list" alias for holder info (#461) 2017-09-04 16:55:10 +01:00
Luck d60d0ac9c5 Reduce buffer times, refactor MessagingService init 2017-09-04 16:49:43 +01:00
Luck e1b51dd6af Catch Throwable instead of Exception when setting up BukkitJsonMessageHandler 2017-09-04 13:03:09 +01:00
Luck fbe9d9f128 Run user cleanup task async and not on Storage init 2017-09-03 16:16:21 +01:00
Luck 509826949d Add login debug option 2017-08-27 13:54:26 +01:00
Luck 39aaa8a292 Update for latest Text version 2017-08-22 22:51:49 +01:00
Luck 069c76c53a Fix time test for node expiry times 2017-08-22 16:30:46 +01:00
Luck f45c0caa45 Refactor metastacks & primary group calculation 2017-08-21 15:45:29 +02:00
EuropeJing 41b0e10709 Update zh-CN locale (#450) 2017-08-21 10:38:30 +02:00
EuropeJing 8e8da20c13 Update zh-CN locale (#449) 2017-08-20 19:43:58 +02:00
Luck 269aa39378 Fix comparator and ensure @Nonnull annotated methods never return null in LogEntry 2017-08-20 14:51:46 +02:00
Luck ae8be97db7 Dispatch log entries via the messaging service 2017-08-20 13:32:52 +02:00
Luck f0ad40825b Refactor and cleanup Log classes 2017-08-20 01:35:03 +02:00
Luck 36d292747a Format times in /lp log into a shorter form 2017-08-19 11:38:08 +02:00
Luck b65d2262bf Remove "... data was saved to storage" messages 2017-08-19 11:08:28 +02:00
Luck a7ab0c6b36 Send a more helpful message when the non legacy version of LP is installed on Bukkit 1.7 (#437) 2017-08-18 13:58:41 +02:00
Luck c02fcb6508 Refactor weight lookups 2017-08-18 11:14:03 +02:00
Luck 07c38de44b Allow permission info command to be filtered by context (#442) 2017-08-18 10:44:54 +02:00
Luck 188d62c99b Fix issue with casting UUIDs in MongoDB backing (#443) 2017-08-18 10:35:56 +02:00
Luck 76b203ad10 Fix text dependencies being shaded 2017-08-17 10:12:08 +02:00
Luck 909844aef5 Update KyoriPowered/text dependency 2017-08-16 10:56:35 +02:00
Luck 046202719a Correctly export meta/prefix/suffix nodes to command strings 2017-08-14 18:58:12 +02:00
Luck ed15e2838d Fix checkinherits command not returning the inheritance data 2017-08-14 09:38:08 +02:00
384 changed files with 15034 additions and 11596 deletions

No files matched your search

+157 -167
View File
@@ -3,53 +3,46 @@
# Author: Luck
prefix: "&7&l[&b&lL&3&lP&7&l] &c"
log-info: "&7&l[&bL&3P&7&l] &3{0}"
log-warn: "&7&l[&bLuck&3Perms&7&l] &c[WARN] {0}"
log-error: "&7&l[&bLuck&3Perms&7&l] &4[ERROR] {0}"
empty: "{0}"
player-online: "&aOnline"
player-offline: "&cOffline"
loading-error: "Permissions data could not be loaded. Please try again later."
loading-error: "&cPermissions data could not be loaded. Please try again later."
op-disabled: "&bThe vanilla OP system is disabled on this server."
op-disabled-sponge: "&2Server Operator status has no effect when a permission plugin is installed. Please edit user data directly."
op-disabled-sponge: "&2Please note that Server Operator status has no effect on Sponge permission checks when a permission plugin is installed. Please edit user data directly."
log-info: "&7&l[&bL&3P&7&l] &3{0}"
log-warn: "&7&l[&bLuck&3Perms&7&l] &c[WARN] {0}"
log-error: "&7&l[&bLuck&3Perms&7&l] &4[ERROR] {0}"
log: "&3LOG &3&l> {0}"
verbose-log: "&3VERBOSE &3&l> {0}"
verbose-log: "&3VB &3&l> {0}"
export-log: "&3EXPORT &3&l> &f{0}"
export-log-progress: "&3EXPORT &3&l> &7{0}"
migration-log: "&3MIGRATION &7[&3{0}&7] &3&l> &f{1}"
migration-log-progress: "&3MIGRATION &7[&3{0}&7] &3&l> &7{1}"
command-not-recognised: "Command not recognised."
command-no-permission: "You do not have permission to use this command!"
already-haspermission: "{0} already has this permission!"
does-not-havepermission: "{0} does not have this permission set."
already-has-temp-permission: "{0} already has this permission set temporarily!"
does-not-have-temp-permission: "{0} does not have this permission set temporarily."
user-not-found: "&bUser could not be found."
user-not-online: "&bUser &a{0}&b is not online."
user-save-success: "&7(User data was saved to storage)"
user-save-error: "There was an error whilst saving the user."
group-not-found: "&bGroup could not be found."
group-save-success: "&7(Group data was saved to storage)"
group-save-error: "There was an error whilst saving the group."
track-not-found: "&bTrack could not be found."
track-save-success: "&7(Track data was saved to storage)"
track-save-error: "There was an error whilst saving the track."
user-invalid-entry: "&d{0}&c is not a valid username/uuid."
group-invalid-entry: "Group names can only contain alphanumeric characters."
track-invalid-entry: "Track names can only contain alphanumeric characters."
server-world-invalid-entry: "Server/world names can only contain alphanumeric characters and cannot exceed 36 characters in length."
use-inherit-command: "Use the 'parent add' and 'parent remove' commands instead of specifying the node."
verbose-invalid-filter: "&cInvalid verbose filter: &f{0}"
verbose-on: "&bVerbose checking output set to &aTRUE &bfor all permissions."
verbose-on-query: "&bVerbose checking output set to &aTRUE &bfor permissions matching filter: &f{0}"
verbose-off: "&bVerbose checking output set to &cFALSE&b."
verbose-recording-on: "&bVerbose recording set to &aTRUE &bfor all permissions."
verbose-recording-on-query: "&bVerbose recording set to &aTRUE &bfor permissions matching filter: &f{0}"
verbose-recording-upload-start: "&bVerbose recording was disabled. Uploading results..."
verbose-recording-url: "&aVerbose results URL:"
command-not-recognised: "&cCommand not recognised."
command-no-permission: "&cYou do not have permission to use this command!"
user-not-found: "&cA user for &4{0}&c could not be found."
user-not-online: "&aUser &b{0}&a is not online."
user-save-error: "&cThere was an error whilst saving user data for &4{0}&c."
group-not-found: "&cA group named &4{0}&c could not be found."
group-save-error: "&cThere was an error whilst saving group data for &4{0}&c."
track-not-found: "&cA track named &4{0}&c could not be found."
track-save-error: "&cThere was an error whilst saving track data for &4{0}&c."
user-invalid-entry: "&4{0}&c is not a valid username/uuid."
group-invalid-entry: "&4{0}&c is not a valid group name."
track-invalid-entry: "&4{0}&c is not a valid track name."
server-world-invalid-entry: "&cServer/world names can only contain alphanumeric characters and cannot exceed 36 characters in length."
use-inherit-command: "&cUse the 'parent add' and 'parent remove' commands instead of specifying the node."
verbose-invalid-filter: "&4{0}&c is not a valid verbose filter."
verbose-on: "&bVerbose logging &aenabled &bfor checks matching &aANY&b."
verbose-on-query: "&bVerbose logging &aenabled &bfor checks matching &a{0}&b."
verbose-off: "&bVerbose logging &cdisabled&b."
verbose-recording-on: "&bVerbose recording &aenabled &bfor checks matching &aANY&b."
verbose-recording-on-query: "&bVerbose recording &aenabled &bfor checks matching &a{0}&b."
verbose-upload-start: "&bVerbose logging &cdisabled&b. Uploading results..."
verbose-results-url: "&aVerbose results URL:"
tree-upload-start: "&bGenerating permission tree..."
tree-empty: "&aUnable to generate tree. No results were found."
tree-url: "&aPermission Tree URL:"
tree-empty: "&cUnable to generate tree. No results were found."
tree-url: "&aPermission tree URL:"
search-searching: "&aSearching for users and groups with &b{0}&a..."
search-searching-members: "&aSearching for users and groups who inherit from &b{0}&a..."
search-result: "&aFound &b{0}&a entries from &b{1}&a users and &b{2}&a groups."
@@ -57,13 +50,13 @@ search-showing-users: "&bShowing user entries:"
search-showing-groups: "&bShowing group entries:"
search-showing-users-with-page: "&bShowing user entries: {0}"
search-showing-groups-with-page: "&bShowing group entries: {0}"
apply-edits-invalid-code: "&aInvalid code. &7({0})"
apply-edits-unable-to-read: "&aUnable to read data using the given code. &7({0})"
apply-edits-no-target: "&aUnable to parse the target of the edit. Please supply it as an extra argument."
apply-edits-target-group-not-exists: "&aTarget group &b{0}&a does not exist."
apply-edits-target-user-not-uuid: "&aTarget user &b{0}&a is not a valid uuid."
apply-edits-target-user-unable-to-load: "&aUnable to load target user &b{0}&a."
apply-edits-target-unknown: "&aInvalid target. &7({0})"
apply-edits-invalid-code: "&cInvalid code. &7({0})"
apply-edits-unable-to-read: "&cUnable to read data using the given code. &7({0})"
apply-edits-no-target: "&cUnable to parse the target of the edit. Please supply it as an extra argument."
apply-edits-target-group-not-exists: "&cTarget group &4{0}&c does not exist."
apply-edits-target-user-not-uuid: "&cTarget user &4{0}&c is not a valid uuid."
apply-edits-target-user-unable-to-load: "&cUnable to load target user &4{0}&c."
apply-edits-target-unknown: "&cInvalid target. &7({0})"
apply-edits-success: "&aSuccessfully applied &b{0}&a nodes to &b{1}&a."
editor-upload-failure: "&cUnable to upload permission data to the editor."
editor-url: "&aEditor URL:"
@@ -72,85 +65,74 @@ create-success: "&b{0}&a was successfully created."
delete-success: "&b{0}&a was successfully deleted."
rename-success: "&b{0}&a was successfully renamed to &b{1}&a."
clone-success: "&b{0}&a was successfully cloned onto &b{1}&a."
already-inherits: "{0} already inherits '{1}'."
does-not-inherit: "{0} does not inherit '{1}'."
already-temp-inherits: "{0} already temporarily inherits '{1}'."
does-not-temp-inherit: "{0} does not temporarily inherit '{1}'."
track-already-contains: "Track {0} already contains the group '{1}'."
track-does-not-contain: "Track {0} does not contain the group '{1}'."
track-ambiguous-call: "The user specified is a member of multiple groups on this track. Unable to determine their location."
group-already-exists: "That group already exists!"
group-does-not-exist: "That group does not exist!"
group-load-error: "An unexpected error occurred. Group not loaded."
groups-load-error: "An unexpected error occurred. Unable to load all groups."
track-already-exists: "That track already exists!"
track-does-not-exist: "That track does not exist!"
track-load-error: "An unexpected error occurred. Track not loaded."
tracks-load-error: "An unexpected error occurred. Unable to load all tracks."
track-empty: "The track cannot be used as it is empty or contains only one group."
update-task-request: "&bUpdate task scheduled."
update-task-complete: "&aUpdate task finished."
update-task-complete-network: "&aUpdate task finished. Now attempting to push to other servers."
already-inherits: "&b{0}&a already inherits from &b{1}&a in context {2}&a."
does-not-inherit: "&b{0}&a does not inherit from &b{1}&a in context {2}&a."
already-temp-inherits: "&b{0}&a already temporarily inherits from &b{1}&a in context {2}&a."
does-not-temp-inherit: "&b{0}&a does not temporarily inherit from &b{1}&a in context {2}&a."
track-already-contains: "&b{0}&a already contains &b{1}&a."
track-does-not-contain: "&b{0}&a doesn't contain &b{1}&a."
track-ambiguous-call: "&4{0}&c is a member of multiple groups on this track. Unable to determine their location."
already-exists: "&4{0}&c already exists!"
does-not-exist: "&4{0}&c does not exist!"
group-load-error: "&cAn unexpected error occurred. Group not loaded."
groups-load-error: "&cAn unexpected error occurred. Unable to load all groups."
track-load-error: "&cAn unexpected error occurred. Track not loaded."
tracks-load-error: "&cAn unexpected error occurred. Unable to load all tracks."
track-empty: "&4{0}&c cannot be used as it is empty or contains only one group."
update-task-request: "&bAn update task has been requested. Please wait..."
update-task-complete: "&aUpdate task complete."
update-task-complete-network: "&aUpdate task complete. Now attempting to push to other servers."
update-task-push-success: "&aOther servers were notified via &b{0} Messaging &asuccessfully."
update-task-push-failure: "&cError whilst pushing changes to other servers."
update-task-push-failure-not-setup: "&cError whilst pushing changes to other servers. A messaging service has not been configured."
reload-config-success: "&aThe configuration file was reloaded. &7(some options will only apply after the server has restarted.)"
info: >
update-task-push-failure-not-setup: "&cError whilst pushing changes to other servers. &7(a messaging service has not been configured)"
reload-config-success: "&aThe configuration file was reloaded. &7(some options will only apply after the server has restarted)"
info-top: >
{PREFIX}&2Running &bLuckPerms v{0}&2 by &bLuck&2.\n
{PREFIX}&f- &3Platform: &f{1}\n
{PREFIX}&f- &3Storage Method: &f{2}\n
{PREFIX}&f- &3Server Name: &f{3}\n
{PREFIX}&f- &3Sync Interval: &a{4} &fminutes\n
{PREFIX}&f- &3Messaging Service: &f{5}\n
{PREFIX}&f- &bCounts:\n
{PREFIX}&f- &3Online Players: &a{6}\n
{PREFIX}&f- &3Loaded Users: &a{7}\n
{PREFIX}&f- &3Loaded Groups: &a{8}\n
{PREFIX}&f- &3Loaded Tracks: &a{9}\n
{PREFIX}&f- &3Log size: &a{10}\n
{PREFIX}&f- &3UUID Cache size: &a{11}\n
{PREFIX}&f- &3Translations loaded: &a{12}\n
{PREFIX}&f- &3Pre-process contexts: &a{13}\n
{PREFIX}&f- &3Context Calculators: &a{14}\n
{PREFIX}&f- &3Unique permissions: &a{15}\n
{PREFIX}&f- &bConfiguration:\n
{PREFIX}&f- &3Use Server UUIDs: {16}\n
{PREFIX}&f- &bPermission Calculation:\n
{PREFIX}&f- &3Including Global: {17}\n
{PREFIX}&f- &3Including Global World: {18}\n
{PREFIX}&f- &3Applying Global Groups: {19}\n
{PREFIX}&f- &3Applying Global World Groups: {20}\n
{PREFIX}&f- &3Applying Wildcards: {21}\n
{PREFIX}&f- &3Applying Regex: {22}\n
{PREFIX}&f- &3Applying Shorthand: {23}
create-group-error: "There was an error whilst creating the group."
delete-group-error: "There was an error whilst deleting the group."
delete-group-error-default: "You cannot delete the default group."
{PREFIX}&f- &3Server Brand: &f{2}\n
{PREFIX}&f- &3Server Version: &f{3}
info-middle: >
{PREFIX}&f- &bMessaging Type: &f{0}\n
{PREFIX}&f- &bInstance:\n
{PREFIX}&f- &3Server Name: &f{1}\n
{PREFIX}&f- &3Online Players: &a{2}\n
{PREFIX}&f- &3Unique Connections: &a{3}\n
{PREFIX}&f- &3Uptime: &7{4}\n
{PREFIX}&f- &3Local Data: &a{5} &7users, &a{6} &7groups, &a{7} &7tracks\n
{PREFIX}&f- &3Context Calculators: &a{8}\n
{PREFIX}&f- &3Known permissions: &a{9}\n
{PREFIX}&f- &3Active processors: &7{10}
create-error: "&cThere was an error whilst creating &4{0}&c."
delete-error: "&cThere was an error whilst deleting &4{0}&c."
delete-group-error-default: "&cYou cannot delete the default group."
groups-list: "&aGroups: &7(name, weight, tracks)"
groups-list-entry: "&f- &3{0} &7- &b{1}"
groups-list-entry-with-tracks: "&f- &3{0} &7- &b{1} &7- [&3{2}&7]"
create-track-error: "There was an error whilst creating the track."
delete-track-error: "There was an error whilst deleting the track."
tracks-list: "&aTracks: {0}"
listnodes: "&b{0}'s Nodes:"
listnodes-with-page: "&b{0}'s Nodes: {1}"
listnodes-temp: "&b{0}'s Temporary Nodes:"
listnodes-temp-with-page: "&b{0}'s Temporary Nodes: {1}"
listnodes: "&b{0}'s Permissions:"
listnodes-with-page: "&b{0}'s Permissions: {1}"
listnodes-temp: "&b{0}'s Temporary Permissions:"
listnodes-temp-with-page: "&b{0}'s Temporary Permissions: {1}"
listparents: "&b{0}'s Parent Groups:"
listparents-temp: "&b{0}'s Temporary Parent Groups:"
list-tracks: >
&b{0}'s Tracks:\n
{1}
list-tracks-empty: "{0} is not on any tracks."
list-tracks-empty: "&b{0}&a is not on any tracks."
context-pair-inline: "&3{0}=&b{1}"
context-pair--global-inline: "&eglobal"
context-pair-sep: "&a, "
context-pair: "&8(&7{0}=&f{1}&8)"
check-permission: "&b{0}&a has permission &b{1}&a set to {2}&a in context {3}&a."
check-inherits-permission: "&b{0}&a has permission &b{1}&a set to {2}&a in context {3}&a. &7(inherited from &a{4}&7)"
setpermission-success: "&aSet &b{0}&a to &b{1}&a for &b{2}&a in context {3}&a."
already-haspermission: "&b{0}&a already has &b{1}&a set in context {2}&a."
setpermission-temp-success: "&aSet &b{0}&a to &b{1}&a for &b{2}&a for a duration of &b{3}&a in context {4}&a."
already-has-temp-permission: "&b{0}&a already has &b{1}&a set temporarily in context {2}&a."
unsetpermission-success: "&aUnset &b{0}&a for &b{1}&a in context {2}&a."
does-not-have-permission: "&b{0}&a does not have &b{1}&a set in context {2}&a."
unset-temp-permission-success: "&aUnset temporary permission &b{0}&a for &b{1}&a in context {2}&a."
does-not-have-temp-permission: "&b{0}&a does not have &b{1}&a set temporarily in context {2}&a."
set-inherit-success: "&b{0}&a now inherits permissions from &b{1}&a in context {2}&a."
set-temp-inherit-success: "&b{0}&a now inherits permissions from &b{1}&a for a duration of &b{2}&a in context {3}&a."
set-parent-success: "&b{0}&a had their existing parent groups cleared, and now only inherits &b{1}&a in context {2}&a."
@@ -159,41 +141,46 @@ unset-inherit-success: "&b{0}&a no longer inherits permissions from &b{1}&a in c
unset-temp-inherit-success: "&b{0}&a no longer temporarily inherits permissions from &b{1}&a in context {2}&a."
clear-success: "&b{0}&a's permissions were cleared in context {1}&a. (&b{2}&a nodes were removed.)"
clear-success-singular: "&b{0}&a's permissions were cleared in context {1}&a. (&b{2}&a node was removed.)"
parent-clear-success: "&b{0}&a's parents were cleared in context {1}&a. &b{2}&a nodes were removed.)"
parent-clear-success-singular: "&b{0}&a's parents were cleared in context {1}&a. &b{2}&a node was removed.)"
parent-clear-track-success: "&b{0}&a's parents on track &b{1}&a were cleared in context {2}&a. &b{3}&a nodes were removed.)"
parent-clear-track-success-singular: "&b{0}&a's parents on track &b{1}&a were cleared in context {2}&a. &b{3}&a node was removed.)"
meta-clear-success: "&b{0}&a's meta was cleared in context {1}&a. &b{2}&a nodes were removed.)"
meta-clear-success-singular: "&b{0}&a's meta was cleared in context {1}&a. &b{2}&a node was removed.)"
illegal-date-error: "Could not parse date '{0}'."
past-date-error: "You cannot set a date in the past!"
parent-clear-success: "&b{0}&a's parents were cleared in context {1}&a. (&b{2}&a nodes were removed.)"
parent-clear-success-singular: "&b{0}&a's parents were cleared in context {1}&a. (&b{2}&a node was removed.)"
parent-clear-track-success: "&b{0}&a's parents on track &b{1}&a were cleared in context {2}&a. (&b{3}&a nodes were removed.)"
parent-clear-track-success-singular: "&b{0}&a's parents on track &b{1}&a were cleared in context {2}&a. (&b{3}&a node was removed.)"
meta-clear-success: "&b{0}&a's meta matching type &b{1}&a was cleared in context {2}&a. (&b{3}&a nodes were removed.)"
meta-clear-success-singular: "&b{0}&a's meta matching type &b{1}&a was cleared in context {2}&a. (&b{3}&a node was removed.)"
illegal-date-error: "&cCould not parse date &4{0}&c."
past-date-error: "&cYou cannot set a date in the past!"
chat-meta-prefix-header: "&b{0}'s Prefixes"
chat-meta-suffix-header: "&b{0}'s Suffixes"
meta-header: "&b{0}'s Meta"
chat-meta-entry: "&b-> {0} &f- &f\"{1}&f\" &8(&7inherited from &a{2}&8)"
chat-meta-entry-with-context: "&b-> {0} &f- &f\"{1}&f\" &8(&7inherited from &a{2}&8){3}"
meta-entry: "&b-> &a{0} &f= &f\"{1}&f\" &8(&7inherited from &a{2}&8)"
meta-entry-with-context: "&b-> &a{0} &f= &f\"{1}&f\" &8(&7inherited from &a{2}&8){3}"
chat-meta-entry: "&b-> {0} &f- &f'{1}&f' &8(&7inherited from &a{2}&8)"
chat-meta-entry-with-context: "&b-> {0} &f- &f'{1}&f' &8(&7inherited from &a{2}&8){3}"
meta-entry: "&b-> &a{0} &f= &f'{1}&f' &8(&7inherited from &a{2}&8)"
meta-entry-with-context: "&b-> &a{0} &f= &f'{1}&f' &8(&7inherited from &a{2}&8){3}"
chat-meta-prefix-none: "&b{0} has no prefixes."
chat-meta-suffix-none: "&b{0} has no suffixes."
meta-none: "&b{0} has no meta."
meta-invalid-priority: "Invalid priority '{0}'. Expected a number."
already-has-chat-meta: "{0} already has that {1} set."
does-not-have-chat-meta: "{0} doesn't have that {1} set."
add-chatmeta-success: "&b{0}&a had {1} &f\"{2}&f\"&a set at a priority of &b{3}&a in context {4}&a."
add-temp-chatmeta-success: "&b{0}&a had {1} &f\"{2}&f\"&a set at a priority of &b{3}&a for a duration of &b{4}&a in context {5}&a."
remove-chatmeta-success: "&b{0}&a had {1} &f\"{2}&f\"&a at priority &b{3}&a removed in context {4}&a."
meta-invalid-priority: "&cInvalid priority &4{0}&c. Expected a number."
already-has-chat-meta: "&b{0}&a already has {1} &f'{2}&f'&a set at a priority of &b{3}&a in context {4}&a."
already-has-temp-chat-meta: "&b{0}&a already has {1} &f'{2}&f'&a set temporarily at a priority of &b{3}&a in context {4}&a."
does-not-have-chat-meta: "&b{0}&a doesn't have {1} &f'{2}&f'&a set at a priority of &b{3}&a in context {4}&a."
does-not-have-temp-chat-meta: "&b{0}&a doesn't have {1} &f'{2}&f'&a set temporarily at a priority of &b{3}&a in context {4}&a."
add-chatmeta-success: "&b{0}&a had {1} &f'{2}&f'&a set at a priority of &b{3}&a in context {4}&a."
add-temp-chatmeta-success: "&b{0}&a had {1} &f'{2}&f'&a set at a priority of &b{3}&a for a duration of &b{4}&a in context {5}&a."
remove-chatmeta-success: "&b{0}&a had {1} &f'{2}&f'&a at priority &b{3}&a removed in context {4}&a."
bulk-remove-chatmeta-success: "&b{0}&a had all {1}es at priority &b{2}&a removed in context {3}&a."
remove-temp-chatmeta-success: "&b{0}&a had temporary {1} &f\"{2}&f\"&a at priority &b{3}&a removed in context {4}&a."
remove-temp-chatmeta-success: "&b{0}&a had temporary {1} &f'{2}&f'&a at priority &b{3}&a removed in context {4}&a."
bulk-remove-temp-chatmeta-success: "&b{0}&a had all temporary {1}es at priority &b{2}&a removed in context {3}&a."
already-has-meta: "{0} already has that meta key value pair set."
set-meta-success: "&aSet meta value for key &f\"{0}&f\"&a to &f\"{1}&f\"&a for &b{2}&a in context {3}&a."
set-meta-temp-success: "&aSet meta value for key &f\"{0}&f\"&a to &f\"{1}&f\"&a for &b{2}&a for a duration of &b{3}&a in context {4}&a."
unset-meta-success: "&aUnset meta value with key &f\"{0}&f\"&a for &b{1}&a in context {2}&a."
unset-meta-temp-success: "&aUnset temporary meta value with key &f\"{0}&f\"&a for &b{1}&a in context {2}&a."
bulk-update-invalid-data-type: "Invalid type. Was expecting 'all', 'users' or 'groups'."
bulk-update-invalid-constraint: "Invalid constraint &4{0}&c. Constraints should be in the format '&f<field> <comparison operator> <value>&c'."
bulk-update-invalid-comparison: "Invalid comparison operator '&4{0}&c'. Expected one of the following: &f== != ~~ ~!"
already-has-meta: "&b{0}&a already has meta key &f'{1}&f'&a set to &f'{2}&f'&a in context {3}&a."
already-has-temp-meta: "&b{0}&a already has meta key &f'{1}&f'&a temporarily set to &f'{2}&f'&a in context {3}&a."
doesnt-have-meta: "&b{0}&a doesn't have meta key &f'{1}&f'&a set in context {2}&a."
doesnt-have-temp-meta: "&b{0}&a doesn't have meta key &f'{1}&f'&a set temporarily in context {2}&a."
set-meta-success: "&aSet meta key &f'{0}&f'&a to &f'{1}&f'&a for &b{2}&a in context {3}&a."
set-meta-temp-success: "&aSet meta key &f'{0}&f'&a to &f'{1}&f'&a for &b{2}&a for a duration of &b{3}&a in context {4}&a."
unset-meta-success: "&aUnset meta key &f'{0}&f'&a for &b{1}&a in context {2}&a."
unset-meta-temp-success: "&aUnset temporary meta key &f'{0}&f'&a for &b{1}&a in context {2}&a."
bulk-update-invalid-data-type: "&cInvalid type. Was expecting 'all', 'users' or 'groups'."
bulk-update-invalid-constraint: "&cInvalid constraint &4{0}&c. Constraints should be in the format '&f<field> <comparison operator> <value>&c'."
bulk-update-invalid-comparison: "&cInvalid comparison operator '&4{0}&c'. Expected one of the following: &f== != ~~ ~!"
bulk-update-queued: "&aBulk update operation was queued. &7(&f{0}&7)"
bulk-update-confirm: "&aRun &b/{0} bulkupdate confirm {1} &ato execute the update."
bulk-update-unknown-id: "&aOperation with id &b{0}&a does not exist or has expired."
@@ -206,8 +193,8 @@ user-info-general: >
{PREFIX}&f- &3Status: {2}\n
{PREFIX}&f- &3Primary Group: &f{3}\n
{PREFIX}&f- &aCounts:\n
{PREFIX}&f- &3Permissions: &a{4}\n
{PREFIX}&f- &3Temporary Permissions: &a{5}\n
{PREFIX}&f- &3Nodes: &a{4}\n
{PREFIX}&f- &3Permissions: &a{5}\n
{PREFIX}&f- &3Prefixes: &a{6}\n
{PREFIX}&f- &3Suffixes: &a{7}\n
{PREFIX}&f- &3Meta: &a{8}
@@ -219,30 +206,30 @@ user-info-data: >
{PREFIX}&f- &3Current Suffix: {3}
info-parent-header: "&f- &aParent Groups:"
info-temp-parent-header: "&f- &aTemporary Parent Groups:"
user-removegroup-error-primary: "You cannot remove a user from their primary group."
user-removegroup-error-primary: "&aYou cannot remove a user from their primary group."
user-primarygroup-success: "&b{0}&a's primary group was set to &b{1}&a."
user-primarygroup-warn-option: "&cWarning: The primary group calculation method being used by this server &7({0}) &cmay not reflect this change."
user-primarygroup-error-alreadyhas: "The user already has this group set as their primary group."
user-primarygroup-warn-option: "&aWarning: The primary group calculation method being used by this server &7({0}) &amay not reflect this change."
user-primarygroup-error-alreadyhas: "&b{0}&a already has &b{1}&a set as their primary group."
user-primarygroup-error-notmember: "&b{0}&a was not already a member of &b{1}&a, adding them now."
user-track-error-not-contain-group: "The user specified isn't already in any groups on this track."
user-track-error-not-contain-group: "&b{0}&a isn't already in any groups on &b{1}&a."
user-track-added-to-first: "&b{0}&a isn't in any groups on this track, so they were added to the first group, &b{1}&a in context {2}&a."
user-promote-success: "&aPromoting user along track &b{0}&a from &b{1}&a to &b{2}&a in context {3}&a."
user-promote-error-endoftrack: "The end of track &4{0}&c was reached. Unable to promote user."
user-promote-success: "&aPromoting &b{0}&a along track &b{1}&a from &b{2}&a to &b{3}&a in context {4}&a."
user-promote-error-endoftrack: "&aThe end of track &b{0}&a was reached. Unable to promote &b{1}&a."
user-promote-error-malformed: >
{PREFIX}The next group on the track, {0}, no longer exists. Unable to promote user.\n
{PREFIX}Either create the group, or remove it from the track and try again.
user-demote-success: "&aDemoting user along track &b{0}&a from &b{1}&a to &b{2}&a in context {3}&a."
user-demote-endoftrack: "The end of track &4{0}&c was reached, so &4{1}&c was removed from &4{2}&c."
{PREFIX}&aThe next group on the track, &b{0}&a, no longer exists. Unable to promote user.\n
{PREFIX}&aEither create the group, or remove it from the track and try again.
user-demote-success: "&aDemoting &b{0}&a along track &b{1}&a from &b{2}&a to &b{3}&a in context {4}&a."
user-demote-endoftrack: "&aThe end of track &b{0}&a was reached, so &b{1}&a was removed from &b{2}&a."
user-demote-error-malformed: >
{PREFIX}The previous group on the track, {0}, no longer exists. Unable to demote user.\n
{PREFIX}Either create the group, or remove it from the track and try again.
{PREFIX}&aThe previous group on the track, &b{0}&a, no longer exists. Unable to demote user.\n
{PREFIX}&aEither create the group, or remove it from the track and try again.
group-info-general: >
{PREFIX}&b&l> &bGroup Info: &f{0}\n
{PREFIX}&f- &3Display Name: &f{1}\n
{PREFIX}&f- &3Weight: &f{2}\n
{PREFIX}&f- &aCounts:\n
{PREFIX}&f- &3Permissions: &a{3}\n
{PREFIX}&f- &3Temporary Permissions: &a{4}\n
{PREFIX}&f- &3Nodes: &a{3}\n
{PREFIX}&f- &3Permissions: &a{4}\n
{PREFIX}&f- &3Prefixes: &a{5}\n
{PREFIX}&f- &3Suffixes: &a{6}\n
{PREFIX}&f- &3Meta: &a{7}
@@ -251,39 +238,40 @@ track-info: >
{PREFIX}&b&l> &bShowing Track: &f{0}\n
{PREFIX}&f- &7Path: &f{1}
track-clear: "&b{0}&a's groups track was cleared."
track-append-success: "&aGroup &b{0}&a was successfully appended to track &b{1}&a."
track-insert-success: "&aGroup &b{0}&a was successfully inserted into track &b{1}&a at position &b{2}&a."
track-insert-error-number: "Expected number but instead received: {0}"
track-insert-error-invalid-pos: "Unable to insert at position {0}. Index out of bounds."
track-remove-success: "&aGroup &b{0}&a was successfully removed from track &b{1}&a."
log-load-error: "The log could not be loaded."
log-invalid-page: "Invalid page number."
log-invalid-page-range: "Invalid page number. Please enter a value between 1 and {0}."
track-append-success: "&aGroup &b{0}&a was appended to track &b{1}&a."
track-insert-success: "&aGroup &b{0}&a was inserted into track &b{1}&a at position &b{2}&a."
track-insert-error-number: "&cExpected number but instead received: {0}"
track-insert-error-invalid-pos: "&cUnable to insert at position &4{0}&c. &7(invalid position)"
track-remove-success: "&aGroup &b{0}&a was removed from track &b{1}&a."
log-load-error: "&cThe log could not be loaded."
log-invalid-page: "&cInvalid page number."
log-invalid-page-range: "&cInvalid page number. Please enter a value between &41&c and &4{0}&c."
log-no-entries: "&bNo log entries to show."
log-entry: "&b#{0} -> &8(&7{1} ago&8) {2}"
log-notify-console: "&cCannot toggle notifications for console."
log-notify-toggle-on: "&aEnabled&b logging output."
log-notify-toggle-off: "&cDisabled&b logging output."
log-notify-already-on: "You are already receiving notifications."
log-notify-already-off: "You aren't currently receiving notifications."
log-notify-unknown: "State unknown. Expecting \"on\" or \"off\"."
log-notify-already-on: "&cYou are already receiving notifications."
log-notify-already-off: "&cYou aren't currently receiving notifications."
log-notify-unknown: "&cState unknown. Expecting \"on\" or \"off\"."
log-search-header: "&aShowing recent actions for query &b{0} &a(page &f{1}&a of &f{2}&a)"
log-recent-header: "&aShowing recent actions (page &f{0}&a of &f{1}&a)"
log-recent-by-header: "&aShowing recent actions by &b{0} &a(page &f{1}&a of &f{2}&a)"
log-history-user-header: "&aShowing history for user &b{0} &a(page &f{1}&a of &f{2}&a)"
log-history-group-header: "&aShowing history for group &b{0} &a(page &f{1}&a of &f{2}&a)"
log-history-track-header: "&aShowing history for track &b{0} &a(page &f{1}&a of &f{2}&a)"
log-export-already-exists: "Error: File {0} already exists."
log-export-not-writable: "Error: File {0} is not writable."
log-export-empty: "The log is empty and therefore cannot be exported."
log-export-failure: "An unexpected error occured whilst writing to the file."
log-export-already-exists: "&cError: File &4{0}&c already exists."
log-export-not-writable: "&cError: File &4{0}&c is not writable."
log-export-empty: "&cThe log is empty and therefore cannot be exported."
log-export-failure: "&cAn unexpected error occured whilst writing to the file."
log-export-success: "&aSuccessfully exported the log to &b{0}&a."
import-already-running: "Another import process is already running. Please wait for it to finish and try again."
export-already-running: "Another export process is already running. Please wait for it to finish and try again."
import-log-doesnt-exist: "Error: File {0} does not exist."
import-log-not-readable: "Error: File {0} is not readable."
import-log-failure: "An unexpected error occured whilst reading from the log file."
import-progress: "&b(Import) &b-> &f{0} &fpercent complete &7- &b{1}&f/&b{2} &foperations complete with &c{3} &ferrors."
import-progress-sin: "&b(Import) &b-> &f{0} &fpercent complete &7- &b{1}&f/&b{2} &foperations complete with &c{3} &ferror."
import-already-running: "&cAnother import process is already running. Please wait for it to finish and try again."
export-already-running: "&cAnother export process is already running. Please wait for it to finish and try again."
import-log-doesnt-exist: "&cError: File &4{0}&c does not exist."
import-log-not-readable: "&cError: File &4{0}&c is not readable."
import-log-failure: "&cAn unexpected error occured whilst reading from the log file."
import-progress: "&b(Import) &b-> &f{0}&f% complete &7- &b{1}&f/&b{2} &foperations complete with &c{3} &ferrors."
import-progress-sin: "&b(Import) &b-> &f{0}&f% complete &7- &b{1}&f/&b{2} &foperations complete with &c{3} &ferror."
import-start: "&b(Import) &b-> &fStarting import process."
import-end-complete: "&b(Import) &a&lCOMPLETED &7- took &b{0} &7seconds - &7No errors."
import-end-complete-err: "&b(Import) &a&lCOMPLETED &7- took &b{0} &7seconds - &c{1} errors."
@@ -322,6 +310,7 @@ command-specs:
args:
"on|record|off|paste": "whether to enable/disable logging, or to paste the logged output"
"filter": "the filter to match entries against"
"--slim": "add \"--slim\" to exclude trace data from the pasted output"
tree:
description: "Generate a tree view of permissions"
usage: "/%s tree [selection] [max level] [player]"
@@ -606,8 +595,9 @@ command-specs:
"suffix": "the suffix string"
"context...": "the contexts to remove the suffix in"
meta-clear:
description: "Clears all chat meta"
description: "Clears all meta"
args:
"type": "the type of meta to remove"
"context...": "the contexts to filter by"
track-info:
description: "Gives info about the track"
+728
View File
@@ -0,0 +1,728 @@
# LuckPerms Language File
# Locale: hu_HU (Hungarian)
# Author: montlikadani
prefix: "&7&l[&b&lL&3&lP&7&l] &c"
empty: "{0}"
player-online: "&aOnline"
player-offline: "&cOffline"
loading-error: "&cNem sikerült betölteni az engedélyezési adatokat. Kérlek, próbáld újra később."
op-disabled: "&bA vanilla OP rendszer le van tiltva ezen a szerveren."
op-disabled-sponge: "&2Kérjük, vegye figyelembe, hogy a kiszolgálói operátor állapota nem befolyásolja a Sponge engedély-ellenőrzéseket, ha engedélyezési bővítmény van telepítve. Kérjük, közvetlenül módosítsa a felhasználói adatokat."
log-info: "&7&l[&bL&3P&7&l] &3{0}"
log-warn: "&7&l[&bLuck&3Perms&7&l] &c[FIGYELEM] {0}"
log-error: "&7&l[&bLuck&3Perms&7&l] &4[HIBA] {0}"
log: "&3LOG &3&l> {0}"
verbose-log: "&3VB &3&l> {0}"
export-log: "&3EXPORT &3&l> &f{0}"
export-log-progress: "&3EXPORT &3&l> &7{0}"
migration-log: "&3VÁNDORLÁS &7[&3{0}&7] &3&l> &f{1}"
migration-log-progress: "&3VÁNDORLÁS &7[&3{0}&7] &3&l> &7{1}"
command-not-recognised: "&cA parancs nem ismeretes."
command-no-permission: "&cNincs jogosultságod a használatára!"
user-not-found: "&cEgy felhasználó számára &4{0}&c nem található."
user-not-online: "&b{0}&a felhasználó nem elérhető."
user-save-error: "&cHiba történt a felhasználói adatok mentése közben &4{0}&c."
group-not-found: "&cEgy nevű csoport &4{0}&c nem található."
group-save-error: "&cHiba történt a csoportadatok mentése közben &4{0}&c."
track-not-found: "&cEgy nevű sáv &4{0}&c nem található."
track-save-error: "&cHiba történt a track adatok mentése közben &4{0}&c."
user-invalid-entry: "&4{0}&c nem érvényes felhasználónév/uuid."
group-invalid-entry: "&4{0}&c nem érvényes csoportnév."
track-invalid-entry: "&4{0}&c nem érvényes track név."
server-world-invalid-entry: "&cA szerver/világnév csak alfanumerikus karaktereket tartalmazhat, és nem haladhatja meg a 36 karakter hosszúságú karaktereket."
use-inherit-command: "&cHasználd a 'parent add' és a 'parent remove' parancsokat ahelyett, hogy meghatározta volna a csomópontot."
verbose-invalid-filter: "&4{0}&c nem érvényes kiterjedt szűrő."
verbose-on: "&bRészletes naplózás &aengedélyezve &ba megfelelő ellenőrzésekhez &aBÁRMILYEN&b."
verbose-on-query: "&bRészletes naplózás &aengedélyezve &ba megfelelő ellenőrzésekhez &a{0}&b."
verbose-off: "&bRészletes naplózás &cletiltva&b."
verbose-recording-on: "&bRészletes felvétel &aengedélyezve &ba megfelelő ellenőrzésekhez &aBÁRMILYEN&b."
verbose-recording-on-query: "&bRészletes felvétel &aengedélyezve &ba megfelelő ellenőrzésekhez &a{0}&b."
verbose-upload-start: "&bRészletes naplózás &cletiltva&b. Eredmények feltöltése..."
verbose-results-url: "&aRészletes eredmények URL-je:"
tree-upload-start: "&bEngedélyezési fa létrehozása..."
tree-empty: "&cNem lehet létrehozni a fát. Nincs találat."
tree-url: "&aEngedélyezési fa URL:"
search-searching: "&aFelhasználók és csoportok keresése a következővel: &b{0}&a..."
search-searching-members: "&aFelhasználók és csoportok keresése, akik örökölnek &b{0}&a..."
search-result: "&aTalál &b{0}&a bejegyzéseket a &b{1}&a felhasználók és &b{2}&a csoportok."
search-showing-users: "&bFelhasználói bejegyzések megjelenítése:"
search-showing-groups: "&bCsoportbejegyzések megjelenítése:"
search-showing-users-with-page: "&bFelhasználói bejegyzések megjelenítése: {0}"
search-showing-groups-with-page: "&bCsoportbejegyzések megjelenítése: {0}"
apply-edits-invalid-code: "&cÉrvénytelen kód. &7({0})"
apply-edits-unable-to-read: "&cA megadott kóddal nem lehet adatokat olvasni. &7({0})"
apply-edits-no-target: "&cNem lehet elemezni a szerkesztés célját. Kérjük, nyújtsa be, mint egy extra érv."
apply-edits-target-group-not-exists: "&cCélcsoport &4{0}&c nem létezik."
apply-edits-target-user-not-uuid: "&cCélcsoport &4{0}&c nem érvényes uuid."
apply-edits-target-user-unable-to-load: "&cA célfelhasználó nem tölthető be &4{0}&c."
apply-edits-target-unknown: "&cÉrvénytelen cél. &7({0})"
apply-edits-success: "&aSikeresen alkalmazott &b{0}&a csomópontok &b{1}&a."
editor-upload-failure: "&cNem sikerült feltölteni az engedélyezési adatokat a szerkesztőhöz."
editor-url: "&aSzerkesztő URL:"
check-result: "&aEngedély-ellenőrzés eredmény a felhasználó számára &b{0}&a engedélyért &b{1}&a: &f{2}"
create-success: "&b{0}&a sikeresen létrehozva."
delete-success: "&b{0}&a sikeresen törölve."
rename-success: "&b{0}&a sikeresen átnevezve &b{1}&a."
clone-success: "&b{0}&a sikeresen klónozva &b{1}&a."
already-inherits: "&b{0}&a már örökli &b{1}&a a szövegkörnyezetben {2}&a."
does-not-inherit: "&b{0}&a nem örököl &b{1}&a a szövegkörnyezetben {2}&a."
already-temp-inherits: "&b{0}&a már átmenetileg örökli &b{1}&a a szövegkörnyezetben {2}&a."
does-not-temp-inherit: "&b{0}&a nem átmenetileg örökli &b{1}&a a szövegkörnyezetben {2}&a."
track-already-contains: "&b{0}&a már tartalmazza &b{1}&a."
track-does-not-contain: "&b{0}&a nem tartalmaz &b{1}&a."
track-ambiguous-call: "&4{0}&c ezen a pályán több csoport tagja. Nem tudják meghatározni a helyüket."
already-exists: "&4{0}&c már létezik!"
does-not-exist: "&4{0}&c nem létezik!"
group-load-error: "&cVáratlan hiba történt. A csoport nincs betöltve."
groups-load-error: "&cVáratlan hiba történt. Nem sikerült betölteni az összes csoportot."
track-load-error: "&cVáratlan hiba történt. A nyomvonal nincs betöltve."
tracks-load-error: "&cVáratlan hiba történt. Nem sikerült betölteni az összes számot."
track-empty: "&4{0}&c nem használható, mivel üres vagy csak egy csoportot tartalmaz."
update-task-request: "&bFrissítési feladatot kértek. Kérlek, várj..."
update-task-complete: "&aA feladat frissítése befejeződött."
update-task-complete-network: "&aA feladat frissítése befejeződött. Most más szerverekre próbálkozni."
update-task-push-success: "&aMás szervereket értesítettek &b{0} Üzenetek &asikeresen."
update-task-push-failure: "&cHiba történt a többi kiszolgálóra történő változtatás közben."
update-task-push-failure-not-setup: "&cHiba történt a többi kiszolgálóra történő változtatás közben. &7(egy üzenetkezelő szolgáltatást nem konfigurált)"
reload-config-success: "&aA konfigurációs fájl újratöltve. &7(néhány opció csak a szerver újraindítása után fog működni)"
info-top: >
{PREFIX}&2Running &bLuckPerms v{0}&2 by &bLuck&2.\n
{PREFIX}&f- &3Emelvény: &f{1}\n
{PREFIX}&f- &3Szerver márka: &f{2}\n
{PREFIX}&f- &3Szerver verzió: &f{3}
info-middle: >
{PREFIX}&f- &bÜzenet típusa: &f{0}\n
{PREFIX}&f- &bPélda:\n
{PREFIX}&f- &3Szerver név: &f{1}\n
{PREFIX}&f- &3Elérhető játékosok: &a{2}\n
{PREFIX}&f- &3Egyedi kapcsolatok: &a{3}\n
{PREFIX}&f- &3Üzemidő: &7{4}\n
{PREFIX}&f- &3Helyi adatok: &a{5} &7felhasználó, &a{6} &7csoport, &a{7} &7pályák\n
{PREFIX}&f- &3Kontextus számológépek: &a{8}\n
{PREFIX}&f- &3Ismert engedélyek: &a{9}\n
{PREFIX}&f- &3Aktív processzorok: &7{10}
create-error: "&cHiba történt a létrehozás során &4{0}&c."
delete-error: "&cTörlés közben hiba történt &4{0}&c."
delete-group-error-default: "&cAz alapértelmezett csoportot nem törölheti."
groups-list: "&aCsoportok: &7(név, súly, pályák)"
groups-list-entry: "&f- &3{0} &7- &b{1}"
groups-list-entry-with-tracks: "&f- &3{0} &7- &b{1} &7- [&3{2}&7]"
tracks-list: "&aPályák: {0}"
listnodes: "&b{0}'s Jogok:"
listnodes-with-page: "&b{0} Jogai: {1}"
listnodes-temp: "&b{0} Ideiglenes engedélyei:"
listnodes-temp-with-page: "&b{0} Ideiglenes engedélyei: {1}"
listparents: "&b{0} Szülői csoportok:"
listparents-temp: "&b{0} Ideiglenes szülői csoportok:"
list-tracks: >
&b{0} Pályái:\n
{1}
list-tracks-empty: "&b{0}&a nincs rajta."
context-pair-inline: "&3{0}=&b{1}"
context-pair--global-inline: "&eglobális"
context-pair-sep: "&a, "
context-pair: "&8(&7{0}=&f{1}&8)"
check-permission: "&b{0}&a engedéllyel rendelkezik &b{1}&a állítva {2}&a a szövegkörnyezetben {3}&a."
check-inherits-permission: "&b{0}&a engedéllyel rendelkezik &b{1}&a állítva {2}&a a szövegkörnyezetben {3}&a. &7(örökölt &a{4}&7)"
setpermission-success: "&aBeállítva &b{0}&a-nak/-nek &b{1}&a, mert &b{2}&a a szövegkörnyezetben {3}&a."
already-haspermission: "&b{0}&a már van &b{1}&a összefüggésben {2}&a."
setpermission-temp-success: "&aBeállítva &b{0}&a-nak/-nek &b{1}&a, mert &b{2}&a egy ideig &b{3}&a a szövegkörnyezetben {4}&a."
already-has-temp-permission: "&b{0}&a már van &b{1}&a ideiglenesen környezetben {2}&a."
unsetpermission-success: "&aVisszaállítva &b{0}&a, mert &b{1}&a a szövegkörnyezetben {2}&a."
does-not-have-permission: "&b{0}&a nem rendelkezik &b{1}&a összefüggésben {2}&a."
unset-temp-permission-success: "&aIdeiglenes engedély érvénytelenítése &b{0}&a, mert &b{1}&a a szövegkörnyezetben {2}&a."
does-not-have-temp-permission: "&b{0}&a nem rendelkezik &b{1}&a ideiglenesen környezetben {2}&a."
set-inherit-success: "&b{0}&a most örökli az engedélyeket &b{1}&a a szövegkörnyezetben {2}&a."
set-temp-inherit-success: "&b{0}&a most örökli az engedélyeket &b{1}&a egy ideig &b{2}&a a szövegkörnyezetben {3}&a."
set-parent-success: "&b{0}&a meglévő szüloi csoportjaik tiszták, és most csak örökölnek &b{1}&a a szövegkörnyezetben {2}&a."
set-track-parent-success: "&b{0}&a meglévő szülői csoportjaik voltak a pályán &b{1}&a törölni, és most csak örökli &b{2}&a a szövegkörnyezetben {3}&a."
unset-inherit-success: "&b{0}&a többé nem örökli az engedélyeket &b{1}&a a szövegkörnyezetben {2}&a."
unset-temp-inherit-success: "&b{0}&a már nem ideiglenesen örökli az engedélyeket &b{1}&a a szövegkörnyezetben {2}&a."
clear-success: "&b{0}&a a jogosultságokat a kontextusban törölték {1}&a. (&b{2}&a a csomópontokat eltávolítottuk.)"
clear-success-singular: "&b{0}&a a jogosultságokat a kontextusban törölték {1}&a. (&b{2}&a csomópontot eltávolítottuk.)"
parent-clear-success: "&b{0}&a a szülei a kontextusban megszűntek {1}&a. (&b{2}&a a csomópontokat eltávolítottuk.)"
parent-clear-success-singular: "&b{0}&a a szülei a kontextusban megszűntek {1}&a. (&b{2}&a a csomópontokat eltávolítottuk.)"
parent-clear-track-success: "&b{0}&a szülők a pályán &b{1}&a összefüggésben {2}&a. (&b{3}&a a csomópontokat eltávolítottuk.)"
parent-clear-track-success-singular: "&b{0}&a szülők a pályán &b{1}&a összefüggésben {2}&a. (&b{3}&a csomópontot eltávolítottuk.)"
meta-clear-success: "&b{0}&a meta egyezéstípusa &b{1}&a összefüggésben {2}&a. (&b{3}&a a csomópontokat eltávolítottuk.)"
meta-clear-success-singular: "&b{0}&a meta egyezéstípusa &b{1}&a összefüggésben {2}&a. (&b{3}&a csomópontot eltávolítottuk.)"
illegal-date-error: "&cNem sikerült elemezni a dátumot &4{0}&c."
past-date-error: "&cNem állíthat be dátumot a múltban!"
chat-meta-prefix-header: "&b{0} Előtagjai"
chat-meta-suffix-header: "&b{0} Utótagja"
meta-header: "&b{0} Metája"
chat-meta-entry: "&b-> {0} &f- &f'{1}&f' &8(&7örökölt &a{2}&8)"
chat-meta-entry-with-context: "&b-> {0} &f- &f'{1}&f' &8(&7örökölt &a{2}&8){3}"
meta-entry: "&b-> &a{0} &f= &f'{1}&f' &8(&7örökölt &a{2}&8)"
meta-entry-with-context: "&b-> &a{0} &f= &f'{1}&f' &8(&7örökölt &a{2}&8){3}"
chat-meta-prefix-none: "&b{0} nincsenek előtagjai."
chat-meta-suffix-none: "&b{0} nincs utótagja."
meta-none: "&b{0} nincs meta."
meta-invalid-priority: "&cÉrvénytelen prioritás &4{0}&c. Várható szám."
already-has-chat-meta: "&b{0}&a már van {1} &f'{2}&f'&a elsőbbséget élvez &b{3}&a a szövegkörnyezetben {4}&a."
already-has-temp-chat-meta: "&b{0}&a már van {1} &f'{2}&f'&a ideiglenesen &b{3}&a a szövegkörnyezetben {4}&a."
does-not-have-chat-meta: "&b{0}&a nincs {1} &f'{2}&f'&a elsőbbséget élvez &b{3}&a a szövegkörnyezetben {4}&a."
does-not-have-temp-chat-meta: "&b{0}&a nincs {1} &f'{2}&f'&a ideiglenesen &b{3}&a a szövegkörnyezetben {4}&a."
add-chatmeta-success: "&b{0}&a volt {1} &f'{2}&f'&a elsőbbséget élvez &b{3}&a a szövegkörnyezetben {4}&a."
add-temp-chatmeta-success: "&b{0}&a volt {1} &f'{2}&f'&a elsőbbséget élvez &b{3}&a egy ideig &b{4}&a a szövegkörnyezetben {5}&a."
remove-chatmeta-success: "&b{0}&a volt {1} &f'{2}&f'&a prioritásként &b{3}&a eltávolítva a kontextusban {4}&a."
bulk-remove-chatmeta-success: "&b{0}&a minden volt {1}es a prioritás &b{2}&a eltávolítva a kontextusban {3}&a."
remove-temp-chatmeta-success: "&b{0}&a ideiglenes volt {1} &f'{2}&f'&a prioritásként &b{3}&a eltávolítva a kontextusban {4}&a."
bulk-remove-temp-chatmeta-success: "&b{0}&a minden ideiglenes volt {1}es prioritásként &b{2}&a eltávolítva a kontextusban {3}&a."
already-has-meta: "&b{0}&a már van meta kulcs &f'{1}&f'&a állítva &f'{2}&f'&a a szövegkörnyezetben {3}&a."
already-has-temp-meta: "&b{0}&a már van meta kulcs &f'{1}&f'&a átmenetileg beállítva &f'{2}&f'&a a szövegkörnyezetben {3}&a."
doesnt-have-meta: "&b{0}&a nincs meta kulcs &f'{1}&f'&a összefüggésben {2}&a."
doesnt-have-temp-meta: "&b{0}&a nincs meta kulcs &f'{1}&f'&a ideiglenesen a környezetben {2}&a."
set-meta-success: "&aÁllítva a meta kulcsot &f'{0}&f'&a-nak/-nek &f'{1}&f'&a, mert &b{2}&a a szövegkörnyezetben {3}&a."
set-meta-temp-success: "&aÁllítva a meta kulcsot &f'{0}&f'&a-nak/-nek &f'{1}&f'&a, mert &b{2}&a egy ideig &b{3}&a a szövegkörnyezetben {4}&a."
unset-meta-success: "&aLeválasztva a meta kulcsot &f'{0}&f'&a, mert &b{1}&a a szövegkörnyezetben {2}&a."
unset-meta-temp-success: "&aIdeiglenes meta kulcs kikapcsolása &f'{0}&f'&a, mert &b{1}&a a szövegkörnyezetben {2}&a."
bulk-update-invalid-data-type: "&cÉrvénytelen típus. Típusok 'all', 'users' vagy 'groups'."
bulk-update-invalid-constraint: "&cÉrvénytelen kényszer &4{0}&c. A korlátozásoknak formátumban kell lenniük '&f<mező> <összehasonlító operátor> <érték>&c'."
bulk-update-invalid-comparison: "&cÉrvénytelen összehasonlító operátor '&4{0}&c'. Várható az alábbiak közül: &f== != ~~ ~!"
bulk-update-queued: "&aA tömeges frissítés sorba állt. &7(&f{0}&7)"
bulk-update-confirm: "&aA futtatáshoz &b/{0} a tömeges frissítés megerősítéséhez {1} &aa frissítés végrehajtásához."
bulk-update-unknown-id: "&aMűködési id &b{0}&a nem létezik, vagy lejárt."
bulk-update-starting: "&aFolyamatos frissítés futtatása."
bulk-update-success: "&bA tömeges frissítés sikeresen befejeződött."
bulk-update-failure: "&cA tömeges frissítés sikertelen. Ellenőrizze a konzolt hiba esetén."
user-info-general: >
{PREFIX}&b&l> &bFelhasználói adatok: &f{0}\n
{PREFIX}&f- &3UUID: &f{1}\n
{PREFIX}&f- &3Állapot: {2}\n
{PREFIX}&f- &3Elsődleges csoport: &f{3}\n
{PREFIX}&f- &aGrófok:\n
{PREFIX}&f- &3Csomópontok: &a{4}\n
{PREFIX}&f- &3Jogok: &a{5}\n
{PREFIX}&f- &3Előtagok: &a{6}\n
{PREFIX}&f- &3Utótagok: &a{7}\n
{PREFIX}&f- &3Meta: &a{8}
user-info-data: >
{PREFIX}&f- &aGyorsítótár adat:\n
{PREFIX}&f- &3Kontextuális adatok vannak: {0}\n
{PREFIX}&f- &3Jelenlegi kontextusok: {1}\n
{PREFIX}&f- &3Jelenlegi előtag: {2}\n
{PREFIX}&f- &3Jelenlegi utótag: {3}
info-parent-header: "&f- &aSzülői csoportok:"
info-temp-parent-header: "&f- &aIdeiglenes szülői csoportok:"
user-removegroup-error-primary: "&aNem lehet eltávolítani egy felhasználót az elsődleges csoportból."
user-primarygroup-success: "&b{0}&a az elsődleges csoportot &b{1}&a."
user-primarygroup-warn-option: "&aFigyelmeztetés: A kiszolgáló által használt elsődleges csoportszámítási módszer &7({0}) &anem tükrözheti ezt a változást."
user-primarygroup-error-alreadyhas: "&b{0}&a már van &b{1}&a mint elsődleges csoportjuk."
user-primarygroup-error-notmember: "&b{0}&a már nem tagja &b{1}&a, hozzátéve őket."
user-track-error-not-contain-group: "&b{0}&a már nincsenek benne &b{1}&a."
user-track-added-to-first: "&b{0}&a nincsenek ezen a pályán egyetlen csoportban sem, így az első csoportba kerültek, &b{1}&a a szövegkörnyezetben {2}&a."
user-promote-success: "&aElőmozdítása &b{0}&a a pályán &b{1}&a-tól/-től &b{2}&a-nak/-nek &b{3}&a a szövegkörnyezetben {4}&a."
user-promote-error-endoftrack: "&aA pálya vége &b{0}&a elérte. Nem lehetett népszerűsíteni &b{1}&a."
user-promote-error-malformed: >
{PREFIX}&aA következő csoport a pályán, &b{0}&a, nem létezik többé. Nem sikerült népszerűsíteni a felhasználót.\n
{PREFIX}&aVagy hozzon létre egy csoportot, vagy távolítsa el a zeneszámból, és próbálkozzon újra.
user-demote-success: "&aLefokozandó &b{0}&a a pályán &b{1}&a-tól/-től &b{2}&a-nak/-nek &b{3}&a a szövegkörnyezetben {4}&a."
user-demote-endoftrack: "&aA pálya vége &b{0}&a, így elérte &b{1}&a eltávolították &b{2}&a."
user-demote-error-malformed: >
{PREFIX}&aAz előző csoport a pályán, &b{0}&a, nem létezik többé. Nem lehet lecsökkenteni a felhasználót.\n
{PREFIX}&aVagy hozzon létre egy csoportot, vagy távolítsa el a zeneszámból, és próbálkozzon újra.
group-info-general: >
{PREFIX}&b&l> &bCsoport info: &f{0}\n
{PREFIX}&f- &3Megjelenítendő név: &f{1}\n
{PREFIX}&f- &3Súly: &f{2}\n
{PREFIX}&f- &aGrófok:\n
{PREFIX}&f- &3Csomópontok: &a{3}\n
{PREFIX}&f- &3Jogok: &a{4}\n
{PREFIX}&f- &3Előtagok: &a{5}\n
{PREFIX}&f- &3Utótagok: &a{6}\n
{PREFIX}&f- &3Meta: &a{7}
group-set-weight: "&aA súly beállítása &b{0} &b{1}&a csoporthoz."
track-info: >
{PREFIX}&b&l> &bPálya megjelenítése: &f{0}\n
{PREFIX}&f- &7Pálya: &f{1}
track-clear: "&b{0}&a csoportok nyomvonalát törölték."
track-append-success: "&aCsoport &b{0}&a csatolták a követéshez &b{1}&a."
track-insert-success: "&aCsoport &b{0}&a beillesztésre került a pályára &b{1}&a helyzetben &b{2}&a."
track-insert-error-number: "&cVárható szám, de helyette beérkezett: {0}"
track-insert-error-invalid-pos: "&cNem helyezhető be a pozícióba &4{0}&c. &7(érvénytelen pozíció)"
track-remove-success: "&aCsoport &b{0}&a eltávolították a pályáról &b{1}&a."
log-load-error: "&cA napló nem tölthető be."
log-invalid-page: "&cÉrvénytelen oldalszám."
log-invalid-page-range: "&cÉrvénytelen oldalszám. Kérjük, adjon meg egy értéket &41&c és &4{0}&c."
log-no-entries: "&bNincs megjeleníthető naplóbejegyzés."
log-entry: "&b#{0} -> &8(&7{1} ezelőtt&8) {2}"
log-notify-console: "&cA konzolra vonatkozó értesítések átváltása nem lehetséges."
log-notify-toggle-on: "&aEngedélyezve a&b naplózási kimenet."
log-notify-toggle-off: "&cLetiltva a&b naplózási kimenet."
log-notify-already-on: "&cMár értesítést kapsz."
log-notify-already-off: "&cJelenleg nem kap értesítést."
log-notify-unknown: "&cÁllam ismeretlen. Várandós \"on\" vagy \"off\"."
log-search-header: "&aA lekérdezés legutóbbi műveleteinek megjelenítése &b{0} &a(oldal &f{1} &f{2}&a-ból/-ből)"
log-recent-header: "&aA legutóbbi műveletek megjelenítése (oldal &f{0} &f{1}&a-ból/-ből)"
log-recent-by-header: "&aLegutóbbi fellépések megjelenítése a következővel: &b{0} &a(oldal &f{1} &f{2}&a-ból/-ből)"
log-history-user-header: "&aA felhasználó előzményeinek megjelenítése &b{0} &a(oldal &f{1} &f{2}&a-ból/-ből)"
log-history-group-header: "&aA csoport előzményeinek megjelenítése &b{0} &a(oldal &f{1} &f{2}&a-ból/-ből)"
log-history-track-header: "&aA sáv előzményeinek megjelenítése &b{0} &a(oldal &f{1} &f{2}&a-ból/-ből)"
log-export-already-exists: "&cHiba: A fájl &4{0}&c már létezik."
log-export-not-writable: "&cHiba: A fájl &4{0}&c nem írható."
log-export-empty: "&cA napló üres, ezért nem exportálható."
log-export-failure: "&cVáratlan hiba történt a fájl írása közben."
log-export-success: "&aSikeresen exportálta a naplót &b{0}&a."
import-already-running: "&cEgy másik behozatali folyamat már fut. Kérjük, várjon, amíg befejeződik, és próbálkozzon újra."
export-already-running: "&cEgy másik exportfolyamat már fut. Kérjük, várjon, amíg befejeződik, és próbálkozzon újra."
import-log-doesnt-exist: "&cHiba: A fájl &4{0}&c nem létezik."
import-log-not-readable: "&cHiba: A fájl &4{0}&c nem olvasható."
import-log-failure: "&cA naplófájl olvasása közben váratlan hiba történt."
import-progress: "&b(Import) &b-> &f{0}&f% sikeres &7- &b{1}&f/&b{2} &fműveletekkel kiegészítve &c{3} &fhibák."
import-progress-sin: "&b(Import) &b-> &f{0}&f% sikeres &7- &b{1}&f/&b{2} &fműveletekkel kiegészítve &c{3} &fhiba."
import-start: "&b(Import) &b-> &fImportálás indítása."
import-end-complete: "&b(Import) &a&lSIKERES &7- vett &b{0} &7másodperc - &7Nincs hibák."
import-end-complete-err: "&b(Import) &a&lSIKERES &7- vett &b{0} &7másodperc - &c{1} hibák."
import-end-complete-err-sin: "&b(Import) &a&lSIKERES &7- vett &b{0} &7másodperc - &c{1} hiba."
import-end-error-header: >
{PREFIX}&b(Import) &7------------> &fHiba megjelenítése #&b{0} &7<------------\n
{PREFIX}&b(Import) &fFuttatás közben: &3Parancs #{1}\n
{PREFIX}&b(Import) &fParancs: &7{2}\n
{PREFIX}&b(Import) &fTípus: &3{3}\n
{PREFIX}&b(Import) &fKibocsátás:
import-end-error-content: "&b(Import) &b-> &c{0}"
import-end-error-footer: "&b(Import) &7<------------------------------------------>"
command-specs:
user:
description: "Felhasználói parancsok"
usage: "/%s user <felhasználó>"
group:
description: "Csoport parancsok"
usage: "/%s group <csoport>"
track:
description: "Sávparancsok"
usage: "/%s track <track>"
log:
description: "Napló parancsok"
usage: "/%s log"
sync:
description: "A szinkronizálás megváltozik a tárolóval"
usage: "/%s sync"
info:
description: "Általános plugin info nyomtatása"
usage: "/%s info"
verbose:
description: "Rendszeres engedély ellenőrzése"
usage: "/%s verbose <true|false> [szűrő]"
args:
"on|record|off|paste": "hogy bekapcsolják/kikapcsolják a naplózást, vagy beillesztik-e a naplózott kimenetet"
"filter": "a szűrő a bejegyzések ellen"
"--slim": "hozzáad \"--slim\" a nyomkövetési adatok kizárása a beillesztett kimenetből"
tree:
description: "Hozzon létre egy fa nézetet az engedélyekről"
usage: "/%s tree [kiválasztás] [max. szint] [játékos]"
args:
"selection": "a fa gyökere. Meghatározza \".\", hogy az összes engedélyt tartalmazza"
"max level": "hány fiókszintet kell visszaküldeni"
"player": "az elérhető játékos nevét ellenőrizni"
search:
description: "Keressen olyan felhasználókat/csoportokat, akiknek engedélye van"
usage: "/%s search <jog>"
args:
"permission": "a keresési engedély"
"page": "a megtekinteni kívánt oldalt"
check:
description: "Végezzen el egy szabványos engedélyt egy elérhető játékoson"
usage: "/%s check <felhasználó> <jog>"
args:
"user": "a felhasználó ellenőrizni"
"permission": "az ellenőrzés engedélye"
network-sync:
description: "A szinkronizálás a tárhely módosításával történik, és kérni fogja, hogy a hálózaton lévő összes többi szerver ugyanezt tegye"
usage: "/%s networksync"
import:
description: "Adatok importálása fájlból"
usage: "/%s import <fájl>"
args:
"file": "az importálni kívánt fájl"
export:
description: "Adatok exportálása egy fájlba"
usage: "/%s export <fájl>"
args:
"file": "az exportálni kívánt fájl"
reload-config:
description: "Betölt néhány konfigurációs beállítást"
usage: "/%s reloadconfig"
bulk-update:
description: "Végezzen ömlesztett változás lekérdezéseket az összes adaton"
usage: "/%s bulkupdate"
args:
"data type": "a megváltoztatott adatok típusa. ('all', 'users' vagy 'groups')"
"action": "az elvégzendő művelet az adatokon. ('update' vagy 'delete')"
"action field": "a cselekvési terület. Csak a szükséges 'update'. ('permission', 'server' vagy 'world')"
"action value": "a cserélendő érték. Csak a szükséges 'update'."
"constraint...": "a frissítéshez szükséges korlátokat"
migration:
description: "Migrációs parancsok"
usage: "/%s migration"
apply-edits:
description: "A webszerkesztő által végrehajtott engedély módosításokat alkalmazza"
usage: "/%s applyedits <kód> [cél]"
args:
"code": "az adatok egyedi kódja"
"target": "aki az adatokat alkalmazza"
create-group:
description: "Létrehoz egy új csoportot"
usage: "/%s creategroup <csoport>"
args:
"name": "a csoport neve"
delete-group:
description: "Csoport eltávolítása"
usage: "/%s deletegroup <csoport>"
args:
"name": "a csoport neve"
list-groups:
description: "Felsorolja az összes csoportot a platformon"
usage: "/%s listgroups"
create-track:
description: "Hozzon létre egy új számot"
usage: "/%s createtrack <track>"
args:
"name": "a pálya neve"
delete-track:
description: "Egy szám törlése"
usage: "/%s deletetrack <track>"
args:
"name": "a pálya neve"
list-tracks:
description: "Felsorolja az összes zeneszámot a platformon"
usage: "/%s listtracks"
user-info:
description: "Információkat jelenít meg a felhasználóról"
user-switchprimarygroup:
description: "A felhasználó elsődleges csoportjának átváltása"
args:
"group": "a csoport, amelyre váltani"
user-promote:
description: "Segít a felhasználónak egy számot feltölteni"
args:
"track": "a pálya, hogy elősegítse a felhasználó fel"
"context...": "a felhasználót elősegítő kontextusok"
user-demote:
description: "Megszünteti a felhasználót egy pályán"
args:
"track": "a sáv, amely leállítja a felhasználót"
"context...": "a felhasználókat lecsökkentő kontextusok"
group-info:
description: "Információkat ad a csoportról"
group-listmembers:
description: "Mutassa be a csoportból öröklődő felhasználókat/csoportokat"
args:
"page": "a megtekinteni kívánt oldal"
group-setweight:
description: "Beállítja a csoportok súlyát"
args:
"weight": "a beállítani kívánt súly"
group-rename:
description: "Átnevezi a csoportot"
args:
"name": "az új név"
group-clone:
description: "Klónozza a csoportot"
args:
"name": "a klónozni kívánt csoport neve"
holder-editor:
description: "Megnyitja a webes engedély-szerkesztőt"
holder-showtracks:
description: "Megadja az objektum sávjait"
holder-clear:
description: "Eltávolítja az összes jogosultságot, szülőt és meta-t"
args:
"context...": "a szövegszűrés körülményei"
permission:
description: "Jogok szerkesztése"
parent:
description: "Örökletek szerkesztése"
meta:
description: "Metaadatértékek szerkesztése"
permission-info:
description: "Megadja az objektum engedélyezési csomópontjait"
args:
"page": "a megtekinteni a kívánt oldalt"
"filter": "a string szűrni"
permission-set:
description: "Beállítja az objektum engedélyét"
args:
"node": "a beállítandó jog csomópont"
"true|false": "a csomópont értéke"
"context...": "az engedéllyel rendelkező szövegkörnyezetek"
permission-unset:
description: "Megadja az objektum engedélyét"
args:
"node": "az engedéllyel rendelkező csomópont kiiktatásához"
"context...": "az engedélyek eltávolításának kontextusai"
permission-settemp:
description: "Beállítja az objektum ideiglenes engedélyét"
args:
"node": "a beállítandó engedély csomópont"
"true|false": "a csomópont értéke"
"duration": "amíg az engedély csomópontja lejárt"
"context...": "az engedéllyel rendelkező szövegkörnyezetek"
permission-unsettemp:
description: "Megadja az ideiglenes engedélyt az objektumhoz"
args:
"node": "az engedéllyel rendelkező csomópont kiiktatásához"
"context...": "az engedélyek eltávolításának kontextusai"
permission-check:
description: "Ellenőrzi, hogy az objektumnak van-e engedélyezési csomópontja"
args:
"node": "az engedéllyel rendelkező csomópontot"
"context...": "a bejelentkezés körülményei"
permission-check-inherits:
description: "Ellenőrzi, hogy az objektum örökli-e az engedélyezési csomópontot"
args:
"node": "az engedéllyel rendelkező csomópontot"
"context...": "a bejelentkezés körülményei"
parent-info:
description: "Megadja azokat a csoportokat, amelyeket ez az objektum örököl"
parent-set:
description: "Eltávolítja az összes többi csoportot, amelyet az objektum már örököl, és hozzáadja azokat az adott objektumhoz"
args:
"group": "a csoport, amelyre állít"
"context...": "a csoport beállításához szükséges kontextusok"
parent-add:
description: "Beállít egy másik csoportot az objektumhoz, hogy örökli a jogokat"
args:
"group": "a csoport örökölni"
"context...": "a csoportot örökli a szövegkörnyezetben"
parent-remove:
description: "Eltávolítja a korábban beállított öröklési szabályt"
args:
"group": "a csoport eltávolítani"
"context...": "a csoport eltávolításának kontextusai"
parent-set-track:
description: "Eltávolítja az összes többi olyan csoportot, amelyet az adott objektum már örököl az adott sávon, és hozzáadja azokat az adott sávhoz"
args:
"track": "a pálya bekapcsolásához"
"group": "a beállítani kívánt csoportot, vagy a csoport helyzetét az adott sávon"
"context...": "a csoport beállításához szükséges kontextusok"
parent-add-temp:
description: "Egy másik csoportot állít be, hogy az objektum átmenetileg átengedi az engedélyeket"
args:
"group": "a csoport örökölni"
"duration": "a csoporttagság időtartamát"
"context...": "a csoportot örökli a szövegkörnyezetben"
parent-remove-temp:
description: "Eltávolítja a korábban beállított ideiglenes öröklési szabályt"
args:
"group": "a csoport eltávolítani"
"context...": "a csoport eltávolításának kontextusai"
parent-clear:
description: "Törli az összes szülőt"
args:
"context...": "a szövegszűrés körülményei"
parent-clear-track:
description: "Törli az összes szülőt egy adott pályán"
args:
"track": "az eltávolítandó sáv"
"context...": "a szövegszűrés körülményei"
meta-info:
description: "Az összes csevegési meta megjelenítése"
meta-set:
description: "Meta értéket állít be"
args:
"key": "a beállítani kívánt kulcs"
"value": "a beállítani kívánt érték"
"context...": "a meta-pár beillesztésének kontextusai"
meta-unset:
description: "Megszünteti a meta értéket"
args:
"key": "a kiiktatás kulcsa"
"context...": "a meta-pár eltávolításának kontextusai"
meta-settemp:
description: "Átmenetileg beállítja a metaértéket"
args:
"key": "a beállítani kívánt kulcs"
"value": "a beállítani kívánt érték"
"duration": "amíg a meta érték lejár"
"context...": "a meta-pár beillesztésének kontextusai"
meta-unsettemp:
description: "Megszünteti az ideiglenes metaértéket"
args:
"key": "a kiiktatás kulcsa"
"context...": "a metapár eltávolításának kontextusai"
meta-addprefix:
description: "Hozzáad egy előtagot"
args:
"priority": "az elsőbbséget az előtag hozzáadásához"
"prefix": "az előtag karaktersorozat"
"context...": "az előtag beillesztésének kontextusai"
meta-addsuffix:
description: "Hozzáad egy utótagot"
args:
"priority": "elsőbbséget kell adni az utótagnak a következőhöz:"
"suffix": "az utótagsor"
"context...": "azok a kontextusok, amelyekhez hozzá kell adni az utótagot"
meta-removeprefix:
description: "Eltávolít egy előtagot"
args:
"priority": "az elsőbbség eltávolítása az előtagon"
"prefix": "az előtag karaktersorozat"
"context...": "az előtagot eltávolító szövegkörnyezetek"
meta-removesuffix:
description: "Eltávolít egy utótagot"
args:
"priority": "az elsőbbség, hogy eltávolítsák az utótagot"
"suffix": "az utótagsor"
"context...": "azok a kontextusok, amelyek eltávolítják az utótagot"
meta-addtemp-prefix:
description: "Privát előtagot ad át ideiglenesen"
args:
"priority": "az elsőbbséget az előtag hozzáadásához"
"prefix": "az előtag karaktersorozat"
"duration": "az időtartam, amíg az előtag lejár"
"context...": "az előtag beillesztésének kontextusai"
meta-addtemp-suffix:
description: "Egy ideiglenes utótagot ad hozzá"
args:
"priority": "elsőbbséget kell adni az utótagnak a következőhöz:"
"suffix": "az utótagsor"
"duration": "az időtartam, amíg az utótag lejár"
"context...": "azok a kontextusok, amelyekhez hozzá kell adni az utótagot"
meta-removetemp-prefix:
description: "Eltávolít ideiglenes előtagot"
args:
"priority": "az elsőbbség eltávolítása az előtagon"
"prefix": "az előtag karaktersorozat"
"context...": "az előtagot eltávolító szövegkörnyezetek"
meta-removetemp-suffix:
description: "Eltávolítja az ideiglenes utótagot"
args:
"priority": "az elsőbbség, hogy eltávolítsák az utótagot"
"suffix": "az utótagsor"
"context...": "azok a kontextusok, amelyek eltávolítják az utótagot"
meta-clear:
description: "Törli az összes metódust"
args:
"type": "a eltávolítandó meta típusa"
"context...": "a szövegszűrés körülményei"
track-info:
description: "Ad információt a pályáról"
track-append:
description: "Csatol egy csoportot a pálya végére"
args:
"group": "a csoport hozzáfűzése"
track-insert:
description: "Beilleszti a csoportot egy adott pozícióban a pálya mentén"
args:
"group": "beszúrni a kívánt csoport"
"position": "a helyzet a csoport beillesztésére (az első pozíció a pályán 1)"
track-remove:
description: "Eltávolít egy csoportot a sávról"
args:
"group": "a csoport eltávolítani"
track-clear:
description: "Törli a csoportokat a pályán"
track-rename:
description: "Nevezze át a sávot"
args:
"name": "az új név"
track-clone:
description: "Klónozza a sávot"
args:
"name": "a pálya neve, hogy klónozzanak"
log-recent:
description: "A legutóbbi műveletek megtekintése"
args:
"user": "a felhasználó neve/uuid-je, hogy kiszűrje"
"page": "a megtekinteni kívánt oldalszám"
log-search:
description: "Keressen egy bejegyzés naplót"
args:
"query": "keresési lekérdezés"
"page": "a megtekinteni kívánt oldalszám"
log-notify:
description: "A naplóbejelentések módosítása"
args:
"on|off": "hogy be vagy ki van kapcsolva"
log-user-history:
description: "A felhasználó előzményeinek megtekintése"
args:
"user": "a felhasználó neve/uuid-je"
"page": "a megtekinteni kívánt oldalszám"
log-group-history:
description: "A csoport előzményeinek megtekintése"
args:
"group": "a csoport neve"
"page": "a megtekinteni kívánt oldalszám"
log-track-history:
description: "A sáv előzményeinek megtekintése"
args:
"track": "a pálya neve"
"page": "a megtekinteni kívánt oldalszám"
sponge:
description: "Extra Szivacs adatok szerkesztése"
usage: "/%s sponge <gyűjtemény> <tárgy>"
args:
"collection": "a lekérdezésre kerülő gyűjtemény"
"subject": "módosítani a tárgyat"
sponge-permission-info:
description: "A tárgyhoz tartozó jogokkal kapcsolatos információkat jeleníti meg"
args:
"contexts...": "a szövegszűrés körülményei"
sponge-permission-set:
description: "Engedélyt ad a Tárgyhoz"
args:
"node": "a beállítandó engedély csomópont"
"tristate": "az engedélyhez szükséges érték megadása"
"contexts...": "az engedéllyel rendelkező szövegkörnyezetek"
sponge-permission-clear:
description: "Törli a Tárgy engedélyeit"
args:
"contexts...": "a szövegkörnyezetek engedélyeinek törlése"
sponge-parent-info:
description: "Megjeleníti a tárgy szüleinek adatait"
args:
"contexts...": "a szövegszűrés körülményei"
sponge-parent-add:
description: "Szülő hozzáadása a tárgyhoz"
args:
"collection": "az a tárgyi gyűjtemény, ahol a szülő Tárgy"
"subject": "a szülő Tárgy neve"
"contexts...": "a szülő hozzáadásának összefüggései"
sponge-parent-remove:
description: "Eltávolít egy szülőt a tárgytól"
args:
"collection": "az a tárgyi gyűjtemény, ahol a szülő Tárgy"
"subject": "a szülő Tárgy neve"
"contexts...": "a szülő eltávolításának összefüggései"
sponge-parent-clear:
description: "Törli a Tárgyi szülőket"
args:
"contexts...": "a szülők egyértelművé tételének összefüggései"
sponge-option-info:
description: "Megmutatja a tárggyal kapcsolatos információkat"
args:
"contexts...": "a szövegszűrés körülményei"
sponge-option-set:
description: "Beállít egy opciót a Tárgyhoz"
args:
"key": "a beállítani kívánt kulcs"
"value": "a kulcs beállításához"
"contexts...": "az opció beállításához szükséges kontextusok"
sponge-option-unset:
description: "Lefuttat egy opciót a Tárgyhoz"
args:
"key": "a kiiktatás kulcsa"
"contexts...": "a kulcshoz való hozzáférés megszüntetésének kontextusai"
sponge-option-clear:
description: "Törli a Tárgy opciókat"
args:
"contexts...": "a szövegkörnyezetek egyértelművé tételének lehetőségei"
migration-command:
description: "Migrációs parancs"
migration-groupmanager:
description: "Migrációs parancs"
args:
"migrate as global": "ha a világi engedélyeket figyelmen kívül kell hagyni, és csak globális áttérést kell alkalmazni"
migration-powerfulperms:
description: "Migrációs parancs"
args:
"address": "a PP adatbázis címe"
"database": "a PP adatbázis neve"
"username": "a DB-be való bejelentkezéshez használt felhasználónév"
"password": "a DB-be való bejelentkezéshez használt jelszó"
"db table": "a PP tábla neve, ahol a játékos adatait tárolják"
+662 -237
View File
@@ -3,301 +3,726 @@
# Author: EuropeJing
prefix: "&7&l[&b&lL&3&lP&7&l] &c"
log-info: "&7&l[&bL&3P&7&l] &3{0}"
log-warn: "&7&l[&bLuck&3Perms&7&l] &c[警告] {0}"
log-error: "&7&l[&bLuck&3Perms&7&l] &4[错误] {0}"
empty: "{0}"
player-online: "&a在线"
player-offline: "&c离线"
loading-error: "无法加载权限数据,请稍后再试."
op-disabled: "&b此服务器上禁用了vanilla OP系统."
op-disabled-sponge: "&2服务器管理员状态在安装权限插件时无效,请直接编辑用户数据."
loading-error: "&c权限数据无法加载,请稍后再试。"
op-disabled: "&b此服务器上禁用了vanilla OP系统。"
op-disabled-sponge: "&2请注意,当安装权限插件时,服务器OP状态无效。请直接编辑用户数据。"
log-info: "&7&l[&bL&3P&7&l] &3{0}"
log-warn: "&7&l[&bLuck&3Perms&7&l] &c[警告] {0}"
log-error: "&7&l[&bLuck&3Perms&7&l] &4[错误] {0}"
log: "&3日志 &3&l> {0}"
verbose-log: "&3详细 &3&l> {0}"
export-log: "&3输出 &3&l> &f{0}"
export-log-progress: "&3输出 &3&l> &7{0}"
migration-log: "&3移动 &7[&3{0}&7] &3&l> &f{1}"
migration-log-progress: "&3移动 &7[&3{0}&7] &3&l> &7{1}"
command-not-recognised: "命令未被识别."
command-no-permission: "你没有权限使用这个命令!"
already-haspermission: "{0}已经有这个权限!"
does-not-havepermission: "{0}没有这个命令的权限."
already-has-temp-permission: "{0}已经设置此临时权限!"
does-not-have-temp-permission: "{0}没有设置此临时权限."
user-not-found: "&b找不到用户."
user-not-online: "&b用户&a{0}&b不在线."
user-no-data: "&b没有加载用户&a{0}&b任何数据."
user-save-success: "&7(保存用户数据到文件)"
user-save-error: "保存用户数据时出错."
user-create-fail: "创建新用户时出错."
group-not-found: "&b找不到组."
group-save-success: "&7(保存组数据到文件)"
group-save-error: "保存组数据时出错."
track-not-found: "&b无法找到轨道."
track-save-success: "&7(保存轨道到文件)"
track-save-error: "保存轨道时出错."
user-invalid-entry: "&d{0}&c不是有效的用户名/uuid."
group-invalid-entry: "组名只能包含字母数字字符."
track-invalid-entry: "轨道名称只能包含字母数字字符."
server-invalid-entry: "服务器名称只能包含字母数字字符."
use-inherit-command: "使用'parent add'和'parent remove'命令,而不是指定节点."
verbose-invalid-filter: "&c无效的详细过滤器: &f{0}"
verbose-on: "&b所有权限的详细输出设置为&a开&b."
verbose-on-query: "&b将匹配过滤器:&f{0}&b的权限详细输出设置为&a开&b."
verbose-off: "&b所有权限的详细输出设置为&c关&b."
verbose-recording-on: "&b所有权限的详细记录设置为&a开&b."
verbose-recording-on-query: "&b将匹配过滤器:&f{0}&b的权限详细记录设置为&a开&b."
verbose-recording-upload-start: "&b详细记录被禁用. 正在上传结果..."
verbose-recording-url: "&a详细结果的网址:"
command-not-recognised: "&c命令未被识别。"
command-no-permission: "&c你没有权限使用这个命令!"
user-not-found: "&c找不到用户&4{0}&c。"
user-not-online: "&a用户&b{0}&a不在线。"
user-save-error: "&c保存用户&4{0}&c数据时出错。"
group-not-found: "&b找不到名为&4{0}&c的组。"
group-save-error: "&c保存组&4{0}&c数据时出错。"
track-not-found: "&b无法找到为&4{0}&c的权限组轨道。"
track-save-error: "&c保存权限组轨道&4{0}&c时出错。"
user-invalid-entry: "&4{0}&c不是有效的用户名/uuid。"
group-invalid-entry: "&4{0}&c不是有效的组名称。"
track-invalid-entry: "&4{0}&c不是有效的权限组轨道名称。"
server-world-invalid-entry: "&c服务器/世界名称只能包含字母数字字符,长度不能超过36个字符。"
use-inherit-command: "&c使用'parent add'和'parent remove'命令,而不是指定节点。"
verbose-invalid-filter: "&4{0}&c不是有效的详细过滤器"
verbose-on: "&b将匹配过滤器:&aANY&b的权限详细输出设置为&a开&b。"
verbose-on-query: "&b将匹配过滤器:&a{0}&b的权限详细输出设置为&a开&b。"
verbose-off: "&b详细输出设置为&c关&b。"
verbose-recording-on: "&b将匹配过滤器:&aANY&b的权限详细记录设置为&a开&b。"
verbose-recording-on-query: "&b将匹配过滤器:&a{0}&b的权限详细记录设置为&a开&b。"
verbose-upload-start: "&b详细记录被禁用。正在上传结果..."
verbose-results-url: "&a详细结果的网址:"
tree-upload-start: "&b生成权限树..."
tree-empty: "&a无法生成树,没有找到结果."
tree-empty: "&c无法生成树,没有找到结果。"
tree-url: "&a权限树网址:"
search-searching: "&a使用&b{0}&a搜索用户和组..."
search-searching-members: "&a正在搜索继承自&b{0}&a的用户和组..."
search-result: "&a从&b{1}&a用户和&b{2}&a组找到&b{0}&a个条目."
search-result: "&a从&b{1}&a用户和&b{2}&a组找到&b{0}&a个条目。"
search-showing-users: "&b显示用户条目:"
search-showing-groups: "&b显示组条目:"
search-showing-users-with-page: "&b显示用户条目: {0}"
search-showing-groups-with-page: "&b显示组条目: {0}"
apply-edits-invalid-code: "&c无效的代码。&7({0})"
apply-edits-unable-to-read: "&c无法使用给定代码读取数据。&7({0})"
apply-edits-no-target: "&c无法解析编辑的目标。请提供它作为一个额外的参数。"
apply-edits-target-group-not-exists: "&c目标组&4{0}&c不存在。"
apply-edits-target-user-not-uuid: "&c目标用户&4{0}&c不是一个有效的uuid。"
apply-edits-target-user-unable-to-load: "&c无法加载目标用户&4{0}&c。"
apply-edits-target-unknown: "&c目标无效。&7({0})"
apply-edits-success: "&a成功应用&b{0}&a节点到&b{1}&a。"
editor-upload-failure: "&c无法将权限数据上传到编辑器。"
editor-url: "&a编辑器URL:"
check-result: "&a用户&b{0}&a获得允许&b{1}&a权限的检查结果&a: &f{2}"
create-success: "&b{0}&a成功创建."
delete-success: "&b{0}&a成功删除."
rename-success: "&b{0}&a成功被重命名为&b{1}&a."
clone-success: "&b{0}&a被成功地复制到了&b{1}&a."
already-inherits: "{0}已经继承'{1}'."
does-not-inherit: "{0}已不继承'{1}'."
already-temp-inherits: "{0}已临时继承'{1}'."
does-not-temp-inherit: "{0}不会临时继承'{1}'."
track-already-contains: "轨道{0}已经包含组'{1}'."
track-does-not-contain: "轨道{0}不包含组'{1}'."
track-ambiguous-call: "指定的用户是此轨道上多个组的成员,无法确定他们的位置."
group-already-exists: "该组已存在!"
group-does-not-exist: "组不存在!"
group-load-error: "发生意外错误,组未加载."
groups-load-error: "发生意外错误,无法加载所有组."
track-already-exists: "轨道已存在!"
track-does-not-exist: "轨道不存在!"
track-load-error: "发生意外错误. 轨道未加载."
tracks-load-error: "出现意外错误,无法加载所有轨道."
track-empty: "轨道不能使用,因为它是空的或只包含一个组."
update-task-request: "&b更新任务计划."
update-task-complete: "&a更新任务完成."
update-task-complete-network: "&a更新任务完成,现在尝试推送到其他服务器."
update-task-push-success: "&a其他服务器通过&b{0}成功通知."
update-task-push-failure: "&c将更改推送到其他服务器时出错."
update-task-push-failure-not-setup: "&c将更改推送到其他服务器时出错,未配置消息传递服务."
reload-config-success: "&a重新加载配置文件. &7(一些选项将仅在服务器重新启动后才应用.)"
info: >
{PREFIX}&2运行 &bLuckPerms v{0}&2 by &bLuck&2.\n
create-success: "&b{0}&a创建成功。"
delete-success: "&b{0}&a删除成功。"
rename-success: "&b{0}&a成功被重命名为&b{1}&a。"
clone-success: "&b{0}&a成功地复制到&b{1}&a。"
already-inherits: "&b{0}&a已经从环境&b{2}&a中继承&b{1}&a。"
does-not-inherit: "&b{0}&a不再从环境&b{2}&a中继承&b{1}&a。"
already-temp-inherits: "&b{0}&a已经从环境&b{2}&a中临时继承&b{1}&a。"
does-not-temp-inherit: "&b{0}&a不再从环境&b{2}&a中临时继承&b{1}&a。"
track-already-contains: "&a权限组轨道&b{0}&a已经包含组&b{1}&a。"
track-does-not-contain: "&a权限组轨道&b{0}&a不包含组&b{1}&a。"
track-ambiguous-call: "&c用户&4{0}&c是此权限组轨道上多个组的成员,无法确定他们的位置。"
already-exists: "&4{0}&c已存在!"
does-not-exist: "&4{0}&c不存在!"
group-load-error: "&c发生意外错误,组未加载。"
groups-load-error: "&c发生意外错误,无法加载所有组。"
track-load-error: "&c发生意外错误。权限组轨道未加载。"
tracks-load-error: "&c发生意外错误,无法加载所有权限组轨道。"
track-empty: "&c权限组轨道&4{0}&c不能使用,因为它是空的或只包含一个组。"
update-task-request: "&b已请求更新任务。请稍候..."
update-task-complete: "&a更新任务完成。"
update-task-complete-network: "&a更新任务完成,现在尝试推送到其他服务器。"
update-task-push-success: "&a其他服务器通过&b{0}消息传递成功通知。"
update-task-push-failure: "&c将更改推送到其他服务器时出错。"
update-task-push-failure-not-setup: "&c将更改推送到其他服务器时出错。&7(未配置消息传递服务)"
reload-config-success: "&a重新加载配置文件。&7(一些选项将仅在服务器重新启动后才应用)"
info-top: >
{PREFIX}&2运行 &bLuckPerms v{0}&2 by &bLuck&2。\n
{PREFIX}&f- &3平台: &f{1}\n
{PREFIX}&f- &3存储方式: &f{2}\n
{PREFIX}&f- &3服务器名称: &f{3}\n
{PREFIX}&f- &3同步间隔: &a{4} &f分钟\n
{PREFIX}&f- &3消息服务: &f{5}\n
{PREFIX}&f- &b统计:\n
{PREFIX}&f- &3在线玩家: &a{6}\n
{PREFIX}&f- &3加载用户: &a{7}\n
{PREFIX}&f- &3加载组: &a{8}\n
{PREFIX}&f- &3加载轨道: &a{9}\n
{PREFIX}&f- &3日志大小: &a{10}\n
{PREFIX}&f- &3UUID缓存大小: &a{11}\n
{PREFIX}&f- &3翻译加载: &a{12}\n
{PREFIX}&f- &3预处理环境: &a{13}\n
{PREFIX}&f- &3环境计算器: &a{14}\n
{PREFIX}&f- &3唯一权限: &a{15}\n
{PREFIX}&f- &b配置:\n
{PREFIX}&f- &3使用服务器UUIDs: {16}\n
{PREFIX}&f- &b权限计算:\n
{PREFIX}&f- &3包括全部: {17}\n
{PREFIX}&f- &3包括全部世界: {18}\n
{PREFIX}&f- &3应用全部组: {19}\n
{PREFIX}&f- &3应用全部世界组: {20}\n
{PREFIX}&f- &3应用通配符: {21}\n
{PREFIX}&f- &3应用正则表达式: {22}\n
{PREFIX}&f- &3应用速记: {23}
create-group-error: "创建组时出错."
delete-group-error: "删除组时出错."
delete-group-error-default: "您不能删除默认组."
groups-list: "&aGroups: {0}"
create-track-error: "创建轨道时出错."
delete-track-error: "删除轨道时出错."
tracks-list: "&a轨道: {0}"
listnodes: "&b{0}的节点:"
listnodes-with-page: "&b{0}的节点: {1}"
listnodes-temp: >
&b{0}的临时节点:\n
{1}
listparents: >
&b{0}的父组:\n
{1}
listparents-temp: >
&b{0}的临时父组:\n
{1}
listgroups: >
&b{0}的组:\n
{1}
listgroups-temp: >
&b{0}的临时组:\n
{1}
{PREFIX}&f- &3服务器型号: &f{2}\n
{PREFIX}&f- &3服务器版本: &f{3}
info-middle: >
{PREFIX}&f- &3消息类型: &f{0}\n
{PREFIX}&f- &b详情:\n
{PREFIX}&f- &3服务器名称: &f{1}\n
{PREFIX}&f- &3在线玩家: &a{2}\n
{PREFIX}&f- &3唯一连接: &a{3}\n
{PREFIX}&f- &3正常运行时间: &7{4}\n
{PREFIX}&f- &3本地数据: &a{5}&7用户,&a{6}&7组,&a{7}&7权限组轨道\n
{PREFIX}&f- &3环境统计: &a{8}\n
{PREFIX}&f- &3已知权限: &a{9}\n
{PREFIX}&f- &3活动处理器: &7{10}
create-group-error: "&c创建组&4{0}&c时出错。"
delete-group-error: "&c删除组&4{0}&c时出错。"
delete-group-error-default: "&c你不能删除默认组。"
groups-list: "&a组: &7(名字,权重,权限组轨道)"
groups-list-entry: "&f- &3{0} &7- &b{1}"
groups-list-entry-with-tracks: "&f- &3{0} &7- &b{1} &7- [&3{2}&7]"
tracks-list: "&a权限组轨道: {0}"
listnodes: "&b{0}的权限:"
listnodes-with-page: "&b{0}的权限: {1}"
listnodes-temp: "&b{0}的临时权限:"
listnodes-temp-with-page: "&b{0}的临时权限: {1}"
listparents: "&b{0}的父组"
listparents-temp: "&b{0}的临时父组:"
list-tracks: >
&b{0}的轨道:\n
&b{0}的权限组轨道:\n
{1}
list-tracks-empty: "{0}不在任何轨道上."
list-tracks-empty: "&b{0}&a不在任何权限组轨道上。"
context-pair-inline: "&3{0}=&b{1}"
context-pair--global-inline: "&e全局"
context-pair-end: "&a."
context-pair-sep: "&a, "
context-pair-sep: "&a,"
context-pair: "&8(&7{0}=&f{1}&8)"
check-permission: "&b在环境&b{3}&b中,&b{0}&b的权限&b{1}&b被设置为&b{2}&b."
check-permission-inherited: "&b{0}&a在环境&b{3}&a中的&b{1}&a被设置为&b{2}&a,继承自&b{4}&a."
setpermission-success: "&a在&b{3}&a中为&b{2}&a设置&b{0}&a为&b{1}&a."
setpermission-temp-success: "&a在环境&b{4}&a将&b{0}&a的&b{2}&a设为&b{1}&a,持续时间&b{3}&a."
unsetpermission-success: "&a在环境&b{2}&a中取消&b{1}&a的&b{0}&a."
unset-temp-permission-success: "&a在环境&b{2}&a中取消设置&b{1}&a的临时许可&b{0}&a."
set-inherit-success: "&b{0}&a现在在环境&b{2}&a中继承&b{1}&a的权限&a."
set-temp-inherit-success: "&b{0}&a现在在环境&b{3}&a中继承&b{1}&a的权限,持续时间&b{2}&a."
set-parent-success: "&b{0}&a现有的父组被清除,现在只在&b{2}&a中继承&b{1}&a."
unset-inherit-success: "&b{0}&a不再继承环境&b{2}&a中的&b{1}&a权限."
unset-temp-inherit-success: "&b{0}&a在环境{2}中不再临时从&b{1}&a继承权限."
clear-success: "&b{0}&a的权限被清除. (&b{1}&a节点被删除.)"
clear-success-singular: "&b{0}&a的权限被清除. (&b{1}&a节点已被删除.)"
parent-clear-success: "&b{0}&a的组被清除. (&b{1}&a节点被删除.)"
parent-clear-success-singular: "&b{0}&a的组被清除. (&b{1}&a节点已被删除.)"
meta-clear-success: "&b{0}&a的父组被清除. (&b{1}&a节点被删除.)"
meta-clear-success-singular: "已经清除了这个&b{0}&a的元数据. (&b{1}&a节点已被删除.)"
illegal-date-error: "无法解析日期'{0}'."
past-date-error: "您不能设定一个过去的日期!"
check-permission: "&b在环境&b{3}&b中,&b{0}&b的权限&b{1}&b被设置为&b{2}&b。"
check-inherits-permission: "&a在环境&b{3}&a中将&b{0}&a的&b{1}&a权限设置为&b{2}&a。&7(从&a{4}&7继承)"
setpermission-success: "&a在环境&b{3}&a中将&b{2}&a的&b{0}&a权限设置为&b{1}&a。"
already-haspermission: "&a在环境&b{2}&a中&b{0}&a已经设置权限&b{1}&a。"
setpermission-temp-success: "&a在环境&b{4}&a中将&b{2}&a的&b{0}&a临时权限设为&b{1}&a,持续时间&b{3}&a。"
already-has-temp-permission: "&a在环境&b{2}&a中&b{0}&a已经设置临时权限&b{1}&a。"
unsetpermission-success: "&a在环境&b{2}&a中取消&b{1}&a的&b{0}&a权限。"
does-not-have-permission: "&a在环境&b{2}&a中&b{0}&a没有设置权限&b{1}&a。"
unset-temp-permission-success: "&a在环境&b{2}&a中取消&b{1}&a的临时权限&b{0}&a。"
does-not-have-temp-permission: "&a在环境&b{2}&a中&b{0}&a没有设置临时权限&b{1}&a。"
set-inherit-success: "&b{0}&a现在在环境&b{2}&a中继承&b{1}&a的权限&a。"
set-temp-inherit-success: "&b{0}&a现在在环境&b{3}&a中继承&b{1}&a的权限,持续时间&b{2}&a。"
set-parent-success: "&b{0}&a现有的父组被清除,现在只在&b{2}&a中继承&b{1}&a。"
set-track-parent-success: "在权限组轨道&b{1}&a上&b{0}&a现有的父组被清除,现在只在&b{3}&a中继承&b{2}&a。"
unset-inherit-success: "&b{0}&a不再继承环境&b{2}&a中&b{1}&a的权限。"
unset-temp-inherit-success: "&b{0}&a在环境&b{2}&a中不再继承临时权限&b{1}&a。"
clear-success: "&b{0}&a的权限在环境&b{1}&a中被清除。(&b{2}&a节点已被删除。)"
clear-success-singular: "&b{0}&a的权限在环境&b{1}&a中被清除。(&b{2}&a节点已被删除。)"
parent-clear-success: "&b{0}&a的父组在环境&b{1}&a中被清除。(&b{2}&a节点被删除。)"
parent-clear-success-singular: "&b{0}&a的父组在环境&b{1}&a中被清除。(&b{2}&a节点被删除。)"
parent-clear-track-success: "在权限组轨道&b{1}&a上,&b{0}&a的父组在环境&b{2}&a中被清除。(&b{3}&a节点被删除。)"
parent-clear-track-success-singular: "在权限组轨道&b{1}&a上,&b{0}&a的父组在环境&b{2}&a中被清除。(&b{3}&a节点被删除。)"
meta-clear-success: "&b{0}&a的元数据匹配类型&b{1}&a在环境&b{2}&a中被清除。(&b{3}&a节点已被删除。)"
meta-clear-success-singular: "&b{0}&a的元数据匹配类型&b{1}&a在环境&b{2}&a中被清除。(&b{3}&a节点已被删除。)"
illegal-date-error: "&c无法解析日期'{0}'。"
past-date-error: "&c你不能设定一个过去的日期!"
chat-meta-prefix-header: "&b{0}的前缀"
chat-meta-suffix-header: "&b{0}的后缀"
meta-header: "&b{0}的元信息"
chat-meta-entry: "&b-> {0} &f- &f\"{1}&f\" &8(&7继承自&a{2}&8)"
chat-meta-entry-with-context: "&b-> {0} &f- &f\"{1}&f\" &8(&7继承自&a{2}&8){3}"
meta-entry: "&b-> &a{0} &f= &f\"{1}&f\" &8(&7继承自&a{2}&8)"
meta-entry-with-context: "&b-> &a{0} &f= &f\"{1}&f\" &8(&7继承自&a{2}&8){3}"
chat-meta-prefix-none: "&b{0}没有前缀."
chat-meta-suffix-none: "&b{0}没有后缀."
meta-none: "&b{0}没有元信息."
meta-invalid-priority: "优先级'{0}'无效. 应该是一个数字."
already-has-chat-meta: "{0}已经设置了{1}."
does-not-have-chat-meta: "{0}没有设置{1}."
add-chatmeta-success: "&b{0}&a在环境&b{4}&a中将优先级&b{3}&a设置为 {1} &f\"{2}&f\"."
add-temp-chatmeta-success: "&b{0}&a在环境&b{5}&a中,设置为优先级&b{3}&a的{1} &f\"{2}&f\"&a,持续时间为&b{4}&a."
remove-chatmeta-success: "&b{0}&a在&b{3}&a中删除了优先级&b{3}&a的{1} &f\"{2}&f\"&a."
bulk-remove-chatmeta-success: "&b{0}&a had all {1}es at priority &b{3}&a removed in context {4}&a."
remove-temp-chatmeta-success: "&b{0}&a在环境&b{4}&a中删除了优先级&b{3}&a的临时{1} &f\"{2}&f\"&a."
bulk-remove-temp-chatmeta-success: "&b{0}&a在环境&b{4}&a中移除了优先级&b{3}&a的所有临时&b{1}&a.."
already-has-meta: "{0}已经有了这个元信息集."
set-meta-success: "将环境&b{3}&a中的&b{2}&a的键&f\"{0}&f\"&a的元信息设置为&f\"{1}&f\"&a."
set-meta-temp-success: "&a在环境&b{4}&a中将&b{2}&a的键&f\"{0}&f\"&a的值设置为&f\"{1}&f\"&a,持续时间为&b{3}&a."
unset-meta-success: "&a在环境{2}中取消设置&b{1}&a的键&f\"{0}&f\"&a的元信息."
unset-meta-temp-success: "&a在环境{2}中取消设置&b{1}&a的键&f\"{0}&f\"&a的临时元信息."
bulk-update-invalid-data-type: "Invalid type. Was expecting 'all', 'users' or 'groups'."
bulk-update-invalid-constraint: "无效的约束&4{0}&c.约束的格式应该是'&f<field> <comparison operator> <value>&c'."
bulk-update-invalid-comparison: "比较运算符无效'&4{0}&c'. 应该以下之一: &f== != ~~ ~!"
bulk-update-queued: "&a批量更新操作已排队.&7(&f{0}&7)"
bulk-update-confirm: "&a运行&b/{0} bulkupdate &a确认{1}执行更新."
bulk-update-unknown-id: "&a使用id&b{0}&a的操作不存在或已过期."
bulk-update-starting: "&a运行批量更新."
bulk-update-success: "&b量更新已成功完成."
bulk-update-failure: "&cBulk update failed. Check the console for errors."
bulk-change-type-error: "类型无效. 应该是 'server' 或 'world'."
bulk-change-success: "&a应用批量更改成功. {0}记录已更改."
chat-meta-entry: "&b-> {0} &f- &f'{1}&f' &8(&7继承自&a{2}&8)"
chat-meta-entry-with-context: "&b-> {0} &f- &f'{1}&f' &8(&7继承自&a{2}&8){3}"
meta-entry: "&b-> &a{0} &f= &f'{1}&f' &8(&7继承自&a{2}&8)"
meta-entry-with-context: "&b-> &a{0} &f= &f'{1}&f' &8(&7继承自&a{2}&8){3}"
chat-meta-prefix-none: "&b{0}没有前缀。"
chat-meta-suffix-none: "&b{0}没有后缀。"
meta-none: "&b{0}没有元信息。"
meta-invalid-priority: "&c优先级&4{0}&c无效。应该是一个数字。"
already-has-chat-meta: "在环境&b{4}&a中设置&b{0}&a的元信息{1}&f'{2}&f'&a优先级为&b{3}&a。"
already-has-temp-chat-meta: "在环境&b{4}&a中设置&b{0}&a的元信息{1}&f'{2}&f'&a临时优先级为&b{3}&a。"
does-not-have-chat-meta: "在环境&b{4}&a中没有设置&b{0}&a的元信息{1}&f'{2}&f'&a优先级为&b{3}&a。"
does-not-have-temp-chat-meta: "在环境&b{4}&a中没有设置&b{0}&a的元信息{1}&f'{2}&f'&a临时优先级为&b{3}&a。"
add-chatmeta-success: "在环境&b{4}&a中将&b{0}&a的{1}设置为&f'{2}&f',优先级&b{3}&a。"
add-temp-chatmeta-success: "在环境&b{5}&a中将&b{0}&a的{1}设置为&f'{2}&f'&a,优先级&b{3}&a,持续时间为&b{4}&a。"
remove-chatmeta-success: "在环境&b{4}&a中删除了&b{0}&a优先级为&b{3}&a的{1} &f'{2}&f'&a。"
bulk-remove-chatmeta-success: "在环境&b{3}&a中删除了&b{0}&a优先级&b{2}&a的所有&b{1}&a。"
remove-temp-chatmeta-success: "在环境&b{4}&a中删除了&b{0}&a优先级&b{3}&a的临时{1} &f'{2}&f'&a。"
bulk-remove-temp-chatmeta-success: "在环境&b{3}&a中删除了&b{0}&a优先级&b{2}&a的所有临时&b{1}&a。"
already-has-meta: "&b{0}&a在环境&b{3}&a中设置了元信息&f'{1}&f'&a为&b&f'{2}&f'&a。"
already-has-temp-meta: "&b{0}&a在环境&b{3}&a中设置了临时元信息&f'{1}&f'&a为&b&f'{2}&f'&a。"
doesnt-have-meta: "&b{0}&a在环境&b{2}&a中没有设置元信息&f'{1}&f'&a。"
doesnt-have-temp-meta: "&b{0}&a在环境&b{2}&a中没有设置临时元信息&f'{1}&f'&a。"
set-meta-success: "&a在环境&b{3}&a中将&b{2}&a的元信息&f'{0}&f'&a设置为&f'{1}&f'&a。"
set-meta-temp-success: "&a在环境&b{4}&a中将&b{2}&a的元信息&f'{0}&f'&a设置为&f'{1}&f'&a,持续时间为&b{3}&a。"
unset-meta-success: "&a在环境{2}中取消设置&b{1}&a的元信息&f'{0}&f'&a。"
unset-meta-temp-success: "&a在环境{2}中取消设置&b{1}&a的临时元信息&f'{0}&f'&a。"
bulk-update-invalid-data-type: "&c类型无效。期望类型是'全部'、'用户'或'组'。"
bulk-update-invalid-constraint: "&c无效的约束&4{0}&c。约束的格式应该是'&f<field> <comparison operator> <value>&c'。"
bulk-update-invalid-comparison: "&c比较运算符无效'&4{0}&c'。应该以下之一: &f== != ~~ ~!"
bulk-update-queued: "&a批量更新操作已排队。&7(&f{0}&7)"
bulk-update-confirm: "&a运行&b/{0} bulkupdate &a确认{1}执行更新。"
bulk-update-unknown-id: "&a使用id&b{0}&a的操作不存在或已过期。"
bulk-update-starting: "&a运行批量更新。"
bulk-update-success: "&b批量更新已成功完成。"
bulk-update-failure: "&c批量更新失败。检查控制台是否有错误。"
user-info-general: >
{PREFIX}&b&l> &bUser Info: &f{0}\n
{PREFIX}&b&l> &b用户信息: &f{0}\n
{PREFIX}&f- &3UUID: &f{1}\n
{PREFIX}&f- &3状态: {2}\n
{PREFIX}&f- &3主组: &f{3}\n
{PREFIX}&f- &a统计:\n
{PREFIX}&f- &3权限: &a{4}\n
{PREFIX}&f- &3临时权限: &a{5}\n
{PREFIX}&f- &3节点: &a{4}\n
{PREFIX}&f- &3权限: &a{5}\n
{PREFIX}&f- &3前缀: &a{6}\n
{PREFIX}&f- &3后缀: &a{7}\n
{PREFIX}&f- &3元信息: &a{8}
user-info-data: >
{PREFIX}&f- &aCached Data:\n
{PREFIX}&f- &a缓存数据:\n
{PREFIX}&f- &3具有环境数据: {0}\n
{PREFIX}&f- &3当前环境: {1}\n
{PREFIX}&f- &3当前前缀: {2}\n
{PREFIX}&f- &3当前后缀: {3}
info-parent-header: "&f- &a父组:"
info-temp-parent-header: "&f- &a临时父组:"
user-getuuid: "&b{0}&b的UUID是&b{1}&b."
user-removegroup-error-primary: "您不能从其主组中删除用户."
user-primarygroup-success: "&b{0}&的主组已设为&b{1}&a."
user-primarygroup-warn-option: "&c警告:此服务器&7({0})&c使用的主组计算方法可能无法反映此更改."
user-primarygroup-error-alreadyhas: "用户已将此组设置为其主组."
user-primarygroup-error-notmember: "&b{0}&a已经不是&b{1}&a的成员,现在添加它们."
user-track-error-not-contain-group: "指定的用户尚未在此轨道上的任何组中."
user-track-added-to-first: "&b{0}&a不在此轨道上的任何组中, 因此在环境&b{2}&a中将其添加到第一组&b{2}&a."
user-promote-success: "&a在环境&b{3}&a中将用户沿轨道&b{0}&a从&b{1}&a提升到&b{2}&a."
user-promote-error-endoftrack: "已达到轨道&4{0}&c的结尾,所以无法提升用户."
user-removegroup-error-primary: "&a你不能从其主组中删除用户。"
user-primarygroup-success: "&b{0}&a的主组已设为&b{1}&a。"
user-primarygroup-warn-option: "&a警告:此服务器&7({0})&a使用的主组计算方法可能无法反映此更改。"
user-primarygroup-error-alreadyhas: "&a已将&b{1}&a设置为用户&b{0}&a的主组。"
user-primarygroup-error-notmember: "&b{0}&a不是&b{1}&a的成员,现在添加它们。"
user-track-error-not-contain-group: "&a用户&b{0}&a尚未在权限组轨道&b{1}&a上的任何组中。"
user-track-added-to-first: "&b{0}&a不在此权限组轨道上的任何组中,因此在环境&b{2}&a中将其添加到第一组&b{2}&a。"
user-promote-success: "&a在环境&b{4}&a中将用户&b{0}&a沿权限组轨道&b{1}&a从&b{2}&a提升到&b{3}&a。"
user-promote-error-endoftrack: "&a已达到权限组轨道&b{0}&a的结尾,所以无法提升用户&b{1}&a。"
user-promote-error-malformed: >
{PREFIX}轨道上的下一个组{0}不存在. 无法提升用户.\n
{PREFIX}创建组,或将其从轨道中删除,然后重试.
user-demote-success: "&a在环境&b{3}&a中将用户沿轨道&b{0}&a从&b{1}&a转移到&b{2}&a."
user-demote-endoftrack: "已达到轨道&4{0}&c的结尾,所以&4{1}&c已从&4{2}&c中移除."
{PREFIX}&a权限组轨道上的下一个组&b{0}&a不存在。无法提升用户。\n
{PREFIX}&a创建组,或将其从权限组轨道中删除,然后重试。
user-demote-success: "&a在环境&b{4}&a中将&b{0}&a沿权限组轨道&b{1}&a从&b{2}&a下降到&b{3}&a。"
user-demote-endoftrack: "&a已达到权限组轨道&b{0}&a的结尾,所以&b{1}&a已从&b{2}&a中移除。"
user-demote-error-malformed: >
{PREFIX}轨道上的上一组{0}不存在.无法降级用户.\n
{PREFIX}创建组,或将其从轨道中删除,然后重试.
user-showpos: >
&a显示&b{0}&a在轨道&b{1}&a的位置.\n
{2}
{PREFIX}&a权限组轨道上的上一组&b{0}&a不存在。无法降级用户。\n
{PREFIX}&a创建组,或将其从权限组轨道中删除,然后重试。
group-info-general: >
{PREFIX}&b&l> &b组信息: &f{0}\n
{PREFIX}&f- &3显示名称: &f{1}\n
{PREFIX}&f- &3权重: &f{2}\n
{PREFIX}&f- &a统计:\n
{PREFIX}&f- &3权限: &a{3}\n
{PREFIX}&f- &3节点: &a{3}\n
{PREFIX}&f- &3临时权限: &a{4}\n
{PREFIX}&f- &3前缀: &a{5}\n
{PREFIX}&f- &3后缀: &a{6}\n
{PREFIX}&f- &3元信息: &a{7}
group-set-weight: "&a将组&b{1}&a的权重设置为&b{0}&a."
group-set-weight: "&a将组&b{1}&a的权重设置为&b{0}&a。"
track-info: >
{PREFIX}&b&l> &b显示轨道: &f{0}\n
{PREFIX}&b&l> &b显示权限组轨道: &f{0}\n
{PREFIX}&f- &7路径: &f{1}
track-clear: "&b{0}&a's groups track was cleared."
track-append-success: "&a组&b{0}&a已成功添加到轨道&b{1}&a."
track-insert-success: "&a组&b{0}&a已成功插入轨道&b{1}&a的&b{2}&a位置."
track-insert-error-number: "没有预期的数量,而是收到: {0}"
track-insert-error-invalid-pos: "无法插入位置{0}. 索引超出范围."
track-remove-success: "&aGroup &b{0}&a已成功从轨道&b{1}&a移除."
log-load-error: "日志无法加载."
log-invalid-page: "页码无效."
log-invalid-page-range: "页码无效,请输入1和{0}之间的值."
log-no-entries: "&b没有日志条目显示."
track-clear: "&b{0}&a的权限组轨道已被清除。"
track-append-success: "&a组&b{0}&a已成功添加到权限组轨道&b{1}&a。"
track-insert-success: "&a组&b{0}&a已成功插入权限组轨道&b{1}&a的&b{2}&a位置。"
track-insert-error-number: "&c没有预期的数量,而是收到: {0}"
track-insert-error-invalid-pos: "&c无法插入位置&4{0}&c。&7(无效位置)"
track-remove-success: "&a组&b{0}&a已成功从权限组轨道&b{1}&a移除。"
log-load-error: "&c日志无法加载。"
log-invalid-page: "&c页码无效。"
log-invalid-page-range: "&c页码无效,请输入&41&c和&4{0}&c之间的值。"
log-no-entries: "&b没有日志条目显示。"
log-entry: "&b#{0} -> &8(&7{1} ago&8) {2}"
log-notify-toggle-on: "&a启用&b记录输出."
log-notify-toggle-off: "&c禁用&b记录输出."
log-notify-already-on: "您已经收到通知."
log-notify-already-off: "您目前没有收到通知."
log-notify-unknown: "状态未知.输入 \"on\" 或者 \"off\"."
log-search-header: "&a显示最近的查询操作&b{0} &a(page &f{1}&a of &f{2}&a)"
log-recent-header: "&a显示最近操作 (page &f{0}&a of &f{1}&a)"
log-recent-by-header: "&a显示&b{0}&a的最近操作 (page &f{1}&a of &f{2}&a)"
log-history-user-header: "&a显示用户的历史记录&b{0} &a(page &f{1}&a of &f{2}&a)"
log-history-group-header: "&a显示组的历史记录 &b{0} &a(page &f{1}&a of &f{2}&a)"
log-history-track-header: "&a显示轨道的历史记录 &b{0} &a(page &f{1}&a of &f{2}&a)"
log-export-already-exists: "错误: 文件{0}已经存在."
log-export-not-writable: "错误: 文件{0}不可写."
log-export-empty: "日志为空,因此无法导出."
log-export-failure: "写入文件时出现意外错误."
log-export-success: "&a成功导出日志&b{0}&a."
import-already-running: "另一个导入过程已经在运行,请等待完成,然后重试."
export-already-running: "另一个导出过程已经在运行,请等待完成,然后重试."
import-log-doesnt-exist: "错误: 文件{0}不存在."
import-log-not-readable: "错误: 文件{0}不可读."
import-log-failure: "从日志文件读取时发生意外错误."
import-progress: "&b(导入) &b-> &f百分之{0}完成 &7- &b{1}&f/&b{2} &f完成 &c{3}&f错误."
import-progress-sin: "&b(导入) &b-> &f百分之{0}完成 &7- &b{1}&f/&b{2} &f完成 &c{3}&f错误."
import-start: "&b(导入) &b-> &f开始导入过程."
import-end-complete: "&b(导入) &a&l已完成 &7- 用了 &b{0} &7秒 - &7没有错误."
import-end-complete-err: "&b(导入) &a&l已完成 &7- 用了 &b{0} &7秒 - &c{1}错误."
import-end-complete-err-sin: "&b(导入) &a&l已完成 &7- 用了 &b{0} &7秒 - &c{1}错误."
log-notify-console: "&c无法切换控制台通知。"
log-notify-toggle-on: "&a启用&b记录输出。"
log-notify-toggle-off: "&c禁用&b记录输出。"
log-notify-already-on: "&c你已经收到通知。"
log-notify-already-off: "&c你目前没有收到通知。"
log-notify-unknown: "&c状态未知。输入 \"on\" 或者 \"off\"。"
log-search-header: "&a显示最近的查询操作&b{0} &a(&f{1}&a/&f{2}&a)"
log-recent-header: "&a显示最近操作 (&f{0}&a/&f{1}&a)"
log-recent-by-header: "&a显示&b{0}&a的最近操作 (&f{1}&a/&f{2}&a)"
log-history-user-header: "&a显示用户的历史记录&b{0} &a(&f{1}&a/&f{2}&a)"
log-history-group-header: "&a显示组的历史记录 &b{0} &a(&f{1}&a/&f{2}&a)"
log-history-track-header: "&a显示权限组轨道的历史记录 &b{0} &a(&f{1}&a/&f{2}&a)"
log-export-already-exists: "&c错误: 文件&4{0}&c已经存在。"
log-export-not-writable: "&c错误: 文件&4{0}&c不可写。"
log-export-empty: "&c日志为空,因此无法导出。"
log-export-failure: "&c写入文件时出现意外错误。"
log-export-success: "&a成功导出日志&b{0}&a。"
import-already-running: "&c另一个导入过程已经在运行,请等待完成,然后重试。"
export-already-running: "&c另一个导出过程已经在运行,请等待完成,然后重试。"
import-log-doesnt-exist: "&c错误: 文件&4{0}&c不存在。"
import-log-not-readable: "&c错误: 文件&4{0}&c不可读。"
import-log-failure: "&c从日志文件读取时发生意外错误。"
import-progress: "&b(导入) &b-> &f{0}&f%完成 &7- &b{1}&f/&b{2} &f完成 &c{3}&f错误。"
import-progress-sin: "&b(导入) &b-> &f{0}&f%完成 &7- &b{1}&f/&b{2} &f完成 &c{3}&f错误。"
import-start: "&b(导入) &b-> &f开始导入过程。"
import-end-complete: "&b(导入) &a&l已完成 &7- 用了 &b{0} &7秒 - &7没有错误。"
import-end-complete-err: "&b(导入) &a&l已完成 &7- 用了 &b{0} &7秒 - &c{1}错误。"
import-end-complete-err-sin: "&b(导入) &a&l已完成 &7- 用了 &b{0} &7秒 - &c{1}错误。"
import-end-error-header: >
{PREFIX}&b(导入) &7------------> &f显示错误 #&b{0} &7<------------\n
{PREFIX}&b(导入) &f执行时: &3Command #{1}\n
{PREFIX}&b(导入) &f执行: &3命令 #{1}\n
{PREFIX}&b(导入) &f命令: &7{2}\n
{PREFIX}&b(导入) &f类型: &3{3}\n
{PREFIX}&b(导入) &f输出:
import-end-error-content: "&b(导入) &b-> &c{0}"
import-end-error-footer: "&b(导入) &7<------------------------------------------>"
import-end-error-footer: "&b(导入) &7<------------------------------------------>"
command-specs:
user:
description: "用户命令"
usage: "/%s user <用户>"
group:
description: "组命令"
usage: "/%s group <组>"
track:
description: "权限组轨道指令"
usage: "/%s track <权限组轨道>"
log:
description: "日志命令"
usage: "/%s log"
sync:
description: "与存储同步更改"
usage: "/%s sync"
info:
description: "打印通用插件信息"
usage: "/%s info"
verbose:
description: "管理详细权限检查"
usage: "/%s verbose <true|false> [filter]"
args:
"on|record|off|paste": "是否启用/禁用日志记录,或粘贴记录的输出"
"filter": "过滤器匹配项"
"--slim": "添加\"--slim\"从粘贴的输出中排除跟踪数据"
tree:
description: "生成权限的树视图"
usage: "/%s tree [selection] [max level] [player]"
args:
"selection": "树的根,指定\".\"包括所有权限"
"max level": "应该返回多少分支级别"
"player": "要检查的在线玩家的名称"
search:
description: "搜索具有特定权限的用户/组"
usage: "/%s search <permission>"
args:
"permission": "权限搜索"
"page": "页面查看"
check:
description: "对用户执行标准的权限检查"
usage: "/%s check <user> <permission>"
args:
"user": "检查用户"
"permission": "允许检查"
network-sync:
description: "与存储同步更改并请求网络上所有其他服务器都同步"
usage: "/%s networksync"
import:
description: "从文件导入数据"
usage: "/%s import <file>"
args:
"file": "要导入的文件"
export:
description: "将数据导出到文件"
usage: "/%s export <file>"
args:
"file": "要导出的文件"
reload-config:
description: "重新加载部分配置选项"
usage: "/%s reloadconfig"
bulk-update:
description: "对所有数据执行批量更改查询"
usage: "/%s bulkupdate"
args:
"data type": "正在更改的数据类型。('all'、'users'、'groups')"
"action": "对数据执行的操作。('update' or 'delete')"
"action field": "要采取行动的领域。只需要'update'。('permission'、'server'、'world')"
"action value": "替换的值。只需要'update'。"
"constraint...": "更新所需的限制"
migration:
description: "迁移命令"
usage: "/%s migration"
apply-edits:
description: "应用从Web编辑器进行的权限更改"
usage: "/%s applyedits <code> [target]"
args:
"code": "数据的唯一代码"
"target": "应用数据到谁"
create-group:
description: "创建新组"
usage: "/%s creategroup <group>"
args:
"name": "组的名称"
delete-group:
description: "删除组"
usage: "/%s deletegroup <group>"
args:
"name": "组的名称"
list-groups:
description: "列出平台上的所有组"
usage: "/%s listgroups"
create-track:
description: "创建新权限组轨道"
usage: "/%s createtrack <track>"
args:
"name": "权限组轨道的名称"
delete-track:
description: "删除权限组轨道"
usage: "/%s deletetrack <track>"
args:
"name": "权限组轨道的名称"
list-tracks:
description: "列出平台上的所有权限组轨道"
usage: "/%s listtracks"
user-info:
description: "显示有关用户的信息"
user-switchprimarygroup:
description: "切换用户的主组"
args:
"group": "组切换到"
user-promote:
description: "在权限组轨道提升用户"
args:
"track": "提升用户的权限组轨道"
"context...": "提升用户使用的环境"
user-demote:
description: "在权限组轨道降低用户"
args:
"track": "降低用户的权限组轨道"
"context...": "降低用户使用的环境"
group-info:
description: "提供有关组的信息"
group-listmembers:
description: "显示从该组继承的用户/组"
args:
"page": "页面查看"
group-setweight:
description: "设置组权重"
args:
"weight": "设定权重"
group-rename:
description: "重命名组"
args:
"name": "新名字"
group-clone:
description: "克隆组"
args:
"name": "要克隆到的组的名称"
holder-editor:
description: "打开网页权限编辑器"
holder-showtracks:
description: "列出对象所在的权限组轨道"
holder-clear:
description: "删除所有权限,父组和元"
args:
"context...": "过滤的环境"
permission:
description: "编辑权限"
parent:
description: "编辑继承"
meta:
description: "编辑元数据值"
permission-info:
description: "列出对象拥有的权限节点"
args:
"page": "页面查看"
"filter": "要过滤的字符串"
permission-set:
description: "设置对象的权限"
args:
"node": "要添加的权限"
"true|false": "节点的值"
"context...": "添加权限的环境"
permission-unset:
description: "取消对象的权限"
args:
"node": "取消的权限节点"
"context...": "删除权限的环境"
permission-settemp:
description: "设置对象的临时权限"
args:
"node": "要添加的权限"
"true|false": "节点的值"
"duration": "到期时间"
"context...": "权限的环境"
permission-unsettemp:
description: "取消对对象的临时权限"
args:
"node": "要取消的权限"
"context...": "权限的环境"
permission-check:
description: "检查对象是否具有某个权限节点"
args:
"node": "要检查的权限"
"context...": "权限的环境"
permission-check-inherits:
description: "检查对象是否继承某个权限节点"
args:
"node": "要检查的权限"
"context...": "权限的环境"
parent-info:
description: "列出此对象从哪里继承的组"
parent-set:
description: "删除对象已经继承的所有其他组,并将其添加到给定的对象组中。"
args:
"group": "组设为"
"context...": "设置组的环境"
parent-add:
description: "为该对象设置另一个组来继承权限"
args:
"group": "要继承的组"
"context...": "继承该组的环境"
parent-remove:
description: "删除先前设置的继承规则"
args:
"group": "删除的组"
"context...": "删除组的环境"
parent-set-track:
description: "删除对象在给定权限组轨道上继承的所有其他组,并将其添加到给定的权限组轨道上。"
args:
"track": "设置的权限组轨道"
"group": "组中设置的组,或与组中在给定权限组轨道上的位置相关的数字"
"context...": "设置组的环境"
parent-add-temp:
description: "为对象设置另一个组,以便从临时继承权限"
args:
"group": "要继承的组"
"duration": "持续时间"
"context...": "继承该组的环境"
parent-remove-temp:
description: "删除以前设置的临时继承规则"
args:
"group": "组删除"
"context...": "删除组的环境"
parent-clear:
description: "清除所有的父组"
args:
"context...": "过滤的环境"
parent-clear-track:
description: "清除给定权限组轨道上的所有父组"
args:
"track": "删除的权限组轨道"
"contexts...": "选择的环境"
meta-info:
description: "显示所有聊天元"
meta-set:
description: "设置元值"
args:
"key": "设定的键"
"value": "要设置的值"
"context...": "将元添加到"
meta-unset:
description: "取消设定元值"
args:
"key": "要取消的键"
"context...": "删除元的环境"
meta-settemp:
description: "暂时设置一个元值"
args:
"key": "设定的键"
"value": "要设置的值"
"duration": "持续时间"
"context...": "将元组添加到"
meta-unsettemp:
description: "取消设置临时元值"
args:
"key": "要取消的键"
"context...": "删除元的环境"
meta-addprefix:
description: "添加前缀"
args:
"priority": "添加的前缀优先级"
"prefix": "前缀字符串"
"context...": "添加前缀的环境"
meta-addsuffix:
description: "添加后缀"
args:
"priority": "添加的后缀优先级"
"suffix": "后缀字符串"
"context...": "添加后缀的环境"
meta-removeprefix:
description: "删除前缀"
args:
"priority": "删除的前缀优先级"
"prefix": "前缀字符串"
"context...": "删除前缀的环境"
meta-removesuffix:
description: "删除后缀"
args:
"priority": "删除的后缀优先级"
"suffix": "后缀字符串"
"context...": "删除后缀的环境"
meta-addtemp-prefix:
description: "添加临时前缀"
args:
"priority": "删除的前缀优先级"
"prefix": "前缀字符串"
"duration": "持续时间"
"context...": "添加前缀的环境"
meta-addtemp-suffix:
description: "添加临时后缀"
args:
"priority": "添加的后缀优先级"
"suffix": "后缀字符串"
"duration": "持续时间"
"context...": "添加后缀的环境"
meta-removetemp-prefix:
description: "删除临时前缀"
args:
"priority": "删除的前缀优先级"
"prefix": "前缀字符串"
"context...": "删除前缀的环境"
meta-removetemp-suffix:
description: "删除临时后缀"
args:
"priority": "删除的后缀优先级"
"suffix": "后缀字符串"
"context...": "删除后缀的环境"
meta-clear:
description: "清除所有元"
args:
"type": "要删除的元数据类型"
"context...": "过滤的环境"
track-info:
description: "提供有关权限组轨道的信息"
track-append:
description: "追加一个组到权限组轨道的最后"
args:
"group": "追加的组"
track-insert:
description: "在权限组轨道上的给定位置插入一个组"
args:
"group": "插入的组"
"position": "插入组的位置(权限组轨道上的第一个位置为1)"
track-remove:
description: "从权限组轨道中删除一个组"
args:
"group": "删除的组"
track-clear:
description: "清除权限组轨道上的组"
track-rename:
description: "重命名权限组轨道"
args:
"name": "新名字"
track-clone:
description: "克隆权限组轨道"
args:
"name": "要克隆的权限组轨道的名称"
log-recent:
description: "查看最近的登陆"
args:
"user": "用户的名称/uuid"
"page": "要查看的页码"
log-search:
description: "搜索日志中的条目"
args:
"query": "搜索的查询"
"page": "要查看的页码"
log-notify:
description: "切换日志通知"
args:
"on|off": "是否打开或关闭"
log-user-history:
description: "查看用户的历史记录"
args:
"user": "用户名/ uuid"
"page": "要查看的页码"
log-group-history:
description: "查看组的历史"
args:
"group": "组名"
"page": "要查看的页码"
log-track-history:
description: "查看权限组轨道的历史"
args:
"track": "权限组轨道的名称"
"page": "要查看的页码"
sponge:
description: "编辑额外的Sponge数据"
usage: "/%s sponge <collection> <subject>"
args:
"collection": "要查询的集合"
"subject": "修改的主题"
sponge-permission-info:
description: "显示有关主题权限的信息"
args:
"contexts...": "选择的环境"
sponge-permission-set:
description: "设置一个主题的权限"
args:
"node": "要设置的权限节点"
"tristate": "设置允许值"
"contexts...": "选择的环境"
sponge-permission-clear:
description: "清除一个主题的权限"
args:
"contexts...": "清除的环境"
sponge-parent-info:
description: "显示关于该主题父项的信息"
args:
"contexts...": "选择的环境"
sponge-parent-add:
description: "向主题添加父项"
args:
"collection": "父主题的主题集合"
"subject": "父主题的名称"
"contexts...": "添加父项的环境"
sponge-parent-remove:
description: "从主题中删除父项"
args:
"collection": "父主题的主题集合"
"subject": "父主题的名称"
"contexts...": "删除父项的环境"
sponge-parent-clear:
description: "清除主题父项"
args:
"contexts...": "清除父项的主题"
sponge-option-info:
description: "显示有关主题选项的信息"
args:
"contexts...": "选择的环境"
sponge-option-set:
description: "为主题设置一个选项"
args:
"key": "设定键"
"value": "设置的键"
"contexts...": "设置选项的环境"
sponge-option-unset:
description: "取消主题一个选项"
args:
"key": "取消的键"
"contexts...": "取消选项的环境"
sponge-option-clear:
description: "清除对象的选择"
args:
"contexts...": "清除选项的环境"
migration-command:
description: "迁移命令"
migration-groupmanager:
description: "迁移命令"
args:
"migrate as global": "如果世界权限应该被忽略,并且刚刚迁移为全局"
migration-powerfulperms:
description: "迁移命令"
args:
"address": "PP数据库的地址"
"database": "PP数据库的名称"
"username": "登录DB的用户名"
"password": "登录DB的用密码"
"db table": "存储用户数据的PP表的名称"
+5 -5
View File
@@ -49,15 +49,15 @@ The project is split up into 5 seperate modules.
## License
LuckPerms is licensed under the permissive MIT license. Please see [`LICENSE.txt`](https://github.com/lucko/LuckPerms/blob/master/LICENSE.txt) for more info.
## LuckPerms vs Other Plugins
## Why use LuckPerms?
The main features of LuckPerms are detailed on this wiki page: [Why LuckPerms?](https://github.com/lucko/LuckPerms/wiki/Why-LuckPerms)
### Bukkit
![alt text](https://luckperms.lucko.me/assets/bukkit-compare.png "Feature comparison")
See [here](https://luckperms.lucko.me/assets/bukkit-compare.png) for a comparison chart showing the differences between LuckPerms and other permission plugins for Bukkit.
### Sponge
![alt text](https://luckperms.lucko.me/assets/sponge-compare.png "Feature comparison")
LuckPerms is currently the only maintained and 'working' permissions plugin for Sponge servers.
## Thanks
[![JProfiler](https://www.ej-technologies.com/images/product_banners/jprofiler_large.png)](http://www.ej-technologies.com/products/jprofiler/overview.html)
Thanks to ej-technologies for granting LuckPerms an open source licence to their [Java Profiling Software](http://www.ej-technologies.com/products/jprofiler/overview.html "Java Profiler").
+1 -1
View File
@@ -5,7 +5,7 @@
<parent>
<artifactId>luckperms</artifactId>
<groupId>me.lucko.luckperms</groupId>
<version>3.3-SNAPSHOT</version>
<version>4.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -32,49 +32,59 @@ import java.util.Optional;
import javax.annotation.Nonnull;
/**
* Singleton for the {@link LuckPermsApi}.
* Provides static access to the {@link LuckPermsApi}.
*
* <p>Ideally, the ServiceManager for the platform should be used to obtain and cache an instance, however, this can be
* used if you need static access.</p>
* <p>Ideally, the ServiceManager for the platform should be used to obtain an instance,
* however, this provider can be used if you need static access.</p>
*/
public final class LuckPerms {
private static LuckPermsApi api = null;
private static LuckPermsApi instance = null;
/**
* Gets an instance of {@link LuckPermsApi}, throwing {@link IllegalStateException} if the API is not loaded.
* Gets an instance of the {@link LuckPermsApi},
* throwing {@link IllegalStateException} if an instance is not yet loaded.
*
* <p>Will never return null.</p>
*
* @return an api instance
* @throws IllegalStateException if the api is not loaded
*/
@Nonnull
public static LuckPermsApi getApi() {
if (api == null) {
if (instance == null) {
throw new IllegalStateException("API is not loaded.");
}
return api;
return instance;
}
/**
* Gets an instance of {@link LuckPermsApi}, if it is loaded.
*
* <p> Unlike {@link LuckPerms#getApi}, this method will not throw an {@link IllegalStateException} if the API is
* not loaded, rather return an empty {@link Optional}.
* <p>Unlike {@link LuckPerms#getApi}, this method will not throw an
* {@link IllegalStateException} if an instance is not yet loaded, rather return
* an empty {@link Optional}.
*
* @return an optional api instance
*/
@Nonnull
public static Optional<LuckPermsApi> getApiSafe() {
return Optional.ofNullable(api);
return Optional.ofNullable(instance);
}
/* method used by the implementation to set the singleton instance */
static void registerProvider(LuckPermsApi luckPermsApi) {
api = luckPermsApi;
/**
* Registers an instance of the {@link LuckPermsApi} with this provider.
*
* @param instance the instance
*/
static void registerProvider(LuckPermsApi instance) {
LuckPerms.instance = instance;
}
/* method used by the implementation to remove any previous instance */
/**
* Removes the current instance from this provider.
*/
static void unregisterProvider() {
api = null;
LuckPerms.instance = null;
}
private LuckPerms() {
@@ -28,6 +28,7 @@ package me.lucko.luckperms.api;
import com.google.common.base.Preconditions;
import java.util.Map;
import java.util.Optional;
import javax.annotation.Nonnull;
@@ -104,4 +105,22 @@ public enum ChatMetaType {
@Nonnull
public abstract Map.Entry<Integer, String> getEntry(@Nonnull Node node);
/**
* Parses a ChatMetaType from the given node.
*
* @param node the node
* @return the parsed chat meta type
* @since 3.4
*/
@Nonnull
public static Optional<ChatMetaType> ofNode(@Nonnull Node node) {
if (node.isPrefix()) {
return Optional.of(PREFIX);
} else if (node.isSuffix()) {
return Optional.of(SUFFIX);
} else {
return Optional.empty();
}
}
}
@@ -25,22 +25,46 @@
package me.lucko.luckperms.api;
import com.google.common.base.Preconditions;
import me.lucko.luckperms.api.context.ContextSet;
import me.lucko.luckperms.api.context.ImmutableContextSet;
import javax.annotation.Nonnull;
/**
* Context and options for a permission lookup.
* Encapsulates the options and settings for a permission lookup.
*
* <p>All values are immutable.</p>
* <p>This class is immutable.</p>
*
* @since 2.11
*/
public class Contexts {
/**
* The context key used to denote the subjects server
*/
public static final String SERVER_KEY = "server";
/**
* The context key used to denote the subjects world
*/
public static final String WORLD_KEY = "world";
private static final Contexts GLOBAL = new Contexts(ContextSet.empty(), true, true, true, true, true, false);
/**
* A 'global' or default contexts instance.
*
* Simply passes an empty context set, with all accumulation settings set to true.
*/
private static final Contexts GLOBAL = new Contexts(
ContextSet.empty(),
true,
true,
true,
true,
true,
false
);
/**
* Gets the {@link FullySatisfiedContexts} instance.
@@ -69,12 +93,12 @@ public class Contexts {
/**
* The contexts that apply for this lookup
* The keys for servers and worlds are defined as static values.
*/
private final ContextSet context;
private final ImmutableContextSet context;
/**
* The mode to parse defaults on Bukkit
* If the target subject is OP. This is used to parse defaults on Bukkit,
* and is ignored on all other platforms.
*
* @since 2.12
*/
@@ -105,18 +129,18 @@ public class Contexts {
*/
private final boolean applyGlobalWorldGroups;
public Contexts(@Nonnull ContextSet context, boolean includeGlobal, boolean includeGlobalWorld, boolean applyGroups, boolean applyGlobalGroups, boolean applyGlobalWorldGroups, boolean op) {
if (context == null) {
throw new NullPointerException("context");
}
// cache hashcode - this class is immutable, and is used as an index in the permission cache.
private final int hashCode;
this.context = context.makeImmutable();
public Contexts(@Nonnull ContextSet context, boolean includeGlobal, boolean includeGlobalWorld, boolean applyGroups, boolean applyGlobalGroups, boolean applyGlobalWorldGroups, boolean op) {
this.context = Preconditions.checkNotNull(context, "context").makeImmutable();
this.includeGlobal = includeGlobal;
this.includeGlobalWorld = includeGlobalWorld;
this.applyGroups = applyGroups;
this.applyGlobalGroups = applyGlobalGroups;
this.applyGlobalWorldGroups = applyGlobalWorldGroups;
this.op = op;
this.hashCode = calculateHashCode();
}
/**
@@ -131,7 +155,8 @@ public class Contexts {
}
/**
* Gets if OP defaults should be included
* Gets if the target subject is OP. This is used to parse defaults on Bukkit,
* and is ignored on all other platforms.
*
* @return true if op defaults should be included
*/
@@ -198,10 +223,6 @@ public class Contexts {
")";
}
/*
* Ugly auto-generated lombok code
*/
@Override
public boolean equals(Object o) {
if (o == this) return true;
@@ -218,8 +239,7 @@ public class Contexts {
this.isApplyGlobalWorldGroups() == other.isApplyGlobalWorldGroups();
}
@Override
public int hashCode() {
private int calculateHashCode() {
final int PRIME = 59;
int result = 1;
final Object contexts = this.getContexts();
@@ -233,4 +253,8 @@ public class Contexts {
return result;
}
@Override
public int hashCode() {
return hashCode;
}
}
@@ -25,44 +25,64 @@
package me.lucko.luckperms.api;
import me.lucko.luckperms.exceptions.ObjectAlreadyHasException;
import me.lucko.luckperms.exceptions.ObjectLacksException;
import java.util.function.Supplier;
/**
* Represents the result of a mutation call.
*/
public enum DataMutateResult {
SUCCESS(true, null),
ALREADY_HAS(false, ObjectAlreadyHasException::new),
LACKS(false, ObjectLacksException::new),
FAIL(false, RuntimeException::new);
/**
* Indicates the mutation was a success
*/
SUCCESS(true),
private boolean value;
private final Supplier<? extends Exception> exceptionSupplier;
/**
* Indicates the mutation failed because the subject already has something
*/
ALREADY_HAS(false),
DataMutateResult(boolean value, Supplier<? extends Exception> exceptionSupplier) {
/**
* Indicates the mutation failed because the subject lacks something
*/
LACKS(false),
/**
* Indicates the mutation failed
*/
FAIL(false);
private final boolean value;
DataMutateResult(boolean value) {
this.value = value;
this.exceptionSupplier = exceptionSupplier;
}
public void throwException() {
if (exceptionSupplier != null) {
sneakyThrow(exceptionSupplier.get());
}
}
/**
* Gets a boolean representation of the result.
*
* @return a boolean representation
*/
public boolean asBoolean() {
return value;
}
// allows us to throw checked exceptions without declaring it, as #throwException throws a number of
// exception types.
private static void sneakyThrow(Throwable t) {
sneakyThrow0(t);
/**
* Gets if the result indicates a success
*
* @return if the result indicates a success
* @since 3.4
*/
public boolean wasSuccess() {
return value;
}
private static <T extends Throwable> void sneakyThrow0(Throwable t) throws T {
throw (T) t;
/**
* Gets if the result indicates a failure
*
* @return if the result indicates a failure
* @since 3.4
*/
public boolean wasFailure() {
return !value;
}
}
@@ -1,3 +1,28 @@
/*
* 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.api;
import me.lucko.luckperms.api.caching.MetaContexts;
@@ -9,19 +34,22 @@ import javax.annotation.Nonnull;
/**
* A special instance of {@link Contexts}, which when passed to:
*
* <p><ul>
* <li>{@link UserData#getPermissionData(Contexts)}</li>
* <li>{@link UserData#getMetaData(Contexts)}</li>
* <li>{@link UserData#getMetaData(MetaContexts)}</li>
* </ul></p>
* <p></p>
* <ul>
* <li>{@link UserData#getPermissionData(Contexts)}</li>
* <li>{@link UserData#getMetaData(Contexts)}</li>
* <li>{@link UserData#getMetaData(MetaContexts)}</li>
* </ul>
*
* <p>will always satisfy all contextual requirements.</p>
* <p>... will always satisfy all contextual requirements.</p>
*
* <p>This effectively allows you to do lookups which ignore context.</p>
*
* @since 3.3
*/
public final class FullySatisfiedContexts extends Contexts {
// singleton
private static final FullySatisfiedContexts INSTANCE = new FullySatisfiedContexts();
@Nonnull
@@ -36,16 +64,18 @@ public final class FullySatisfiedContexts extends Contexts {
@Nonnull
@Override
public String toString() {
return "FullySatisfiedContexts";
return "FullySatisfiedContexts()";
}
@Override
public boolean equals(Object o) {
// this class is a singleton, so we can use object comparison to check equality.
return o == this;
}
@Override
public int hashCode() {
// just use the system hashcode - we need to override the hashcode impl in super
return System.identityHashCode(this);
}
}
@@ -26,16 +26,13 @@
package me.lucko.luckperms.api;
import me.lucko.luckperms.api.context.ContextSet;
import me.lucko.luckperms.exceptions.ObjectAlreadyHasException;
import me.lucko.luckperms.exceptions.ObjectLacksException;
import java.util.List;
import java.util.OptionalInt;
import javax.annotation.Nonnull;
/**
* A group which holds permission data.
* An inheritable holder of permission data.
*/
public interface Group extends PermissionHolder {
@@ -78,242 +75,4 @@ public interface Group extends PermissionHolder {
@Nonnull
OptionalInt getWeight();
/**
* Check to see if the group inherits a group on a specific server
*
* @param group The group to check membership of
* @param server The server to check on
* @return true if the group inherits the group on the server
* @throws NullPointerException if the group or server is null
* @throws IllegalStateException if the group instance was not obtained from LuckPerms.
* @throws IllegalArgumentException if the server is invalid
* @deprecated in favour of {@link #inheritsGroup(Group, ContextSet)}
*/
@Deprecated
boolean inheritsGroup(@Nonnull Group group, @Nonnull String server);
/**
* Check to see if the group inherits a group on a specific server and world
*
* @param group The group to check membership of
* @param server The server to check on
* @param world The world to check on
* @return true if the group inherits the group on the server and world
* @throws NullPointerException if the group, server or world is null
* @throws IllegalStateException if the group instance was not obtained from LuckPerms.
* @throws IllegalArgumentException if the server or world is invalid
* @deprecated in favour of {@link #inheritsGroup(Group, ContextSet)}
*/
@Deprecated
boolean inheritsGroup(@Nonnull Group group, @Nonnull String server, @Nonnull String world);
/**
* Make this group inherit another group
*
* @param group the group to be inherited
* @throws ObjectAlreadyHasException if the group already inherits the group
* @throws NullPointerException if the group is null
* @throws IllegalStateException if the group instance was not obtained from LuckPerms.
* @deprecated in favour of {@link NodeFactory#makeGroupNode(Group)}
*/
@Deprecated
void setInheritGroup(@Nonnull Group group) throws ObjectAlreadyHasException;
/**
* Make this group inherit another group on a specific server
*
* @param group the group to be inherited
* @param server The server to inherit the group on
* @throws ObjectAlreadyHasException if the group already inherits the group on that server
* @throws NullPointerException if the group or server is null
* @throws IllegalStateException if the group instance was not obtained from LuckPerms.
* @throws IllegalArgumentException if the server is invalid
* @deprecated in favour of {@link NodeFactory#makeGroupNode(Group)}
*/
@Deprecated
void setInheritGroup(@Nonnull Group group, @Nonnull String server) throws ObjectAlreadyHasException;
/**
* Make this group inherit another group on a specific server and world
*
* @param group the group to be inherited
* @param server The server to inherit the group on
* @param world The world to inherit the group on
* @throws ObjectAlreadyHasException if the group already inherits the group on that server and world
* @throws NullPointerException if the group, server or world is null
* @throws IllegalStateException if the group instance was not obtained from LuckPerms.
* @throws IllegalArgumentException if the server or world is invalid
* @deprecated in favour of {@link NodeFactory#makeGroupNode(Group)}
*/
@Deprecated
void setInheritGroup(@Nonnull Group group, @Nonnull String server, @Nonnull String world) throws ObjectAlreadyHasException;
/**
* Make this group inherit another group temporarily
*
* @param group the group to be inherited
* @param expireAt the unix time when the group should expire
* @throws ObjectAlreadyHasException if the group already inherits the group temporarily
* @throws NullPointerException if the group is null
* @throws IllegalStateException if the group instance was not obtained from LuckPerms.
* @throws IllegalArgumentException if the expiry time is in the past
* @deprecated in favour of {@link NodeFactory#makeGroupNode(Group)}
*/
@Deprecated
void setInheritGroup(@Nonnull Group group, long expireAt) throws ObjectAlreadyHasException;
/**
* Make this group inherit another group on a specific server temporarily
*
* @param group the group to be inherited
* @param server The server inherit add the group on
* @param expireAt when the group should expire
* @throws ObjectAlreadyHasException if the group already inherits the group on that server temporarily
* @throws NullPointerException if the group or server is null
* @throws IllegalStateException if the group instance was not obtained from LuckPerms.
* @throws IllegalArgumentException if the expiry time is in the past or the server is invalid
* @deprecated in favour of {@link NodeFactory#makeGroupNode(Group)}
*/
@Deprecated
void setInheritGroup(@Nonnull Group group, @Nonnull String server, long expireAt) throws ObjectAlreadyHasException;
/**
* Make this group inherit another group on a specific server and world temporarily
*
* @param group the group to be inherited
* @param server The server to inherit the group on
* @param world The world to inherit the group on
* @param expireAt when the group should expire
* @throws ObjectAlreadyHasException if the group already inherits the group on that server and world temporarily
* @throws NullPointerException if the group, server or world is null
* @throws IllegalStateException if the group instance was not obtained from LuckPerms.
* @throws IllegalArgumentException if the expiry time is in the past or the server/world is invalid
* @deprecated in favour of {@link NodeFactory#makeGroupNode(Group)}
*/
@Deprecated
void setInheritGroup(@Nonnull Group group, @Nonnull String server, @Nonnull String world, long expireAt) throws ObjectAlreadyHasException;
/**
* Remove a previously set inheritance rule
*
* @param group the group to uninherit
* @throws ObjectLacksException if the group does not already inherit the group
* @throws NullPointerException if the group is null
* @throws IllegalStateException if the group instance was not obtained from LuckPerms.
* @deprecated in favour of {@link NodeFactory#makeGroupNode(Group)}
*/
@Deprecated
void unsetInheritGroup(@Nonnull Group group) throws ObjectLacksException;
/**
* Remove a previously set inheritance rule
*
* @param group the group to uninherit
* @param temporary if the group being removed is temporary
* @throws ObjectLacksException if the group does not already inherit the group
* @throws NullPointerException if the group is null
* @throws IllegalStateException if the group instance was not obtained from LuckPerms.
* @deprecated in favour of {@link NodeFactory#makeGroupNode(Group)}
*/
@Deprecated
void unsetInheritGroup(@Nonnull Group group, boolean temporary) throws ObjectLacksException;
/**
* Remove a previously set inheritance rule on a specific server
*
* @param group the group to uninherit
* @param server The server to remove the group on
* @throws ObjectLacksException if the group does not already inherit the group on that server
* @throws NullPointerException if the group or server is null
* @throws IllegalStateException if the group instance was not obtained from LuckPerms.
* @throws IllegalArgumentException if the server is invalid
* @deprecated in favour of {@link NodeFactory#makeGroupNode(Group)}
*/
@Deprecated
void unsetInheritGroup(@Nonnull Group group, @Nonnull String server) throws ObjectLacksException;
/**
* Remove a previously set inheritance rule on a specific server and world
*
* @param group the group to uninherit
* @param server The server to remove the group on
* @param world The world to remove the group on
* @throws ObjectLacksException if the group does not already inherit the group
* @throws NullPointerException if the group, server or world is null
* @throws IllegalStateException if the group instance was not obtained from LuckPerms.
* @throws IllegalArgumentException if the server or world is invalid
* @deprecated in favour of {@link NodeFactory#makeGroupNode(Group)}
*/
@Deprecated
void unsetInheritGroup(@Nonnull Group group, @Nonnull String server, @Nonnull String world) throws ObjectLacksException;
/**
* Remove a previously set inheritance rule on a specific server
*
* @param group the group to uninherit
* @param server The server to remove the group on
* @param temporary if the group being removed is temporary
* @throws ObjectLacksException if the group does not already inherit the group
* @throws NullPointerException if the group or server is null
* @throws IllegalStateException if the group instance was not obtained from LuckPerms.
* @throws IllegalArgumentException if the expiry time is in the past or the server is invalid
* @deprecated in favour of {@link NodeFactory#makeGroupNode(Group)}
*/
@Deprecated
void unsetInheritGroup(@Nonnull Group group, @Nonnull String server, boolean temporary) throws ObjectLacksException;
/**
* Remove a previously set inheritance rule on a specific server and world
*
* @param group the group to uninherit
* @param server The server to remove the group on
* @param world The world to remove the group on
* @param temporary if the group being removed was set temporarily
* @throws ObjectLacksException if the group does not already inherit the group
* @throws NullPointerException if the group, server or world is null
* @throws IllegalStateException if the group instance was not obtained from LuckPerms.
* @throws IllegalArgumentException if the expiry time is in the past or the server/world is invalid
* @deprecated in favour of {@link NodeFactory#makeGroupNode(Group)}
*/
@Deprecated
void unsetInheritGroup(@Nonnull Group group, @Nonnull String server, @Nonnull String world, boolean temporary) throws ObjectLacksException;
/**
* Get a {@link List} of all of the groups the group inherits, on all servers
*
* @return a {@link List} of group names
* @deprecated in favour of just querying a users permissions
*/
@Deprecated
@Nonnull
List<String> getGroupNames();
/**
* Get a {@link List} of the groups the group inherits on a specific server
*
* @param server the server to check
* @return a {@link List} of group names
* @throws NullPointerException if the server is null
* @throws IllegalArgumentException if the server is invalid
* @deprecated in favour of just querying a users permissions
*/
@Deprecated
@Nonnull
List<String> getLocalGroups(@Nonnull String server);
/**
* Get a {@link List} of the groups the group inherits on a specific server and world
*
* @param server the server to check
* @param world the world to check
* @return a {@link List} of group names
* @throws NullPointerException if the server or world is null
* @throws IllegalArgumentException if the server or world is invalid
* @deprecated in favour of just querying a users permissions
*/
@Deprecated
@Nonnull
List<String> getLocalGroups(@Nonnull String server, @Nonnull String world);
}
@@ -25,7 +25,7 @@
package me.lucko.luckperms.api;
import com.google.common.collect.Multimap;
import me.lucko.luckperms.api.context.ContextSet;
import java.util.Optional;
import java.util.OptionalLong;
@@ -33,7 +33,7 @@ import java.util.OptionalLong;
import javax.annotation.Nonnull;
/**
* A relationship between a Holder and a permission
* A relationship between a PermissionHolder and a permission
*
* @param <T> the identifier type of the holder
* @since 2.17
@@ -87,12 +87,11 @@ public interface HeldPermission<T> {
OptionalLong getExpiry();
/**
* Gets the context for the permission.
* Gets the extra context for the permission.
*
* @return the context
* @return the extra context
*/
@Nonnull
Multimap<String, String> getContext();
ContextSet getContexts();
/**
* Converts this permission into a Node
@@ -25,147 +25,73 @@
package me.lucko.luckperms.api;
import me.lucko.luckperms.api.data.DatastoreConfiguration;
import java.util.Map;
import javax.annotation.Nonnull;
/**
* Read-only access to the LuckPerms configuration settings
* Wrapper around parts of the LuckPerms configuration file
*/
public interface LPConfiguration {
/**
* Returns the name of this server
* Gets the name of this server
*
* @return the name of this server
*/
@Nonnull
String getServer();
/**
* Returns how often a sync task will run in minutes
* @return how often a sync task will run in minutes
*/
int getSyncTime();
/**
* Returns if the users on this server will have their global permissions applied
* Gets if the users on this server will have their global permissions applied
*
* @return if the users on this server will have their global permissions applied
*/
boolean getIncludeGlobalPerms();
/**
* Returns if the users on this server will have their global world permissions applied
* Gets if the users on this server will have their global world permissions applied
*
* @return if the users on this server will have their global world permissions applied
* @since 2.9
*/
boolean getIncludeGlobalWorldPerms();
/**
* Returns true if the platform is applying global groups
* Gets if the platform is applying global groups
*
* @return true if the platform is applying global groups
* @since 2.9
*/
boolean getApplyGlobalGroups();
/**
* Returns true if the platform is applying global world groups
* Gets if the platform is applying global world groups
*
* @return true if the platform is applying global world groups
* @since 2.9
*/
boolean getApplyGlobalWorldGroups();
/**
* Returns the online mode setting
* @return the online mode setting
*/
boolean getOnlineMode();
/**
* Returns if LuckPerms is applying wildcard permissions
* @return if LuckPerms is applying wildcard permissions
*/
boolean getApplyWildcards();
/**
* Returns if LuckPerms is resolving and applying regex permissions
* @return if LuckPerms is resolving and applying regex permissions
*/
boolean getApplyRegex();
/**
* Returns if LuckPerms is expanding shorthand permissions
* @return if LuckPerms is expanding shorthand permissions
*/
boolean getApplyShorthand();
/**
* Returns if LuckPerms will send notifications to users when permissions are modified
* @return if LuckPerms will send notifications to users when permissions are modified
* @since 2.7
*/
boolean getLogNotify();
/**
* Returns true if the vanilla op system is enabled
* @return true if the vanilla op system is enabled
* @since 2.8
*/
boolean getEnableOps();
/**
* Returns true if opped players are allowed to use LuckPerms commands
* @return true if opped players are allowed to use LuckPerms commands
* @since 2.8
*/
boolean getCommandsAllowOp();
/**
* Returns true if auto op is enabled
* @return true if auto op is enabled
* @since 2.9
*/
boolean getAutoOp();
/**
* Returns the name of the server used within Vault operations
* @return the name of the server used within Vault operations
* @since 2.7
*/
@Nonnull
String getVaultServer();
/**
* Returns true if global permissions should be considered when retrieving meta or player groups
* @return true if global permissions should be considered when retrieving meta or player groups
* @since 2.7
*/
boolean getVaultIncludeGlobal();
/**
* Returns the values set for data storage in the configuration
* @return the values set for data storage in the configuration
*/
@Nonnull
DatastoreConfiguration getDatastoreConfig();
/**
* Returns the storage method string from the configuration
* Gets the storage method string from the configuration
*
* @return the storage method string from the configuration
*/
@Nonnull
String getStorageMethod();
/**
* Returns true if split storage is enabled
* Gets true if split storage is enabled
*
* @return true if split storage is enabled
* @since 2.7
*/
boolean getSplitStorage();
/**
* Returns a map of split storage options
* Gets a map of split storage options
*
* @return a map of split storage options, where the key is the storage section, and the value is the storage
* method. For example: key = user, value = json
* @since 2.7
@@ -184,7 +110,7 @@ public interface LPConfiguration {
* <p>This method is nested under {@link Unsafe} because the keys
* and return types may change between versions without warning.</p>
*
* @param key the key, as defined as a parameter name in
* @param key the key, as defined as a field name in
* the "ConfigKeys" class.
* @return the corresponding object, if one is present
* @throws IllegalArgumentException if the key isn't known
@@ -34,18 +34,26 @@ import javax.annotation.Nonnull;
/**
* Represents the internal LuckPerms log.
*
* <p>The returned instance provides a copy of the data at the time of retrieval. Any changes made to log entries will
* only apply to this instance of the log. You can add to the log using the {@link Storage}, and then request an updated copy.</p>
* <p>The returned instance provides a copy of the data at the time of retrieval.</p>
*
* <p>Any changes made to log entries will only apply to this instance of the log.
* You can add to the log using the {@link Storage}, and then request an updated copy.</p>
*
* <p>All methods are thread safe, and return immutable and thread safe collections.</p>
*/
public interface Log {
/**
* Get the content of the log
*
* @return a {@link SortedSet} of all of the {@link LogEntry} objects in this {@link Log}
*/
@Nonnull
SortedSet<LogEntry> getContent();
/**
* Get the content of the log
*
* @return all content in this log
*/
@Nonnull
@@ -57,18 +65,22 @@ public interface Log {
* @param pageNo the page number
* @return the page content
* @throws IllegalArgumentException if the pageNo is less than 1
* @throws IllegalStateException if the log doesn't contain enough entries to populate the page. See {@link #getRecentMaxPages()}}
* @throws IllegalStateException if the log doesn't contain enough entries to populate the page. See {@link #getRecentMaxPages()}
*/
@Nonnull
SortedMap<Integer, LogEntry> getRecent(int pageNo);
/**
* Gets the max page number for the recent pages.
*
* @return the max page number allowed in the {@link #getRecent(int)} method
*/
int getRecentMaxPages();
/**
* Gets the recent content for a given actor
*
* @param actor the uuid of the actor to filter by
* @return all content in this log where is actor = uuid
*/
@@ -76,18 +88,20 @@ public interface Log {
SortedSet<LogEntry> getRecent(@Nonnull UUID actor);
/**
* Gets the recent content for the uuid, separated into pages
* Gets the recent content for a given actor, separated by page
*
* @param pageNo the page number
* @param actor the uuid of the actor to filter by
* @return the page content
* @throws IllegalArgumentException if the pageNo is less than 1
* @throws IllegalStateException if the log doesn't contain enough entries to populate the page. See {@link #getRecentMaxPages(UUID)}}
* @throws IllegalStateException if the log doesn't contain enough entries to populate the page. See {@link #getRecentMaxPages(UUID)}
*/
@Nonnull
SortedMap<Integer, LogEntry> getRecent(int pageNo, @Nonnull UUID actor);
/**
* Gets the max page number for the recent pages.
*
* @param actor the actor to filter by
* @return the max page number allowed in the {@link #getRecent(int, UUID)} method
*/
@@ -95,6 +109,8 @@ public interface Log {
/**
* Gets the log content for a given user
*
* @param uuid the uuid to filter by
* @return all content in this log where the user = uuid
*/
@@ -102,18 +118,20 @@ public interface Log {
SortedSet<LogEntry> getUserHistory(@Nonnull UUID uuid);
/**
* Gets the user history content, separated by pages
* Gets the log content for a given user, separated by page
*
* @param pageNo the page number
* @param uuid the uuid of the acted user to filter by
* @return the page content
* @throws IllegalArgumentException if the pageNo is less than 1
* @throws IllegalStateException if the log doesn't contain enough entries to populate the page. See {@link #getUserHistoryMaxPages(UUID)}}
* @throws IllegalStateException if the log doesn't contain enough entries to populate the page. See {@link #getUserHistoryMaxPages(UUID)}
*/
@Nonnull
SortedMap<Integer, LogEntry> getUserHistory(int pageNo, @Nonnull UUID uuid);
/**
* Gets the max page number for the user history pages.
*
* @param uuid the uuid to filter by
* @return the max page number allowed in the {@link #getUserHistory(int, UUID)} method
*/
@@ -121,6 +139,8 @@ public interface Log {
/**
* Gets the log content for a given group
*
* @param name the name to filter by
* @return all content in this log where the group = name
*/
@@ -128,18 +148,20 @@ public interface Log {
SortedSet<LogEntry> getGroupHistory(@Nonnull String name);
/**
* Gets the group history content, separated by pages
* Gets the log content for a given group, separated by page
*
* @param pageNo the page number
* @param name the name of the acted group to filter by
* @return the page content
* @throws IllegalArgumentException if the pageNo is less than 1
* @throws IllegalStateException if the log doesn't contain enough entries to populate the page. See {@link #getGroupHistoryMaxPages(String)}}
* @throws IllegalStateException if the log doesn't contain enough entries to populate the page. See {@link #getGroupHistoryMaxPages(String)}
*/
@Nonnull
SortedMap<Integer, LogEntry> getGroupHistory(int pageNo, @Nonnull String name);
/**
* Gets the max page number for the group history pages.
*
* @param name the name to filter by
* @return the max page number allowed in the {@link #getGroupHistory(int, String)} method
*/
@@ -147,6 +169,8 @@ public interface Log {
/**
* Gets the log content for a given track
*
* @param name the name to filter by
* @return all content in this log where the track = name
*/
@@ -154,25 +178,29 @@ public interface Log {
SortedSet<LogEntry> getTrackHistory(@Nonnull String name);
/**
* Gets the track history content, separated by pages
* Gets the log content for a given track, separated by page
*
* @param pageNo the page number
* @param name the name of the acted track to filter by
* @return the page content
* @throws IllegalArgumentException if the pageNo is less than 1
* @throws IllegalStateException if the log doesn't contain enough entries to populate the page. See {@link #getTrackHistoryMaxPages(String)}
*/
@Nonnull
SortedMap<Integer, LogEntry> getTrackHistory(int pageNo, @Nonnull String name);
/**
* Gets the max page number for the track history pages.
*
* @param name the name to filter by
* @return the max page number allowed in the {@link #getTrackHistory(int, String)} method
* @throws IllegalArgumentException if the pageNo is less than 1
* @throws IllegalStateException if the log doesn't contain enough entries to populate the page. See {@link #getTrackHistoryMaxPages(String)}}
*/
int getTrackHistoryMaxPages(@Nonnull String name);
/**
* Gets the log content for a given search query
*
* @param query the query to filter by
* @return all content in this log where the content matches query
*/
@@ -180,7 +208,7 @@ public interface Log {
SortedSet<LogEntry> getSearch(@Nonnull String query);
/**
* Gets the search content, separated by pages
* Gets the log content for a given search query, separated by page
*
* @param pageNo the page number
* @param query the query to filter by
@@ -192,8 +220,11 @@ public interface Log {
SortedMap<Integer, LogEntry> getSearch(int pageNo, @Nonnull String query);
/**
* Gets the max page number for the search query pages.
*
* @param query the query to filter by
* @return the max page number allowed in the {@link #getSearch(int, String)} method
*/
int getSearchMaxPages(@Nonnull String query);
}
@@ -25,307 +25,139 @@
package me.lucko.luckperms.api;
import java.util.Optional;
import java.util.UUID;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* A single entry in the log
*
* <p>Implements {@link Comparable} ordering based upon the timestamp of the entry.</p>
* Represents a logged action.
*/
public class LogEntry implements Comparable<LogEntry> {
private static final String FORMAT = "&8(&e%s&8) [&a%s&8] (&b%s&8) &7--> &f%s";
public interface LogEntry extends Comparable<LogEntry> {
@Nonnull
public static LogEntryBuilder builder() {
return new LogEntryBuilder();
}
/**
* Gets the time in unix seconds when the action occurred.
*
* @return the timestamp
*/
long getTimestamp();
private long timestamp;
private UUID actor;
private String actorName;
private char type;
private UUID acted;
private String actedName;
private String action;
/**
* Gets the id of the object which performed the action.
*
* <p>This is the players uuid in most cases.</p>
*
* @return the actor id
*/
UUID getActor();
public LogEntry(long timestamp, @Nonnull UUID actor, @Nonnull String actorName, char type, @Nullable UUID acted, @Nonnull String actedName, @Nonnull String action) {
if (actor == null) {
throw new NullPointerException("actor");
}
if (actorName == null) {
throw new NullPointerException("actorName");
}
if (actedName == null) {
throw new NullPointerException("actedName");
}
if (action == null) {
throw new NullPointerException("action");
/**
* Gets the name describing the actor.
*
* @return the name of the actor
*/
String getActorName();
/**
* Gets the type of action.
*
* @return the action type
*/
Type getType();
/**
* Gets the uuid of the object which was acted upon.
*
* <p>Will only return a value for {@link Type#USER} entries.</p>
*
* @return the uuid of acted object
*/
Optional<UUID> getActed();
/**
* Gets the name describing the object which was acted upon
*
* @return the name of the acted object
*/
String getActedName();
/**
* Returns a string describing the action which took place.
*
* <p>In most instances, this returns a variation of the command string which caused
* the change.</p>
*
* @return the action
*/
String getAction();
/**
* Represents the type of a {@link LogEntry}.
*
* @since 3.3
*/
enum Type {
USER('U'),
GROUP('G'),
TRACK('T');
private final char code;
Type(char code) {
this.code = code;
}
this.timestamp = timestamp;
this.actor = actor;
this.actorName = actorName;
this.type = type;
this.acted = acted;
this.actedName = actedName;
this.action = action;
}
protected LogEntry() {
this.timestamp = 0L;
this.actor = null;
this.actorName = null;
this.type = Character.MIN_VALUE;
this.acted = null;
this.actedName = null;
this.action = null;
}
@Override
public int compareTo(LogEntry o) {
return equals(o) ? 0 : (Long.compare(timestamp, o.getTimestamp()) == 0 ? 1 : Long.compare(timestamp, o.getTimestamp()));
}
public boolean matchesSearch(@Nonnull String query) {
query = query.toLowerCase();
return actorName.toLowerCase().contains(query) || actedName.toLowerCase().contains(query)
|| action.toLowerCase().contains(query);
}
@Nonnull
public String getFormatted() {
return String.format(FORMAT,
String.valueOf(actorName).equals("null") ? actor.toString() : actorName,
Character.toString(type),
String.valueOf(actedName).equals("null") && acted != null ? acted.toString() : actedName,
action
);
}
public long getTimestamp() {
return timestamp;
}
void setTimestamp(long timestamp) {
this.timestamp = timestamp;
}
@Nonnull
public UUID getActor() {
return actor;
}
void setActor(@Nonnull UUID actor) {
this.actor = actor;
}
@Nonnull
public String getActorName() {
return actorName;
}
void setActorName(@Nonnull String actorName) {
this.actorName = actorName;
}
public char getType() {
return type;
}
void setType(char type) {
this.type = type;
}
@Nullable
public UUID getActed() {
return acted;
}
void setActed(@Nullable UUID acted) {
this.acted = acted;
}
@Nonnull
public String getActedName() {
return actedName;
}
void setActedName(@Nonnull String actedName) {
this.actedName = actedName;
}
@Nonnull
public String getAction() {
return action;
}
void setAction(@Nonnull String action) {
this.action = action;
}
@Override
public String toString() {
return "LogEntry(" +
"timestamp=" + this.getTimestamp() + ", " +
"actor=" + this.getActor() + ", " +
"actorName=" + this.getActorName() + ", " +
"type=" + this.getType() + ", " +
"acted=" + this.getActed() + ", " +
"actedName=" + this.getActedName() + ", " +
"action=" + this.getAction() +
")";
}
@Override
public boolean equals(Object o) {
if (o == this) return true;
if (!(o instanceof LogEntry)) return false;
final LogEntry other = (LogEntry) o;
return this.getTimestamp() == other.getTimestamp() &&
(this.getActor() == null ? other.getActor() == null : this.getActor().equals(other.getActor())) &&
(this.getActorName() == null ? other.getActorName() == null : this.getActorName().equals(other.getActorName())) &&
this.getType() == other.getType() &&
(this.getActed() == null ? other.getActed() == null : this.getActed().equals(other.getActed())) &&
(this.getActedName() == null ? other.getActedName() == null : this.getActedName().equals(other.getActedName())) &&
(this.getAction() == null ? other.getAction() == null : this.getAction().equals(other.getAction()));
}
@Override
public int hashCode() {
final int PRIME = 59;
int result = 1;
result = result * PRIME + (int) (this.getTimestamp() >>> 32 ^ this.getTimestamp());
result = result * PRIME + (this.getActor() == null ? 43 : this.getActor().hashCode());
result = result * PRIME + (this.getActorName() == null ? 43 : this.getActorName().hashCode());
result = result * PRIME + this.getType();
result = result * PRIME + (this.getActed() == null ? 43 : this.getActed().hashCode());
result = result * PRIME + (this.getActedName() == null ? 43 : this.getActedName().hashCode());
result = result * PRIME + (this.getAction() == null ? 43 : this.getAction().hashCode());
return result;
}
public static class LogEntryBuilder extends AbstractLogEntryBuilder<LogEntry, LogEntry.LogEntryBuilder> {
@Override
protected LogEntry createObj() {
return new LogEntry();
public char getCode() {
return this.code;
}
@Override
protected LogEntryBuilder getThis() {
return this;
}
}
public static abstract class AbstractLogEntryBuilder<T extends LogEntry, B extends AbstractLogEntryBuilder<T, B>> {
private T obj;
private B thisObj;
public AbstractLogEntryBuilder() {
obj = createObj();
thisObj = getThis();
}
protected abstract T createObj();
protected abstract B getThis();
public long getTimestamp() {
return obj.getTimestamp();
}
public UUID getActor() {
return obj.getActor();
}
public String getActorName() {
return obj.getActorName();
}
public char getType() {
return obj.getType();
}
public UUID getActed() {
return obj.getActed();
}
public String getActedName() {
return obj.getActedName();
}
public String getAction() {
return obj.getAction();
}
public B timestamp(long timestamp) {
obj.setTimestamp(timestamp);
return thisObj;
}
public B actor(UUID actor) {
obj.setActor(actor);
return thisObj;
}
public B actorName(String actorName) {
obj.setActorName(actorName);
return thisObj;
}
public B type(char type) {
obj.setType(type);
return thisObj;
}
public B acted(UUID acted) {
obj.setActed(acted);
return thisObj;
}
public B actedName(String actedName) {
obj.setActedName(actedName);
return thisObj;
}
public B action(String action) {
obj.setAction(action);
return thisObj;
}
public T build() {
if (getActor() == null) {
throw new NullPointerException("actor");
public static Type valueOf(char code) {
switch (code) {
case 'U':
case 'u':
return USER;
case 'G':
case 'g':
return GROUP;
case 'T':
case 't':
return TRACK;
default:
throw new IllegalArgumentException("Unknown code: " + code);
}
if (getActorName() == null) {
throw new NullPointerException("actorName");
}
if (getActedName() == null) {
throw new NullPointerException("actedName");
}
if (getAction() == null) {
throw new NullPointerException("action");
}
return obj;
}
}
/**
* Builds a LogEntry instance
*/
interface Builder {
@Nonnull
Builder setTimestamp(long timestamp);
@Nonnull
Builder setActor(@Nonnull UUID actor);
@Nonnull
Builder setActorName(@Nonnull String actorName);
@Nonnull
Builder setType(@Nonnull Type type);
@Nonnull
Builder setActed(@Nullable UUID acted);
@Nonnull
Builder setActedName(@Nonnull String actedName);
@Nonnull
Builder setAction(@Nonnull String action);
@Nonnull
LogEntry build();
@Override
public String toString() {
return "LogEntry.LogEntryBuilder(" +
"timestamp=" + this.getTimestamp() + ", " +
"actor=" + this.getActor() + ", " +
"actorName=" + this.getActorName() + ", " +
"type=" + this.getType() + ", " +
"acted=" + this.getActed() + ", " +
"actedName=" + this.getActedName() + ", " +
"action=" + this.getAction() +
")";
}
}
}
@@ -25,52 +25,79 @@
package me.lucko.luckperms.api;
import me.lucko.luckperms.LuckPerms;
import me.lucko.luckperms.api.context.ContextCalculator;
import me.lucko.luckperms.api.context.ContextManager;
import me.lucko.luckperms.api.context.ContextSet;
import me.lucko.luckperms.api.event.EventBus;
import me.lucko.luckperms.api.manager.GroupManager;
import me.lucko.luckperms.api.manager.TrackManager;
import me.lucko.luckperms.api.manager.UserManager;
import me.lucko.luckperms.api.metastacking.MetaStackFactory;
import me.lucko.luckperms.api.platform.PlatformInfo;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* The root API interface for LuckPerms
* The LuckPerms API.
*
* <p>This interface is the base of the entire API package. All API functions
* are accessed via this interface.</p>
*
* <p>An instance can be obtained via {@link LuckPerms#getApi()}, or the platforms
* Services Manager.</p>
*/
public interface LuckPermsApi {
/**
* Gets information about the platform LuckPerms is running on.
*
* @return the platform info
* @since 4.0
*/
@Nonnull
PlatformInfo getPlatformInfo();
/**
* Gets the user manager
*
* @return the user manager
* @since 4.0
*/
@Nonnull
UserManager getUserManager();
/**
* Gets the group manager
*
* @return the group manager
* @since 4.0
*/
@Nonnull
GroupManager getGroupManager();
/**
* Gets the track manager
*
* @return the track manager
* @since 4.0
*/
@Nonnull
TrackManager getTrackManager();
/**
* Schedules an update task to run
*/
void runUpdateTask();
/**
* Gets the API version
*
* @return the version of the API running on the platform
* @since 2.6
*/
double getApiVersion();
/**
* Gets the plugin version
*
* @return the version of the plugin running on the platform
* @since 4.0
*/
@Nonnull
String getVersion();
/**
* Gets the platform LuckPerms is running on
*
* @return the platform LuckPerms is running on
* @since 2.7
*/
@Nonnull
PlatformType getPlatformType();
CompletableFuture<Void> runUpdateTask();
/**
* Gets the event bus, used for subscribing to events
@@ -82,7 +109,7 @@ public interface LuckPermsApi {
EventBus getEventBus();
/**
* Gets a wrapped {@link LPConfiguration} instance, with read only access
* Gets the configuration
*
* @return a configuration instance
*/
@@ -90,7 +117,7 @@ public interface LuckPermsApi {
LPConfiguration getConfiguration();
/**
* Gets a wrapped {@link Storage} instance.
* Gets the backend storage dao
*
* @return a storage instance
* @since 2.14
@@ -99,7 +126,7 @@ public interface LuckPermsApi {
Storage getStorage();
/**
* Gets the messaging service in use on the platform, if present.
* Gets the messaging service
*
* @return an optional that may contain a messaging service instance.
*/
@@ -107,21 +134,41 @@ public interface LuckPermsApi {
Optional<MessagingService> getMessagingService();
/**
* Gets the {@link Logger} wrapping used by the platform
*
* @return the logger instance
*/
@Nonnull
Logger getLogger();
/**
* Gets a wrapped {@link UuidCache} instance, providing read access to the LuckPerms internal uuid caching system
* Gets a {@link UuidCache} instance, providing read access to the LuckPerms
* internal uuid caching system
*
* @return a uuid cache instance
*/
@Nonnull
UuidCache getUuidCache();
/**
* Gets the context manager
*
* @return the context manager
* @since 4.0
*/
ContextManager getContextManager();
/**
* Gets the node factory
*
* @return the node factory
*/
@Nonnull
NodeFactory getNodeFactory();
/**
* Gets the MetaStackFactory
*
* @return the meta stack factory
* @since 3.2
*/
@Nonnull
MetaStackFactory getMetaStackFactory();
// convenience methods
/**
* Gets a wrapped user object from the user storage
*
@@ -130,19 +177,21 @@ public interface LuckPermsApi {
* @throws NullPointerException if the uuid is null
*/
@Nullable
User getUser(@Nonnull UUID uuid);
default User getUser(@Nonnull UUID uuid) {
return getUserManager().getUser(uuid);
}
/**
* Gets a wrapped user object from the user storage.
*
* <p>This method does not return null, unlike {@link #getUser(UUID)}</p>
*
* @param uuid the uuid of the user to get
* @return an optional {@link User} object
* @throws NullPointerException if the uuid is null
*/
@Nonnull
Optional<User> getUserSafe(@Nonnull UUID uuid);
default Optional<User> getUserSafe(@Nonnull UUID uuid) {
return getUserManager().getUserOpt(uuid);
}
/**
* Gets a wrapped user object from the user storage
@@ -152,19 +201,21 @@ public interface LuckPermsApi {
* @throws NullPointerException if the name is null
*/
@Nullable
User getUser(@Nonnull String name);
default User getUser(@Nonnull String name) {
return getUserManager().getUser(name);
}
/**
* Gets a wrapped user object from the user storage.
*
* <p>This method does not return null, unlike {@link #getUser(String)}</p>
*
* @param name the username of the user to get
* @return an optional {@link User} object
* @throws NullPointerException if the name is null
*/
@Nonnull
Optional<User> getUserSafe(@Nonnull String name);
default Optional<User> getUserSafe(@Nonnull String name) {
return getUserManager().getUserOpt(name);
}
/**
* Gets a set of all loaded users.
@@ -172,7 +223,9 @@ public interface LuckPermsApi {
* @return a {@link Set} of {@link User} objects
*/
@Nonnull
Set<User> getUsers();
default Set<User> getUsers() {
return getUserManager().getLoadedUsers();
}
/**
* Check if a user is loaded in memory
@@ -181,16 +234,19 @@ public interface LuckPermsApi {
* @return true if the user is loaded
* @throws NullPointerException if the uuid is null
*/
boolean isUserLoaded(@Nonnull UUID uuid);
default boolean isUserLoaded(@Nonnull UUID uuid) {
return getUserManager().isLoaded(uuid);
}
/**
* Unload a user from the internal storage, if they're not currently online.
*
* @param user the user to unload
* @throws NullPointerException if the user is null
* @since 2.6
*/
void cleanupUser(@Nonnull User user);
default void cleanupUser(@Nonnull User user) {
getUserManager().cleanupUser(user);
}
/**
* Gets a wrapped group object from the group storage
@@ -200,7 +256,9 @@ public interface LuckPermsApi {
* @throws NullPointerException if the name is null
*/
@Nullable
Group getGroup(@Nonnull String name);
default Group getGroup(@Nonnull String name) {
return getGroupManager().getGroup(name);
}
/**
* Gets a wrapped group object from the group storage.
@@ -212,7 +270,9 @@ public interface LuckPermsApi {
* @throws NullPointerException if the name is null
*/
@Nonnull
Optional<Group> getGroupSafe(@Nonnull String name);
default Optional<Group> getGroupSafe(@Nonnull String name) {
return getGroupManager().getGroupOpt(name);
}
/**
* Gets a set of all loaded groups.
@@ -220,7 +280,9 @@ public interface LuckPermsApi {
* @return a {@link Set} of {@link Group} objects
*/
@Nonnull
Set<Group> getGroups();
default Set<Group> getGroups() {
return getGroupManager().getLoadedGroups();
}
/**
* Check if a group is loaded in memory
@@ -229,17 +291,22 @@ public interface LuckPermsApi {
* @return true if the group is loaded
* @throws NullPointerException if the name is null
*/
boolean isGroupLoaded(@Nonnull String name);
default boolean isGroupLoaded(@Nonnull String name) {
return getGroupManager().isLoaded(name);
}
/**
* Gets a wrapped track object from the track storage
*
* @param name the name of the track to get
* @return a {@link Track} object, if one matching the name exists, or null if not
* @return a {@link Track} object, if one matching the name exists, or null
* if not
* @throws NullPointerException if the name is null
*/
@Nullable
Track getTrack(@Nonnull String name);
default Track getTrack(@Nonnull String name) {
return getTrackManager().getTrack(name);
}
/**
* Gets a wrapped track object from the track storage.
@@ -251,7 +318,9 @@ public interface LuckPermsApi {
* @throws NullPointerException if the name is null
*/
@Nonnull
Optional<Track> getTrackSafe(@Nonnull String name);
default Optional<Track> getTrackSafe(@Nonnull String name) {
return getTrackManager().getTrackOpt(name);
}
/**
* Gets a set of all loaded tracks.
@@ -259,7 +328,9 @@ public interface LuckPermsApi {
* @return a {@link Set} of {@link Track} objects
*/
@Nonnull
Set<Track> getTracks();
default Set<Track> getTracks() {
return getTrackManager().getLoadedTracks();
}
/**
* Check if a track is loaded in memory
@@ -268,24 +339,17 @@ public interface LuckPermsApi {
* @return true if the track is loaded
* @throws NullPointerException if the name is null
*/
boolean isTrackLoaded(@Nonnull String name);
default boolean isTrackLoaded(@Nonnull String name) {
return getTrackManager().isLoaded(name);
}
/**
* Gets the node factory instance for the platform
* Returns a new LogEntry Builder instance
*
* @return the node factory
* @return a new builder
* @since 4.0
*/
@Nonnull
NodeFactory getNodeFactory();
/**
* Gets the MetaStackFactory, used for creating MetaStacks.
*
* @return the meta stack factory
* @since 3.2
*/
@Nonnull
MetaStackFactory getMetaStackFactory();
LogEntry.Builder newLogEntryBuilder();
/**
* Returns a permission builder instance
@@ -297,15 +361,19 @@ public interface LuckPermsApi {
* @since 2.6
*/
@Nonnull
Node.Builder buildNode(@Nonnull String permission) throws IllegalArgumentException;
default Node.Builder buildNode(@Nonnull String permission) throws IllegalArgumentException {
return getNodeFactory().newBuilder(permission);
}
/**
* Register a custom context calculator to the server
*
* @param contextCalculator the context calculator to register. The type MUST be the player class of the platform.
* @param calculator the context calculator to register. The type MUST be the player class of the platform.
* @throws ClassCastException if the type is not the player class of the platform.
*/
void registerContextCalculator(@Nonnull ContextCalculator<?> contextCalculator);
default void registerContextCalculator(@Nonnull ContextCalculator<?> calculator) {
getContextManager().registerCalculator(calculator);
}
/**
* Gets a calculated context instance for the user using the rules of the platform.
@@ -316,7 +384,9 @@ public interface LuckPermsApi {
* @return an optional containing contexts. Will return empty if the user is not online.
*/
@Nonnull
Optional<Contexts> getContextForUser(@Nonnull User user);
default Optional<Contexts> getContextForUser(@Nonnull User user) {
return getContextManager().lookupApplicableContexts(user);
}
/**
* Gets set of contexts applicable to a player using the platforms {@link ContextCalculator}s.
@@ -326,7 +396,9 @@ public interface LuckPermsApi {
* @since 2.17
*/
@Nonnull
ContextSet getContextForPlayer(@Nonnull Object player);
default ContextSet getContextForPlayer(@Nonnull Object player) {
return getContextManager().getApplicableContext(player);
}
/**
* Gets a Contexts instance for the player using the platforms {@link ContextCalculator}s.
@@ -336,23 +408,8 @@ public interface LuckPermsApi {
* @since 3.3
*/
@Nonnull
Contexts getContextsForPlayer(@Nonnull Object player);
/**
* Gets the unique players which have connected to the server since it started.
*
* @return the unique connections
* @since 3.3
*/
@Nonnull
Set<UUID> getUniqueConnections();
/**
* Gets the time when the plugin first started in milliseconds.
*
* @return the enable time
* @since 3.3
*/
long getStartTime();
default Contexts getContextsForPlayer(@Nonnull Object player) {
return getContextManager().getApplicableContexts(player);
}
}
@@ -1,286 +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.api;
import me.lucko.luckperms.LuckPerms;
import me.lucko.luckperms.exceptions.ObjectAlreadyHasException;
import me.lucko.luckperms.exceptions.ObjectLacksException;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* A collection of utilities to help retrieve meta values for {@link PermissionHolder}s
*
* @since 2.7
* @deprecated in favour of using {@link NodeFactory#makeMetaNode(String, String)} or {@link me.lucko.luckperms.api.caching.MetaData}.
*/
@Deprecated
public class MetaUtils {
private static String escapeDelimiters(String s, String... delims) {
for (String delim : delims) {
s = s.replace(delim, "\\" + delim);
}
return s;
}
private static String unescapeDelimiters(String s, String... delims) {
for (String delim : delims) {
s = s.replace("\\" + delim, delim);
}
return s;
}
/**
* Escapes special characters used within LuckPerms, so the string can be saved without issues
*
* @param s the string to escape
* @return an escaped string
* @throws NullPointerException if the string is null
*/
public static String escapeCharacters(String s) {
if (s == null) {
throw new NullPointerException();
}
return escapeDelimiters(s, ".", "/", "-", "$");
}
/**
* Unescapes special characters used within LuckPerms, the inverse of {@link #escapeCharacters(String)}
*
* @param s the string to unescape
* @return an unescaped string
* @throws NullPointerException if the string is null
*/
public static String unescapeCharacters(String s) {
if (s == null) {
throw new NullPointerException();
}
s = s.replace("{SEP}", ".");
s = s.replace("{FSEP}", "/");
s = s.replace("{DSEP}", "$");
s = unescapeDelimiters(s, ".", "/", "-", "$");
return s;
}
/**
* Sets a meta value on a holder
*
* @param holder the holder to apply the meta node to
* @param server the server to apply the meta on, can be null
* @param world the world to apply the meta on, can be null
* @param key the meta key
* @param value the meta value
* @throws NullPointerException if the holder, node or value is null
* @throws IllegalArgumentException if the node or value is empty
*/
public static void setMeta(PermissionHolder holder, String server, String world, String key, String value) {
if (holder == null) throw new NullPointerException("holder");
if (key == null) throw new NullPointerException("node");
if (value == null) throw new NullPointerException("value");
if (key.equals("")) throw new IllegalArgumentException("node is empty");
if (value.equals("")) throw new IllegalArgumentException("value is empty");
key = escapeCharacters(key);
value = escapeCharacters(value);
Set<Node> toRemove = new HashSet<>();
for (Node n : holder.getEnduringPermissions()) {
if (n.isMeta() && n.getMeta().getKey().equals(key)) {
toRemove.add(n);
}
}
for (Node n : toRemove) {
try {
holder.unsetPermission(n);
} catch (ObjectLacksException ignored) {}
}
Node.Builder metaNode = LuckPerms.getApi().buildNode("meta." + key + "." + value).setValue(true);
if (server != null) {
metaNode.setServer(server);
}
if (world != null) {
metaNode.setWorld(world);
}
try {
holder.setPermission(metaNode.build());
} catch (ObjectAlreadyHasException ignored) {}
}
/**
* Gets a meta value for the holder
*
* @param holder the holder to get the meta from
* @param server the server to retrieve the meta on, can be null
* @param world the world to retrieve the meta on, can be null
* @param node the node to get
* @param defaultValue the default value to return if the node is not set
* @param includeGlobal if global nodes should be considered when retrieving the meta
* @return a meta string, or the default value if the user does not have the meta node
* @throws NullPointerException if the holder or node is null
* @throws IllegalArgumentException if the node is empty
*/
public static String getMeta(PermissionHolder holder, String server, String world, String node, String defaultValue, boolean includeGlobal) {
if (holder == null) throw new NullPointerException("holder");
if (node == null) throw new NullPointerException("node");
if (node.equals("")) {
throw new IllegalArgumentException("node is empty");
}
node = escapeCharacters(node);
for (Node n : holder.getPermissions()) {
if (!n.getValue() || !n.isMeta()) continue;
if (!n.shouldApplyOnServer(server, includeGlobal, false)) continue;
if (!n.shouldApplyOnWorld(world, includeGlobal, false)) continue;
Map.Entry<String, String> meta = n.getMeta();
if (meta.getKey().equalsIgnoreCase(node)) {
return unescapeCharacters(meta.getValue());
}
}
return defaultValue;
}
private static void setChatMeta(boolean prefix, PermissionHolder holder, String value, int priority, String server, String world) {
if (holder == null) throw new NullPointerException("holder");
if (value == null || value.equals("")) {
throw new IllegalArgumentException("value is null/empty");
}
Node.Builder node = LuckPerms.getApi().buildNode(prefix ? "prefix" : "suffix" + "." + priority + "." + escapeCharacters(value));
node.setValue(true);
if (server != null) {
node.setServer(server);
}
if (world != null) {
node.setWorld(world);
}
try {
holder.setPermission(node.build());
} catch (ObjectAlreadyHasException ignored) {}
}
/**
* Adds a prefix to a holder on a specific server and world
*
* @param holder the holder to set the prefix for
* @param prefix the prefix value
* @param priority the priority to set the prefix at
* @param server the server to set the prefix on, can be null
* @param world the world to set the prefix on, can be null
* @throws NullPointerException if the holder is null
* @throws IllegalArgumentException if the prefix is null or empty
*/
public static void setPrefix(PermissionHolder holder, String prefix, int priority, String server, String world) {
setChatMeta(true, holder, prefix, priority, server, world);
}
/**
* Adds a suffix to a holder on a specific server and world
*
* @param holder the holder to set the suffix for
* @param suffix the suffix value
* @param priority the priority to set the suffix at
* @param server the server to set the suffix on, can be null
* @param world the world to set the suffix on, can be null
* @throws NullPointerException if the holder is null
* @throws IllegalArgumentException if the suffix is null or empty
*/
public static void setSuffix(PermissionHolder holder, String suffix, int priority, String server, String world) {
setChatMeta(false, holder, suffix, priority, server, world);
}
private static String getChatMeta(boolean prefix, PermissionHolder holder, String server, String world, boolean includeGlobal) {
if (holder == null) {
throw new NullPointerException("holder");
}
int priority = Integer.MIN_VALUE;
String meta = null;
for (Node n : holder.getAllNodes(Contexts.allowAll())) {
if (!n.getValue()) continue;
if (!n.shouldApplyOnServer(server, includeGlobal, false)) continue;
if (!n.shouldApplyOnWorld(world, includeGlobal, false)) continue;
if (prefix ? !n.isPrefix() : !n.isSuffix()) {
continue;
}
Map.Entry<Integer, String> value = prefix ? n.getPrefix() : n.getSuffix();
if (value.getKey() > priority) {
meta = value.getValue();
priority = value.getKey();
}
}
return meta == null ? "" : unescapeCharacters(meta);
}
/**
* Returns a holders highest priority prefix, if they have one
*
* @param holder the holder
* @param server the server to retrieve the prefix on
* @param world the world to retrieve the prefix on
* @param includeGlobal if global nodes should be considered when retrieving the prefix
* @return a prefix string, if the holder has one, or an empty string if not.
* @throws NullPointerException if the holder is null
*/
public static String getPrefix(PermissionHolder holder, String server, String world, boolean includeGlobal) {
return getChatMeta(true, holder, server, world, includeGlobal);
}
/**
* Returns a holders highest priority suffix, if they have one
*
* @param holder the holder
* @param server the server to retrieve the suffix on
* @param world the world to retrieve the suffix on
* @param includeGlobal if global nodes should be considered when retrieving the suffix
* @return a suffix string, if the holder has one, or an empty string if not.
* @throws NullPointerException if the holder is null
*/
public static String getSuffix(PermissionHolder holder, String server, String world, boolean includeGlobal) {
return getChatMeta(false, holder, server, world, includeGlobal);
}
private MetaUtils() {
}
}
@@ -34,19 +34,20 @@ import java.util.Optional;
import java.util.Set;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* An immutable permission node
* Represents a permission node.
*
* <p>Use {@link LuckPermsApi#buildNode(String)} to get an instance.</p>
* <p>All implementations of this interface must be immutable.</p>
*
* <p>Use the {@link NodeFactory} to obtain and construct instances.</p>
*
* @since 2.6
*/
public interface Node extends Map.Entry<String, Boolean> {
/**
* Returns the actual permission string
* Gets the permission string
*
* @return the actual permission node
*/
@@ -54,36 +55,50 @@ public interface Node extends Map.Entry<String, Boolean> {
String getPermission();
/**
* Gets what value the permission is set to. A negated node would return <code>false</code>.
* Gets the value.
*
* <p>A negated node would return a value of <code>false</code>.</p>
*
* @return the permission's value
*/
@Override
@Nonnull
Boolean getValue();
default Boolean getValue() {
return getValuePrimitive();
}
/**
* Returns the value of this node as a tristate
* Gets the value.
*
* <p>A negated node would return a value of <code>false</code>.</p>
*
* @return the permission's value
*/
boolean getValuePrimitive();
/**
* Gets the value of this node as a {@link Tristate}
*
* @return the value of this node as a Tristate
*/
@Nonnull
default Tristate getTristate() {
return Tristate.fromBoolean(getValue());
return Tristate.fromBoolean(getValuePrimitive());
}
/**
* Returns if the node is negated
* Gets if the node is negated
*
* @return true if the node is negated
*/
default boolean isNegated() {
return !getValue();
return !getValuePrimitive();
}
/**
* If this node is set to override explicitly.
* This value does not persist across saves, and is therefore only useful for transient nodes
* Gets if this node is set to override explicitly.
*
* <p>This value does not persist across saves, and is therefore only useful for transient nodes</p>
*
* @return true if this node is set to override explicitly
*/
@@ -106,21 +121,21 @@ public interface Node extends Map.Entry<String, Boolean> {
Optional<String> getWorld();
/**
* Returns if this node is server specific
* Gets if this node is server specific
*
* @return true if this node is server specific
*/
boolean isServerSpecific();
/**
* Returns if this node is server specific
* Gets if this node is server specific
*
* @return true if this node is server specific
*/
boolean isWorldSpecific();
/**
* Returns if this node applies globally, and has no specific context
* Gets if this node applies globally, and therefore has no specific context
*
* @return true if this node applies globally, and has no specific context
* @since 3.1
@@ -128,7 +143,7 @@ public interface Node extends Map.Entry<String, Boolean> {
boolean appliesGlobally();
/**
* Returns if this node has any specific context in order to apply.
* Gets if this node has any specific context in order for it to apply
*
* @return true if this node has specific context
* @since 3.1
@@ -136,51 +151,7 @@ public interface Node extends Map.Entry<String, Boolean> {
boolean hasSpecificContext();
/**
* Returns if this node is able to apply in the given context
*
* @param includeGlobal if global server values should apply
* @param includeGlobalWorld if global world values should apply
* @param server the server being checked against, or null
* @param world the world being checked against, or null
* @param context the context being checked against, or null
* @param applyRegex if regex should be applied
* @return true if the node should apply, otherwise false
* @since 3.1
*/
boolean shouldApply(boolean includeGlobal, boolean includeGlobalWorld, @Nullable String server, @Nullable String world, @Nullable ContextSet context, boolean applyRegex);
/**
* If this node should apply on a specific server
*
* @param server the name of the server
* @param includeGlobal if global permissions should apply
* @param applyRegex if regex should be applied
* @return true if the node should apply
*/
boolean shouldApplyOnServer(@Nullable String server, boolean includeGlobal, boolean applyRegex);
/**
* If this node should apply on a specific world
*
* @param world the name of the world
* @param includeGlobal if global permissions should apply
* @param applyRegex if regex should be applied
* @return true if the node should apply
*/
boolean shouldApplyOnWorld(@Nullable String world, boolean includeGlobal, boolean applyRegex);
/**
* If this node should apply in the given context
*
* @param context the context key value pairs
* @param worldAndServer if world and server contexts should be checked
* @return true if the node should apply
* @since 2.13
*/
boolean shouldApplyWithContext(@Nonnull ContextSet context, boolean worldAndServer);
/**
* If this node should apply in the given context
* Gets if this node should apply in the given context
*
* @param context the context key value pairs
* @return true if the node should apply
@@ -188,33 +159,6 @@ public interface Node extends Map.Entry<String, Boolean> {
*/
boolean shouldApplyWithContext(@Nonnull ContextSet context);
/**
* Similar to {@link #shouldApplyOnServer(String, boolean, boolean)}, except this method accepts a List
*
* @param servers the list of servers
* @param includeGlobal if global permissions should apply
* @return true if the node should apply
*/
boolean shouldApplyOnAnyServers(@Nonnull List<String> servers, boolean includeGlobal);
/**
* Similar to {@link #shouldApplyOnWorld(String, boolean, boolean)}, except this method accepts a List
*
* @param worlds the list of world
* @param includeGlobal if global permissions should apply
* @return true if the node should apply
*/
boolean shouldApplyOnAnyWorlds(@Nonnull List<String> worlds, boolean includeGlobal);
/**
* Resolves a list of wildcards that match this node
*
* @param possibleNodes a list of possible permission nodes
* @return a list of permissions that match this wildcard
*/
@Nonnull
List<String> resolveWildcard(@Nonnull List<String> possibleNodes);
/**
* Resolves any shorthand parts of this node and returns the full list
*
@@ -224,14 +168,14 @@ public interface Node extends Map.Entry<String, Boolean> {
List<String> resolveShorthand();
/**
* Returns if this node will expire in the future
* Gets if this node will expire in the future
*
* @return true if this node will expire in the future
*/
boolean isTemporary();
/**
* Returns if this node will not expire
* Gets if this node will not expire
*
* @return true if this node will not expire
*/
@@ -240,7 +184,7 @@ public interface Node extends Map.Entry<String, Boolean> {
}
/**
* Returns a unix timestamp in seconds when this node will expire
* Gets a unix timestamp in seconds when this node will expire
*
* @return the time in Unix time when this node will expire
* @throws IllegalStateException if the node is not temporary
@@ -248,7 +192,7 @@ public interface Node extends Map.Entry<String, Boolean> {
long getExpiryUnixTime() throws IllegalStateException;
/**
* Returns the date when this node will expire
* Gets the date when this node will expire
*
* @return the {@link Date} when this node will expire
* @throws IllegalStateException if the node is not temporary
@@ -257,7 +201,7 @@ public interface Node extends Map.Entry<String, Boolean> {
Date getExpiry() throws IllegalStateException;
/**
* Return the number of seconds until this permission will expire
* Gets the number of seconds until this permission will expire
*
* @return the number of seconds until this permission will expire
* @throws IllegalStateException if the node is not temporary
@@ -265,8 +209,9 @@ public interface Node extends Map.Entry<String, Boolean> {
long getSecondsTilExpiry() throws IllegalStateException;
/**
* Return true if the node has expired.
* This also returns false if the node is not temporary
* Gets if the node has expired.
*
* <p>This also returns false if the node is not temporary.</p>
*
* @return true if this node has expired
*/
@@ -291,24 +236,14 @@ public interface Node extends Map.Entry<String, Boolean> {
ContextSet getFullContexts();
/**
* Converts this node into a serialized form
*
* @return a serialized node string
* @deprecated because this serialized form is no longer used by the implementation.
*/
@Deprecated
@Nonnull
String toSerializedNode();
/**
* Returns if this is a group node
* Gets if this is a group node
*
* @return true if this is a group node
*/
boolean isGroupNode();
/**
* Returns the name of the group
* Gets the name of the group, if this is a group node.
*
* @return the name of the group
* @throws IllegalStateException if this is not a group node. See {@link #isGroupNode()}
@@ -317,7 +252,7 @@ public interface Node extends Map.Entry<String, Boolean> {
String getGroupName() throws IllegalStateException;
/**
* Returns if this node is a wildcard node
* Gets if this node is a wildcard node
*
* @return true if this node is a wildcard node
*/
@@ -332,7 +267,7 @@ public interface Node extends Map.Entry<String, Boolean> {
int getWildcardLevel() throws IllegalStateException;
/**
* Returns if this node is a meta node
* Gets if this node is a meta node
*
* @return true if this node is a meta node
*/
@@ -348,7 +283,7 @@ public interface Node extends Map.Entry<String, Boolean> {
Map.Entry<String, String> getMeta() throws IllegalStateException;
/**
* Returns if this node is a prefix node
* Gets if this node is a prefix node
*
* @return true if this node is a prefix node
*/
@@ -364,7 +299,7 @@ public interface Node extends Map.Entry<String, Boolean> {
Map.Entry<Integer, String> getPrefix() throws IllegalStateException;
/**
* Returns if this node is a suffix node
* Gets if this node is a suffix node
*
* @return true if this node is a suffix node
*/
@@ -28,27 +28,12 @@ package me.lucko.luckperms.api;
import javax.annotation.Nonnull;
/**
* Builds {@link Node} instances
* Assists with constructing {@link Node} instances.
*
* @since 2.17
*/
public interface NodeFactory {
/**
* Creates a node from a serialised node string
*
* @param serialisedPermission the serialised permission string
* @param value the value of the node
* @return a node instance
* @throws NullPointerException if the permission is null
* @deprecated since this format isn't used internally for permissions anymore
* @see Node#toSerializedNode()
*/
@Deprecated
@Nonnull
Node fromSerialisedNode(@Nonnull String serialisedPermission, boolean value);
/**
* Creates a new node builder from a given base permission string
*
@@ -69,20 +54,6 @@ public interface NodeFactory {
@Nonnull
Node.Builder newBuilderFromExisting(@Nonnull Node other);
/**
* Creates a node builder from a serialised node string
*
* @param serialisedPermission the serialised permission string
* @param value the value of the node
* @return a node builder instance
* @throws NullPointerException if the permission is null
* @deprecated since this format isn't used internally for permissions anymore
* @see Node#toSerializedNode()
*/
@Deprecated
@Nonnull
Node.Builder newBuilderFromSerialisedNode(@Nonnull String serialisedPermission, boolean value);
/**
* Creates a node builder from a group
@@ -96,6 +67,17 @@ public interface NodeFactory {
@Nonnull
Node.Builder makeGroupNode(@Nonnull Group group);
/**
* Creates a node builder from a group
*
* @param groupName the name of the group
* @return a node builder instance
* @throws NullPointerException if the groupName is null
* @since 4.0
*/
@Nonnull
Node.Builder makeGroupNode(@Nonnull String groupName);
/**
* Creates a node builder from a key value pair
*
@@ -30,8 +30,6 @@ import com.google.common.collect.Multimap;
import me.lucko.luckperms.api.context.ContextSet;
import me.lucko.luckperms.api.context.ImmutableContextSet;
import me.lucko.luckperms.exceptions.ObjectAlreadyHasException;
import me.lucko.luckperms.exceptions.ObjectLacksException;
import java.util.List;
import java.util.Map;
@@ -40,17 +38,17 @@ import java.util.SortedSet;
import java.util.function.Predicate;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* An object capable of holding permissions
* An object which holds permissions.
*
* <p>Any changes made will be lost unless the instance is saved back to the {@link Storage}.</p>
* <p>Any changes made to permission holding objects will be lost unless the
* instance is saved back to the {@link Storage}.</p>
*/
public interface PermissionHolder {
/**
* Gets the objects name
* Gets the objects name.
*
* <p>{@link User#getUuid()}, {@link User#getName()} or {@link Group#getName()} should normally be used instead of
* this method.</p>
@@ -123,7 +121,7 @@ public interface PermissionHolder {
SortedSet<? extends Node> getPermissions();
/**
* Similar to {@link #getPermissions()}, except without transient permissions
* Similar to {@link #getPermissions()}, except without transient permissions.
*
* <p>Unlike transient permissions, enduring permissions will be saved to storage, and exist after the session.</p>
*
@@ -134,7 +132,7 @@ public interface PermissionHolder {
Set<? extends Node> getEnduringPermissions();
/**
* Similar to {@link #getPermissions()}, except without enduring permissions
* Similar to {@link #getPermissions()}, except without enduring permissions.
*
* <p>Transient permissions only exist for the duration of the session.</p>
*
@@ -290,22 +288,12 @@ public interface PermissionHolder {
* Sets a permission for the object
*
* @param node The node to be set
* @throws ObjectAlreadyHasException if the object already has the permission
* @throws NullPointerException if the node is null
* @since 2.6
*/
void setPermission(@Nonnull Node node) throws ObjectAlreadyHasException;
/**
* Sets a permission for the object
*
* @param node The node to be set
* @throws NullPointerException if the node is null
* @return the result of the operation
* @since 3.1
* @throws NullPointerException if the node is null
* @since 4.0
*/
@Nonnull
DataMutateResult setPermissionUnchecked(@Nonnull Node node);
DataMutateResult setPermission(@Nonnull Node node);
/**
* Sets a transient permission for the object
@@ -320,73 +308,34 @@ public interface PermissionHolder {
* <p>For unsetting a transient permission, see {@link #unsetTransientPermission(Node)}</p>
*
* @param node The node to be set
* @throws ObjectAlreadyHasException if the object already has the permission
* @throws NullPointerException if the node is null
* @since 2.6
*/
void setTransientPermission(@Nonnull Node node) throws ObjectAlreadyHasException;
/**
* Sets a transient permission for the object
*
* <p>A transient node is a permission that does not persist.
* Whenever a user logs out of the server, or the server restarts, this permission will disappear.
* It is never saved to the datastore, and therefore will not apply on other servers.</p>
*
* <p>This is useful if you want to temporarily set a permission for a user while they're online, but don't
* want it to persist, and have to worry about removing it when they log out.</p>
*
* <p>For unsetting a transient permission, see {@link #unsetTransientPermission(Node)}</p>
*
* @param node The node to be set
* @throws NullPointerException if the node is null
* @return the result of the operation
* @since 3.1
* @throws NullPointerException if the node is null
* @since 4.0
*/
@Nonnull
DataMutateResult setTransientPermissionUnchecked(@Nonnull Node node);
DataMutateResult setTransientPermission(@Nonnull Node node);
/**
* Unsets a permission for the object
*
* @param node The node to be unset
* @throws ObjectLacksException if the node wasn't already set
* @throws NullPointerException if the node is null
* @since 2.6
*/
void unsetPermission(@Nonnull Node node) throws ObjectLacksException;
/**
* Unsets a permission for the object
*
* @param node The node to be unset
* @throws NullPointerException if the node is null
* @return the result of the operation
* @since 3.1
* @throws NullPointerException if the node is null
* @since 4.0
*/
@Nonnull
DataMutateResult unsetPermissionUnchecked(@Nonnull Node node);
DataMutateResult unsetPermission(@Nonnull Node node);
/**
* Unsets a transient permission for the object
*
* @param node The node to be unset
* @throws ObjectLacksException if the node wasn't already set
* @throws NullPointerException if the node is null
* @since 2.6
*/
void unsetTransientPermission(@Nonnull Node node) throws ObjectLacksException;
/**
* Unsets a transient permission for the object
*
* @param node The node to be unset
* @throws NullPointerException if the node is null
* @return the result of the operation
* @since 3.1
* @throws NullPointerException if the node is null
* @since 4.0
*/
@Nonnull
DataMutateResult unsetTransientPermissionUnchecked(@Nonnull Node node);
DataMutateResult unsetTransientPermission(@Nonnull Node node);
/**
* Clears any nodes from the holder which pass the predicate
@@ -454,421 +403,64 @@ public interface PermissionHolder {
*/
void clearTransientNodes();
/**
* Checks to see if the object has a certain permission
*
* @param node The permission node
* @param b If the node is true/false(negated)
* @return true if the object has the permission
* @throws NullPointerException if the node is null
* @throws IllegalArgumentException if the node is invalid
* @deprecated in favour of {@link #hasPermission(Node)}
*/
@Deprecated
boolean hasPermission(@Nonnull String node, boolean b);
/**
* Checks to see the the object has a permission on a certain server
*
* @param node The permission node
* @param b If the node is true/false(negated)
* @param server The server
* @return true if the object has the permission
* @throws NullPointerException if the node or server is null
* @throws IllegalArgumentException if the node or server is invalid
* @deprecated in favour of {@link #hasPermission(Node)}
*/
@Deprecated
boolean hasPermission(@Nonnull String node, boolean b, @Nonnull String server);
/**
* Checks to see the the object has a permission on a certain server and world
*
* @param node The permission node
* @param b If the node is true/false(negated)
* @param server The server
* @param world The world
* @return true if the object has the permission
* @throws NullPointerException if the node, server or world is null
* @throws IllegalArgumentException if the node, server or world is invalid
* @deprecated in favour of {@link #hasPermission(Node)}
*/
@Deprecated
boolean hasPermission(@Nonnull String node, boolean b, @Nonnull String server, @Nonnull String world);
/**
* Checks to see the the object has a permission
*
* @param node The permission node
* @param b If the node is true/false(negated)
* @param temporary if the permission is temporary
* @return true if the object has the permission
* @throws NullPointerException if the node is null
* @throws IllegalArgumentException if the node is invalid
* @deprecated in favour of {@link #hasPermission(Node)}
*/
@Deprecated
boolean hasPermission(@Nonnull String node, boolean b, boolean temporary);
/**
* Checks to see the the object has a permission on a certain server
*
* @param node The permission node
* @param b If the node is true/false(negated)
* @param server The server to check on
* @param temporary if the permission is temporary
* @return true if the object has the permission
* @throws NullPointerException if the node or server is null
* @throws IllegalArgumentException if the node or server is invalid
* @deprecated in favour of {@link #hasPermission(Node)}
*/
@Deprecated
boolean hasPermission(@Nonnull String node, boolean b, @Nonnull String server, boolean temporary);
/**
* Checks to see the the object has a permission on a certain server and world
*
* @param node The permission node
* @param b If the node is true/false(negated)
* @param server The server to check on
* @param world The world to check on
* @param temporary if the permission is temporary
* @return true if the object has the permission
* @throws NullPointerException if the node, server or world is null
* @throws IllegalArgumentException if the node, server or world is invalid
* @deprecated in favour of {@link #hasPermission(Node)}
*/
@Deprecated
boolean hasPermission(@Nonnull String node, boolean b, @Nonnull String server, @Nonnull String world, boolean temporary);
/**
* Checks to see if the object inherits a certain permission
*
* @param node The permission node
* @param b If the node is true/false(negated)
* @return true if the object inherits the permission
* @throws NullPointerException if the node is null
* @throws IllegalArgumentException if the node is invalid
* @deprecated in favour of {@link #hasPermission(Node)}
*/
@Deprecated
boolean inheritsPermission(@Nonnull String node, boolean b);
/**
* Checks to see the the object inherits a permission on a certain server
*
* @param node The permission node
* @param b If the node is true/false(negated)
* @param server The server
* @return true if the object inherits the permission
* @throws NullPointerException if the node or server is null
* @throws IllegalArgumentException if the node or server is invalid
* @deprecated in favour of {@link #hasPermission(Node)}
*/
@Deprecated
boolean inheritsPermission(@Nonnull String node, boolean b, @Nonnull String server);
/**
* Checks to see the the object inherits a permission on a certain server and world
*
* @param node The permission node
* @param b If the node is true/false(negated)
* @param server The server
* @param world The world
* @return true if the object inherits the permission
* @throws NullPointerException if the node, server or world is null
* @throws IllegalArgumentException if the node server or world is invalid
* @deprecated in favour of {@link #hasPermission(Node)}
*/
@Deprecated
boolean inheritsPermission(@Nonnull String node, boolean b, @Nonnull String server, @Nonnull String world);
/**
* Checks to see if the object inherits a permission
*
* @param node The permission node
* @param b If the node is true/false(negated)
* @param temporary if the permission is temporary
* @return true if the object inherits the permission
* @throws NullPointerException if the node is null
* @throws IllegalArgumentException if the node is invalid
* @deprecated in favour of {@link #hasPermission(Node)}
*/
@Deprecated
boolean inheritsPermission(@Nonnull String node, boolean b, boolean temporary);
/**
* Checks to see if the object inherits a permission on a certain server
*
* @param node The permission node
* @param b If the node is true/false(negated)
* @param server The server
* @param temporary if the permission is temporary
* @return true if the object inherits the permission
* @throws NullPointerException if the node or server is null
* @throws IllegalArgumentException if the node or server is invalid
* @deprecated in favour of {@link #hasPermission(Node)}
*/
@Deprecated
boolean inheritsPermission(@Nonnull String node, boolean b, @Nonnull String server, boolean temporary);
/**
* Checks to see if the object inherits a permission on a certain server and world
*
* @param node The permission node
* @param b If the node is true/false(negated)
* @param server The server
* @param world The world
* @param temporary if the permission is temporary
* @return true if the object inherits the permission
* @throws NullPointerException if the node, server or world is null
* @throws IllegalArgumentException if the node, server or world if invalid
* @deprecated in favour of {@link #hasPermission(Node)}
*/
@Deprecated
boolean inheritsPermission(@Nonnull String node, boolean b, @Nonnull String server, @Nonnull String world, boolean temporary);
/**
* Sets a permission for the object
*
* @param node The node to be set
* @param value What to set the node to - true/false(negated)
* @throws ObjectAlreadyHasException if the object already has the permission
* @throws NullPointerException if the node is null
* @throws IllegalArgumentException if the node is invalid
* @deprecated in favour of {@link #setPermission(Node)}
* @param node The node to be set
* @return the result of the operation
* @throws NullPointerException if the node is null
* @since 3.1
* @deprecated now forwards to {@link #setPermission(Node)}.
*/
@Nonnull
@Deprecated
void setPermission(@Nonnull String node, boolean value) throws ObjectAlreadyHasException;
default DataMutateResult setPermissionUnchecked(@Nonnull Node node) {
return setPermission(node);
}
/**
* Sets a permission for the object on a specific server
* Sets a transient permission for the object
*
* @param node The node to set
* @param value What to set the node to - true/false(negated)
* @param server The server to set the permission on
* @throws ObjectAlreadyHasException if the object already has the permission
* @throws NullPointerException if the node or server is null
* @throws IllegalArgumentException if the node or server is invalid
* @deprecated in favour of {@link #setPermission(Node)}
* @param node The node to be set
* @return the result of the operation
* @throws NullPointerException if the node is null
* @since 3.1
* @deprecated now forwards to {@link #setTransientPermission(Node)}
*/
@Nonnull
@Deprecated
void setPermission(@Nonnull String node, boolean value, @Nonnull String server) throws ObjectAlreadyHasException;
/**
* Sets a permission for the object on a specific server and world
*
* @param node The node to set
* @param value What to set the node to - true/false(negated)
* @param server The server to set the permission on
* @param world The world to set the permission on
* @throws ObjectAlreadyHasException if the object already has the permission
* @throws NullPointerException if the node, server or world is null
* @throws IllegalArgumentException if the node, server or world is invalid
* @deprecated in favour of {@link #setPermission(Node)}
*/
@Deprecated
void setPermission(@Nonnull String node, boolean value, @Nonnull String server, @Nonnull String world) throws ObjectAlreadyHasException;
/**
* Sets a temporary permission for the object
*
* @param node The node to set
* @param value What to set the node to - true/false(negated)
* @param expireAt The time in unixtime when the permission will expire
* @throws ObjectAlreadyHasException if the object already has the permission
* @throws NullPointerException if the node is null
* @throws IllegalArgumentException if the node is invalid or if the expiry time is in the past
* @deprecated in favour of {@link #setPermission(Node)}
*/
@Deprecated
void setPermission(@Nonnull String node, boolean value, long expireAt) throws ObjectAlreadyHasException;
/**
* Sets a temporary permission for the object on a specific server
*
* @param node The node to set
* @param value What to set the node to - true/false(negated)
* @param server The server to set the permission on
* @param expireAt The time in unixtime when the permission will expire
* @throws ObjectAlreadyHasException if the object already has the permission
* @throws NullPointerException if the node or server is null
* @throws IllegalArgumentException if the node/server is invalid or if the expiry time is in the past
* @deprecated in favour of {@link #setPermission(Node)}
*/
@Deprecated
void setPermission(@Nonnull String node, boolean value, @Nonnull String server, long expireAt) throws ObjectAlreadyHasException;
/**
* Sets a temporary permission for the object on a specific server and world
*
* @param node The node to set
* @param value What to set the node to - true/false(negated)
* @param server The server to set the permission on
* @param world The world to set the permission on
* @param expireAt The time in unixtime when the permission will expire
* @throws ObjectAlreadyHasException if the object already has the permission
* @throws NullPointerException if the node, server or world is null
* @throws IllegalArgumentException if the node/server/world is invalid, or if the expiry time is in the past
* @deprecated in favour of {@link #setPermission(Node)}
*/
@Deprecated
void setPermission(String node, boolean value, @Nonnull String server, @Nonnull String world, long expireAt) throws ObjectAlreadyHasException;
/**
* Unsets a permission for the object
*
* @param node The node to be unset
* @param temporary if the permission being removed is temporary
* @throws ObjectLacksException if the node wasn't already set
* @throws NullPointerException if the node is null
* @throws IllegalArgumentException if the node is invalid
* @deprecated in favour of {@link #unsetPermission(Node)}
*/
@Deprecated
void unsetPermission(@Nonnull String node, boolean temporary) throws ObjectLacksException;
default DataMutateResult setTransientPermissionUnchecked(@Nonnull Node node) {
return setTransientPermission(node);
}
/**
* Unsets a permission for the object
*
* @param node The node to be unset
* @throws ObjectLacksException if the node wasn't already set
* @throws NullPointerException if the node is null
* @throws IllegalArgumentException if the node is invalid
* @deprecated in favour of {@link #unsetPermission(Node)}
* @throws NullPointerException if the node is null
* @return the result of the operation
* @since 3.1
* @deprecated now forwards to {@link #unsetPermission(Node)}
*/
@Nonnull
@Deprecated
void unsetPermission(@Nonnull String node) throws ObjectLacksException;
default DataMutateResult unsetPermissionUnchecked(@Nonnull Node node) {
return unsetPermission(node);
}
/**
* Unsets a permission for the object on a specific server
* Unsets a transient permission for the object
*
* @param node The node to be unset
* @param server The server to unset the node on
* @throws ObjectLacksException if the node wasn't already set
* @throws NullPointerException if the node or server is null
* @throws IllegalArgumentException if the node or server is invalid
* @deprecated in favour of {@link #unsetPermission(Node)}
* @param node The node to be unset
* @throws NullPointerException if the node is null
* @return the result of the operation
* @since 3.1
* @deprecated now forwards to {@link #unsetTransientPermission(Node)}
*/
@Nonnull
@Deprecated
void unsetPermission(@Nonnull String node, @Nonnull String server) throws ObjectLacksException;
/**
* Unsets a permission for the object on a specific server and world
*
* @param node The node to be unset
* @param server The server to unset the node on
* @param world The world to unset the node on
* @throws ObjectLacksException if the node wasn't already set
* @throws NullPointerException if the node, server or world is null
* @throws IllegalArgumentException if the node, server or world is invalid
* @deprecated in favour of {@link #unsetPermission(Node)}
*/
@Deprecated
void unsetPermission(@Nonnull String node, @Nonnull String server, @Nonnull String world) throws ObjectLacksException;
/**
* Unsets a permission for the object on a specific server
*
* @param node The node to be unset
* @param server The server to unset the node on
* @param temporary if the permission being unset is temporary
* @throws ObjectLacksException if the node wasn't already set
* @throws NullPointerException if the node or server is null
* @throws IllegalArgumentException if the node or server is invalid
* @deprecated in favour of {@link #unsetPermission(Node)}
*/
@Deprecated
void unsetPermission(@Nonnull String node, @Nonnull String server, boolean temporary) throws ObjectLacksException;
/**
* Unsets a permission for the object on a specific server and world
*
* @param node The node to be unset
* @param server The server to unset the node on
* @param world The world to unset the node on
* @param temporary if the permission being unset is temporary
* @throws ObjectLacksException if the node wasn't already set
* @throws NullPointerException if the node, server or world is null
* @throws IllegalArgumentException if the node, server or world is invalid
* @deprecated in favour of {@link #unsetPermission(Node)}
*/
@Deprecated
void unsetPermission(@Nonnull String node, @Nonnull String server, @Nonnull String world, boolean temporary) throws ObjectLacksException;
/**
* Clears all nodes held by the object on a specific server
*
* @param server the server to filter by, can be null
* @since 2.17
* @deprecated in favour of {@link #clearNodes(ContextSet)}
*/
@Deprecated
void clearNodes(@Nullable String server);
/**
* Clears all nodes held by the object on a specific server and world
*
* @param server the server to filter by, can be null
* @param world the world to filter by, can be null
* @since 2.17
* @deprecated in favour of {@link #clearNodes(ContextSet)}
*/
@Deprecated
void clearNodes(@Nullable String server, @Nullable String world);
/**
* Clears all parents on a specific server
*
* @param server the server to filter by, can be null
* @since 2.17
* @deprecated in favour of {@link #clearParents(ContextSet)}
*/
@Deprecated
void clearParents(@Nullable String server);
/**
* Clears all parents on a specific server and world
*
* @param server the server to filter by, can be null
* @param world the world to filter by, can be null
* @since 2.17
* @deprecated in favour of {@link #clearParents(ContextSet)}
*/
@Deprecated
void clearParents(@Nullable String server, @Nullable String world);
/**
* Clears all meta held by the object on a specific server
*
* @param server the server to filter by, can be null
* @since 2.17
* @deprecated in favour of {@link #clearMeta(ContextSet)}
*/
@Deprecated
void clearMeta(@Nullable String server);
/**
* Clears all meta held by the object on a specific server and world
*
* @param server the server to filter by, can be null
* @param world the world to filter by, can be null
* @since 2.17
* @deprecated in favour of {@link #clearMeta(ContextSet)}
*/
@Deprecated
void clearMeta(@Nullable String server, @Nullable String world);
/**
* Clears all meta for a given key.
*
* @param key the meta key
* @param server the server to filter by, can be null
* @param world the world to filter by, can be null
* @param temporary whether the query is for temporary nodes or not.
* @deprecated in favour of {@link #clearMatching(Predicate)}
*/
@Deprecated
void clearMetaKeys(@Nonnull String key, @Nullable String server, @Nullable String world, boolean temporary);
default DataMutateResult unsetTransientPermissionUnchecked(@Nonnull Node node) {
return unsetTransientPermission(node);
}
}
@@ -36,14 +36,14 @@ import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* A means of loading and saving data to/from the Storage provider.
* A means of loading and saving permission data to/from the backend.
*
* <p>All methods return {@link CompletableFuture}s, which will be populated with the result once the data has been
* loaded asynchronously. Care should be taken when using the methods to ensure that the main server thread is not
* <p>All blocking methods return {@link CompletableFuture}s, which will be populated with the result once the data has been
* loaded/saved asynchronously. Care should be taken when using such methods to ensure that the main server thread is not
* blocked.</p>
*
* <p>Methods such as {@link CompletableFuture#get()} and equivalent should <strong>not</strong> be called on the main
* server thread. If you need to use the result of these operations on the main server thread, please register a
* server thread. If you need to use the result of these operations on the main server thread, register a
* callback using {@link CompletableFuture#thenAcceptAsync(Consumer, Executor)} and {@link #getSyncExecutor()}.</p>
*
* @since 2.14
@@ -59,7 +59,7 @@ public interface Storage {
String getName();
/**
* Return whether the storage instance is allowing logins on the platform.
* Gets whether the storage instance is allowing logins on the platform.
*
* @return true if logins are enabled
*/
@@ -134,17 +134,10 @@ public interface Storage {
@Nonnull
CompletableFuture<Boolean> saveUser(@Nonnull User user);
/**
* Removes users from the main storage who are "default". This is called every time the plugin loads.
*
* @return true if the operation completed successfully
*/
@Nonnull
CompletableFuture<Boolean> cleanupUsers();
/**
* Gets a set all "unique" user UUIDs.
* "Unique" meaning the user isn't just a member of the "default" group.
*
* <p>"Unique" meaning the user isn't just a member of the "default" group.</p>
*
* @return a set of uuids, or null if the operation failed.
*/
@@ -25,30 +25,30 @@
package me.lucko.luckperms.api;
import me.lucko.luckperms.exceptions.ObjectAlreadyHasException;
import me.lucko.luckperms.exceptions.ObjectLacksException;
import java.util.List;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* An ordered collection of groups for easy promotions and demotions
* An ordered chain of {@link Group}s.
*/
public interface Track {
/**
* Gets the name of this track
*
* @return the name of this track
*/
@Nonnull
String getName();
/**
* Gets an ordered list of the groups on this track
* Gets a list of the groups on this track
*
* <p>Index 0 is the first/lowest group in (or start of) the track</p>
* <p>Index 0 is the first/lowest group in (or start of) the track.</p>
*
* <p>The returned collection is immutable, and cannot be modified.</p>
*
* @return an ordered {@link List} of the groups on this track
*/
@@ -65,67 +65,69 @@ public interface Track {
/**
* Gets the next group on the track, after the one provided
*
* <p>{@code null} is returned if the group is not on the track.</p>
*
* @param current the group before the group being requested
* @return the group name, or null if the end of the track has been reached
* @throws ObjectLacksException if the track does not contain the group given
* @throws NullPointerException if the group is null
* @throws IllegalStateException if the group instance was not obtained from LuckPerms.
*/
@Nullable
String getNext(@Nonnull Group current) throws ObjectLacksException;
String getNext(@Nonnull Group current);
/**
* Gets the previous group on the track, before the one provided
*
* <p>{@code null} is returned if the group is not on the track.</p>
*
* @param current the group after the group being requested
* @return the group name, or null if the start of the track has been reached
* @throws ObjectLacksException if the track does not contain the group given
* @throws NullPointerException if the group is null
* @throws IllegalStateException if the group instance was not obtained from LuckPerms.
*/
@Nullable
String getPrevious(@Nonnull Group current) throws ObjectLacksException;
String getPrevious(@Nonnull Group current);
/**
* Appends a group to the end of this track
*
* @param group the group to append
* @throws ObjectAlreadyHasException if the group is already on this track somewhere
* @return the result of the operation
* @throws NullPointerException if the group is null
* @throws IllegalStateException if the group instance was not obtained from LuckPerms.
*/
void appendGroup(@Nonnull Group group) throws ObjectAlreadyHasException;
DataMutateResult appendGroup(@Nonnull Group group);
/**
* Inserts a group at a certain position on this track
*
* @param group the group to be inserted
* @param position the index position (a value of 0 inserts at the start)
* @throws ObjectAlreadyHasException if the group is already on this track somewhere
* @return the result of the operation
* @throws IndexOutOfBoundsException if the position is less than 0 or greater than the size of the track
* @throws NullPointerException if the group is null
* @throws IllegalStateException if the group instance was not obtained from LuckPerms.
*/
void insertGroup(@Nonnull Group group, int position) throws ObjectAlreadyHasException, IndexOutOfBoundsException;
DataMutateResult insertGroup(@Nonnull Group group, int position) throws IndexOutOfBoundsException;
/**
* Removes a group from this track
*
* @param group the group to remove
* @throws ObjectLacksException if the group is not on this track
* @return the result of the operation
* @throws NullPointerException if the group is null
* @throws IllegalStateException if the group instance was not obtained from LuckPerms.
*/
void removeGroup(@Nonnull Group group) throws ObjectLacksException;
DataMutateResult removeGroup(@Nonnull Group group);
/**
* Removes a group from this track
*
* @param group the group to remove
* @throws ObjectLacksException if the group is not on this track
* @return the result of the operation
* @throws NullPointerException if the group is null
*/
void removeGroup(@Nonnull String group) throws ObjectLacksException;
DataMutateResult removeGroup(@Nonnull String group);
/**
* Checks if a group features on this track
@@ -28,37 +28,42 @@ package me.lucko.luckperms.api;
import javax.annotation.Nonnull;
/**
* Represents a permission setting.
* Represents three different states of a setting.
*
* <p>Consider a value of {@link #TRUE} to be a positive setting, {@link #FALSE} to be a "negated" setting,
* and a value of {@link #UNDEFINED} to be a non-existent value.</p>
* <p>Possible values:</p>
* <p></p>
* <ul>
* <li>{@link #TRUE} - a positive setting</li>
* <li>{@link #FALSE} - a negative (negated) setting</li>
* <li>{@link #UNDEFINED} - a non-existent setting</li>
* </ul>
*/
public enum Tristate {
/**
* A value indicating a holder has a permission set.
* A value indicating a positive setting
*/
TRUE(true),
/**
* A value indicating a holder has a negated value for a permission.
* A value indicating a negative (negated) setting
*/
FALSE(false),
/**
* A value indicating a holder doesn't have a value for a permission set.
* A value indicating a non-existent setting
*/
UNDEFINED(false);
/**
* Converts from {@link Boolean} a boolean
* Returns a {@link Tristate} from a boolean
*
* @param b the boolean
* @param val the boolean value
* @return {@link #TRUE} or {@link #FALSE}, depending on the value of the boolean.
*/
@Nonnull
public static Tristate fromBoolean(boolean b) {
return b ? TRUE : FALSE;
public static Tristate fromBoolean(boolean val) {
return val ? TRUE : FALSE;
}
private final boolean booleanValue;
@@ -27,18 +27,15 @@ package me.lucko.luckperms.api;
import me.lucko.luckperms.api.caching.UserData;
import me.lucko.luckperms.api.context.ContextSet;
import me.lucko.luckperms.exceptions.ObjectAlreadyHasException;
import me.lucko.luckperms.exceptions.ObjectLacksException;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* A player holding permission data
* A player which holds permission data.
*/
public interface User extends PermissionHolder {
@@ -51,7 +48,9 @@ public interface User extends PermissionHolder {
UUID getUuid();
/**
* Gets the users username, or null if no username is associated with this user
* Gets the users username
*
* <p>Returns null if no username is associated with this user.</p>
*
* @return the users username
*/
@@ -73,19 +72,11 @@ public interface User extends PermissionHolder {
* Sets a users primary group. This will only take effect if platform is using stored primary groups.
*
* @param group the new primary group
* @throws ObjectAlreadyHasException if the user already has this set as their primary group
* @return if the change was applied successfully
* @throws IllegalStateException if the user is not a member of that group
* @throws NullPointerException if the group is null
*/
void setPrimaryGroup(@Nonnull String group) throws ObjectAlreadyHasException;
/**
* Refresh and re-assign the users permissions.
*
* <p>This request is not buffered, and the refresh call will be ran directly. This should be called on an
* asynchronous thread.</p>
*/
void refreshPermissions();
DataMutateResult setPrimaryGroup(@Nonnull String group);
/**
* Gets the user's {@link UserData} cache.
@@ -96,6 +87,15 @@ public interface User extends PermissionHolder {
@Nonnull
UserData getCachedData();
/**
* Refreshes and applies any changes to the cached user data.
*
* @return the task future
* @since 4.0
*/
@Nonnull
CompletableFuture<Void> refreshCachedData();
/**
* Check to see if the user is a direct member of a group
*
@@ -118,15 +118,15 @@ public interface User extends PermissionHolder {
boolean isInGroup(@Nonnull Group group, @Nonnull ContextSet contextSet);
/**
* Gets the user's {@link UserData} cache, if they have one setup.
* Refresh and re-assign the users permissions.
*
* @return an optional, possibly containing the user's cached lookup data.
* @since 2.13
* @deprecated in version 3.2, as this cache is now always loaded
* <p>This request is not buffered, and the refresh call will be ran directly. This should be called on an
* asynchronous thread.</p>
*
* @deprecated in favour of {@link #refreshCachedData()}.
*/
@Deprecated
@Nonnull
Optional<UserData> getUserDataCache();
void refreshPermissions();
/**
* Pre-calculates some values in the user's data cache.
@@ -135,242 +135,9 @@ public interface User extends PermissionHolder {
* using {@link #getCachedData()}.</p>
*
* @since 2.17
* @deprecated because use of this method is no longer necessary.
*/
@Deprecated
void setupDataCache();
/**
* Check to see if a user is a member of a group on a specific server
*
* @param group The group to check membership of
* @param server The server to check on
* @return true if the user is a member of the group
* @throws NullPointerException if the group or server is null
* @throws IllegalArgumentException if the server is invalid
* @deprecated in favour of {@link #isInGroup(Group, ContextSet)}
*/
@Deprecated
boolean isInGroup(@Nonnull Group group, @Nonnull String server);
/**
* Check to see if a user is a member of a group on a specific server and world
*
* @param group The group to check membership of
* @param server The server to check on
* @param world The world to check on
* @return true if the user is a member of the group
* @throws NullPointerException if the group, server or world is null
* @throws IllegalArgumentException if the server or world is invalid
* @deprecated in favour of {@link #isInGroup(Group, ContextSet)}
*/
@Deprecated
boolean isInGroup(@Nonnull Group group, @Nonnull String server, @Nonnull String world);
/**
* Add a user to a group
*
* @param group The group to add the user to
* @throws ObjectAlreadyHasException if the user is already a member of the group
* @throws NullPointerException if the group is null
* @throws IllegalStateException if the group instance was not obtained from LuckPerms.
* @deprecated in favour of {@link NodeFactory#makeGroupNode(Group)}
*/
@Deprecated
void addGroup(@Nonnull Group group) throws ObjectAlreadyHasException;
/**
* Add a user to a group on a specific server
*
* @param group The group to add the user to
* @param server The server to add the group on
* @throws ObjectAlreadyHasException if the user is already a member of the group on that server
* @throws NullPointerException if the group or server is null
* @throws IllegalStateException if the group instance was not obtained from LuckPerms.
* @throws IllegalArgumentException if the server is invalid
* @deprecated in favour of {@link NodeFactory#makeGroupNode(Group)}
*/
@Deprecated
void addGroup(@Nonnull Group group, @Nonnull String server) throws ObjectAlreadyHasException;
/**
* Add a user to a group on a specific server and world
*
* @param group The group to add the user to
* @param server The server to add the group on
* @param world The world to add the group on
* @throws ObjectAlreadyHasException if the user is already a member of the group on that server
* @throws NullPointerException if the group, server or world is null
* @throws IllegalStateException if the group instance was not obtained from LuckPerms.
* @throws IllegalArgumentException if the server or world is invalid
* @deprecated in favour of {@link NodeFactory#makeGroupNode(Group)}
*/
@Deprecated
void addGroup(@Nonnull Group group, @Nonnull String server, @Nonnull String world) throws ObjectAlreadyHasException;
/**
* Add a user to a group temporarily on a specific server
*
* @param group The group to add the user to
* @param expireAt when the group should expire
* @throws ObjectAlreadyHasException if the user is already a member of the group on that server
* @throws NullPointerException if the group is null
* @throws IllegalStateException if the group instance was not obtained from LuckPerms.
* @throws IllegalArgumentException if the expiry time is in the past
* @deprecated in favour of {@link NodeFactory#makeGroupNode(Group)}
*/
@Deprecated
void addGroup(@Nonnull Group group, long expireAt) throws ObjectAlreadyHasException;
/**
* Add a user to a group temporarily on a specific server
*
* @param group The group to add the user to
* @param server The server to add the group on
* @param expireAt when the group should expire
* @throws ObjectAlreadyHasException if the user is already a member of the group on that server
* @throws NullPointerException if the group or server is null
* @throws IllegalStateException if the group instance was not obtained from LuckPerms.
* @throws IllegalArgumentException if the expiry time is in the past or the server is invalid
* @deprecated in favour of {@link NodeFactory#makeGroupNode(Group)}
*/
@Deprecated
void addGroup(@Nonnull Group group, @Nonnull String server, long expireAt) throws ObjectAlreadyHasException;
/**
* Add a user to a group temporarily on a specific server and world
*
* @param group The group to add the user to
* @param server The server to add the group on
* @param world The world to add the group on
* @param expireAt when the group should expire
* @throws ObjectAlreadyHasException if the user is already a member of the group on that server
* @throws NullPointerException if the group, server or world is null
* @throws IllegalStateException if the group instance was not obtained from LuckPerms.
* @throws IllegalArgumentException if the expiry time is in the past or the server/world is invalid
* @deprecated in favour of {@link NodeFactory#makeGroupNode(Group)}
*/
@Deprecated
void addGroup(@Nonnull Group group, @Nonnull String server, @Nonnull String world, long expireAt) throws ObjectAlreadyHasException;
/**
* Remove the user from a group
*
* @param group the group to remove the user from
* @throws ObjectLacksException if the user isn't a member of the group
* @throws NullPointerException if the group is null
* @throws IllegalStateException if the group instance was not obtained from LuckPerms.
* @deprecated in favour of {@link NodeFactory#makeGroupNode(Group)}
*/
@Deprecated
void removeGroup(@Nonnull Group group) throws ObjectLacksException;
/**
* Remove the user from a group
*
* @param group the group to remove the user from
* @param temporary if the group being removed is temporary
* @throws ObjectLacksException if the user isn't a member of the group
* @throws NullPointerException if the group is null
* @throws IllegalStateException if the group instance was not obtained from LuckPerms.
* @deprecated in favour of {@link NodeFactory#makeGroupNode(Group)}
*/
@Deprecated
void removeGroup(@Nonnull Group group, boolean temporary) throws ObjectLacksException;
/**
* Remove the user from a group on a specific server
*
* @param group The group to remove the user from
* @param server The server to remove the group on
* @throws ObjectLacksException if the user isn't a member of the group
* @throws NullPointerException if the group or server is null
* @throws IllegalStateException if the group instance was not obtained from LuckPerms.
* @throws IllegalArgumentException if the server is invalid
* @deprecated in favour of {@link NodeFactory#makeGroupNode(Group)}
*/
@Deprecated
void removeGroup(@Nonnull Group group, @Nonnull String server) throws ObjectLacksException;
/**
* Remove the user from a group on a specific server and world
*
* @param group The group to remove the user from
* @param server The server to remove the group on
* @param world The world to remove the group on
* @throws ObjectLacksException if the user isn't a member of the group
* @throws NullPointerException if the group, server or world is null
* @throws IllegalStateException if the group instance was not obtained from LuckPerms.
* @throws IllegalArgumentException if the server or world is invalid
* @deprecated in favour of {@link NodeFactory#makeGroupNode(Group)}
*/
@Deprecated
void removeGroup(@Nonnull Group group, @Nonnull String server, @Nonnull String world) throws ObjectLacksException;
/**
* Remove the user from a group on a specific server
*
* @param group The group to remove the user from
* @param server The server to remove the group on
* @param temporary if the group being removed is temporary
* @throws ObjectLacksException if the user isn't a member of the group
* @throws NullPointerException if the group or server is null
* @throws IllegalStateException if the group instance was not obtained from LuckPerms.
* @throws IllegalArgumentException if the expiry time is in the past or the server is invalid
* @deprecated in favour of {@link NodeFactory#makeGroupNode(Group)}
*/
@Deprecated
void removeGroup(@Nonnull Group group, @Nonnull String server, boolean temporary) throws ObjectLacksException;
/**
* Remove the user from a group on a specific server and world
*
* @param group The group to remove the user from
* @param server The server to remove the group on
* @param world The world to remove the group on
* @param temporary if the group being removed is temporary
* @throws ObjectLacksException if the user isn't a member of the group
* @throws NullPointerException if the group, server or world is null
* @throws IllegalStateException if the group instance was not obtained from LuckPerms.
* @throws IllegalArgumentException if the expiry time is in the past or the server/world is invalid
* @deprecated in favour of {@link NodeFactory#makeGroupNode(Group)}
*/
@Deprecated
void removeGroup(@Nonnull Group group, @Nonnull String server, @Nonnull String world, boolean temporary) throws ObjectLacksException;
/**
* Get a {@link List} of all of the groups the user is a member of, on all servers
*
* @return a {@link List} of group names
* @deprecated in favour of just querying a users permissions
*/
@Deprecated
@Nonnull
List<String> getGroupNames();
/**
* Get a {@link List} of the groups the user is a member of on a specific server
*
* @param server the server to check
* @param world the world to check
* @return a {@link List} of group names
* @throws NullPointerException if the server or world is null
* @throws IllegalArgumentException if the server or world is invalid
* @deprecated in favour of just querying a users permissions
*/
@Deprecated
@Nonnull
List<String> getLocalGroups(@Nonnull String server, @Nonnull String world);
/**
* Get a {@link List} of the groups the user is a member of on a specific server
*
* @param server the server to check
* @return a {@link List} of group names
* @throws NullPointerException if the server is null
* @throws IllegalArgumentException if the server is invalid
* @deprecated in favour of just querying a users permissions
*/
@Deprecated
@Nonnull
List<String> getLocalGroups(@Nonnull String server);
}
@@ -32,34 +32,34 @@ import javax.annotation.Nonnull;
/**
* A UUID cache for online users, between external Mojang UUIDs, and internal LuckPerms UUIDs.
*
* <p>This UuidCache is a means of allowing users to have the same internal UUID across a network of offline mode
* servers or mixed offline mode and online mode servers. Platforms running in offline mode generate a UUID for a
* user when they first join the server, but this UUID will then not be consistent across the network. LuckPerms will
* instead check the datastore cache, to get a UUID for a user that is consistent across an entire network.</p>
* <p>A user's internal LuckPerms UUID is always the same as their Mojang one,
* unless <code>use-server-uuids</code> is disabled.</p>
*
* <p>If you want to get a user object from the Storage using the api on a server in offline mode, you will need to use
* this cache, OR use Storage#getUUID, for users that are not online.</p>
* <p>When this setting is disabled, this cache becomes active, and allows you to convert
* between 'internal' and 'server provided' uuids.</p>
*
* <p><strong>This is only effective for online players. Use {@link Storage#getUUID(String)} for offline players.</strong></p>
*/
public interface UuidCache {
/**
* Gets a users internal "LuckPerms" UUID, from the one given by the server.
* Gets a users "internal" LuckPerms UUID, from the one given by the server.
*
* @param external the UUID assigned by the server, through Player#getUniqueId or ProxiedPlayer#getUniqueId
* <p>When <code>use-server-uuids</code> is true, this returns the same UUID instance.</p>
*
* @param mojangUuid the UUID assigned by the server, through <code>Player#getUniqueId</code> or <code>ProxiedPlayer#getUniqueId</code>
* @return the corresponding internal UUID
*/
@Nonnull
UUID getUUID(@Nonnull UUID external);
UUID getUUID(@Nonnull UUID mojangUuid);
/**
* Gets a users external, server assigned or Mojang assigned unique id, from the internal one used within LuckPerms.
* Gets a users "external", server assigned unique id, from the internal one used within LuckPerms.
*
* @param internal the UUID used within LuckPerms, through User#getUuid
* @param internalUuid the UUID used within LuckPerms, through <code>User#getUuid</code>
* @return the corresponding external UUID
*/
@Nonnull
UUID getExternalUUID(@Nonnull UUID internal);
UUID getExternalUUID(@Nonnull UUID internalUuid);
}
@@ -33,8 +33,10 @@ import me.lucko.luckperms.api.metastacking.MetaStackDefinition;
import javax.annotation.Nonnull;
/**
* Represents the context for a meta lookup, consisting of a standard {@link Contexts} element, plus options to define how
* the meta stack should be constructed.
* Represents the context for a meta lookup.
*
* <p>Consisting of a standard {@link Contexts} element, plus options to define how
* the meta stack should be constructed.</p>
*
* @since 3.2
*/
@@ -56,6 +58,9 @@ public final class MetaContexts {
private final MetaStackDefinition prefixStackDefinition;
private final MetaStackDefinition suffixStackDefinition;
// cache hashcode - this class is immutable, and is used as an index in the permission cache.
private final int hashCode;
/**
* Creates a new meta contexts instance
*
@@ -67,6 +72,7 @@ public final class MetaContexts {
this.contexts = Preconditions.checkNotNull(contexts, "contexts");
this.prefixStackDefinition = Preconditions.checkNotNull(prefixStackDefinition, "prefixStackDefinition");
this.suffixStackDefinition = Preconditions.checkNotNull(suffixStackDefinition, "suffixStackDefinition");
this.hashCode = calculateHashCode();
}
@Nonnull
@@ -104,8 +110,7 @@ public final class MetaContexts {
this.getSuffixStackDefinition().equals(other.getSuffixStackDefinition());
}
@Override
public int hashCode() {
private int calculateHashCode() {
final int PRIME = 59;
int result = 1;
result = result * PRIME + this.getContexts().hashCode();
@@ -113,4 +118,9 @@ public final class MetaContexts {
result = result * PRIME + this.getSuffixStackDefinition().hashCode();
return result;
}
@Override
public int hashCode() {
return hashCode;
}
}
@@ -33,6 +33,7 @@ import javax.annotation.Nonnull;
* @param <T> the subject type. Is ALWAYS the player class of the platform.
* @since 2.13
*/
@FunctionalInterface
public interface ContextCalculator<T> {
/**
@@ -0,0 +1,172 @@
/*
* 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.api.context;
import me.lucko.luckperms.api.Contexts;
import me.lucko.luckperms.api.User;
import java.util.Optional;
import javax.annotation.Nonnull;
/**
* 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
* on specific server implementations. In all cases, the "player" or "subject" type for
* the platform must be used.
*
* Specifically, {@code org.bukkit.entity.Player},
* {@code net.md_5.bungee.api.connection.ProxiedPlayer} and
* {@code org.spongepowered.api.service.permission.Subject}.
*
* @since 4.0
*/
public interface ContextManager {
/**
* Queries the ContextManager for current context values for the subject.
*
* @param subject the subject
* @return the applicable context for the subject
*/
@Nonnull
ImmutableContextSet getApplicableContext(@Nonnull Object subject);
/**
* Queries the ContextManager for current context values for the subject.
*
* @param subject the subject
* @return the applicable context for the subject
*/
@Nonnull
Contexts getApplicableContexts(@Nonnull Object subject);
/**
* Queries the ContextManager for current context values for the given User.
*
* <p>This will only return a value if the player corresponding to the
* {@link User} is online.</p>
*
* <p>If you need to be a {@link Contexts} instance regardless, you should
* initially try this method, and then fallback on {@link #getStaticContext()}.</p>
*
* @param user the user
* @return the applicable context for the subject
*/
@Nonnull
Optional<ImmutableContextSet> lookupApplicableContext(@Nonnull User user);
/**
* Queries the ContextManager for current context values for the given User.
*
* <p>This will only return a value if the player corresponding to the
* {@link User} is online.</p>
*
* <p>If you need to be a {@link Contexts} instance regardless, you should
* initially try this method, and then fallback on {@link #getStaticContexts()}.</p>
*
* @param user the user
* @return the applicable context for the subject
*/
@Nonnull
Optional<Contexts> lookupApplicableContexts(@Nonnull User user);
/**
* Gets the contexts from the static calculators in this manager.
*
* <p>Static calculators provide the same context for all subjects, and are
* marked as such when registered.</p>
*
* @return the current active static contexts
*/
@Nonnull
ImmutableContextSet getStaticContext();
/**
* Gets the contexts from the static calculators in this manager.
*
* <p>Static calculators provide the same context for all subjects, and are
* marked as such when registered.</p>
*
* @return the current active static contexts
*/
@Nonnull
Contexts getStaticContexts();
/**
* Forms a {@link Contexts} instance from an {@link ImmutableContextSet}.
*
* <p>This method relies on the plugins configuration to form the
* {@link Contexts} instance.</p>
*
* @param subject the reference subject
* @param contextSet the context set
* @return a contexts instance
*/
@Nonnull
Contexts formContexts(@Nonnull Object subject, @Nonnull ImmutableContextSet contextSet);
/**
* Forms a {@link Contexts} instance from an {@link ImmutableContextSet}.
*
* <p>This method relies on the plugins configuration to form the
* {@link Contexts} instance.</p>
*
* @param contextSet the context set
* @return a contexts instance
*/
@Nonnull
Contexts formContexts(@Nonnull ImmutableContextSet contextSet);
/**
* Registers a context calculator with the manager.
*
* @param calculator the calculator
*/
@Nonnull
void registerCalculator(@Nonnull ContextCalculator<?> calculator);
/**
* Registers a static context calculator with the manager.
*
* <p>Static calculators provide the same context for all subjects.</p>
*
* @param calculator the calculator
*/
@Nonnull
void registerStaticCalculator(@Nonnull StaticContextCalculator calculator);
/**
* Invalidates the lookup cache for a given subject
*
* @param subject the subject
*/
@Nonnull
void invalidateCache(@Nonnull Object subject);
}
@@ -35,7 +35,13 @@ import java.util.Set;
import javax.annotation.Nonnull;
/**
* Holder of contexts.
* A set of context pairs.
*
* <p>You can think of ContextSets as a wrapped <code>Multimap&lt;String, String&gt;</code>.
* Each key can be mapped to multiple values.</p>
*
* <p>Keys are automatically converted to lowercase when added, and are therefore
* case-insensitive. Values however are not.</p>
*
* <p>Implementations may be either mutable or immutable.</p>
*
@@ -72,18 +78,6 @@ public interface ContextSet {
return ImmutableContextSet.of(key1, value1, key2, value2);
}
/**
* Creates an ImmutableContextSet from an existing map
*
* @param map the map to copy from
* @return a new ImmutableContextSet representing the pairs from the map
* @throws NullPointerException if the map is null
*/
@Nonnull
static ImmutableContextSet fromMap(@Nonnull Map<String, String> map) {
return ImmutableContextSet.fromMap(map);
}
/**
* Creates an ImmutableContextSet from an existing iterable of Map Entries
*
@@ -96,6 +90,18 @@ public interface ContextSet {
return ImmutableContextSet.fromEntries(iterable);
}
/**
* Creates an ImmutableContextSet from an existing map
*
* @param map the map to copy from
* @return a new ImmutableContextSet representing the pairs from the map
* @throws NullPointerException if the map is null
*/
@Nonnull
static ImmutableContextSet fromMap(@Nonnull Map<String, String> map) {
return ImmutableContextSet.fromMap(map);
}
/**
* Creates an ImmutableContextSet from an existing multimap
*
@@ -133,7 +139,7 @@ public interface ContextSet {
}
/**
* Check to see if this set is in an immutable form
* Gets if this set is in an immutable form
*
* @return true if the set is immutable
*/
@@ -167,12 +173,14 @@ public interface ContextSet {
/**
* Converts this ContextSet to an immutable {@link Map}
*
* <b>NOTE: Use of this method may result in data being lost. ContextSets can contain lots of different values for
* <b>IMPORTANT: Use of this method may result in data being lost. ContextSets can contain lots of different values for
* one key.</b>
*
* @return an immutable map
* @deprecated because the resultant map may not contain all data in the ContextSet
*/
@Nonnull
@Deprecated
Map<String, String> toMap();
/**
@@ -225,6 +233,19 @@ public interface ContextSet {
*/
boolean has(@Nonnull String key, @Nonnull String value);
/**
* Check if thr set contains a given key mapped to a given value
*
* @param entry the entry to look for
* @return true if the set contains the KV pair
* @throws NullPointerException if the key or value is null
* @since 3.4
*/
default boolean has(@Nonnull Map.Entry<String, String> entry) {
Preconditions.checkNotNull(entry, "entry");
return has(entry.getKey(), entry.getValue());
}
/**
* Same as {@link #has(String, String)}, except ignores the case of the value.
*
@@ -235,6 +256,19 @@ public interface ContextSet {
*/
boolean hasIgnoreCase(@Nonnull String key, @Nonnull String value);
/**
* Same as {@link #has(Map.Entry)}, except ignores the case of the value.
*
* @param entry the entry to look for
* @return true if the set contains the KV pair
* @throws NullPointerException if the key or value is null
* @since 3.4
*/
default boolean hasIgnoreCase(@Nonnull Map.Entry<String, String> entry) {
Preconditions.checkNotNull(entry, "entry");
return hasIgnoreCase(entry.getKey(), entry.getValue());
}
/**
* Checks to see if all entries in this context set are also included in another set.
*
@@ -243,6 +277,18 @@ public interface ContextSet {
* @since 3.1
*/
default boolean isSatisfiedBy(@Nonnull ContextSet other) {
return isSatisfiedBy(other, true);
}
/**
* Checks to see if all entries in this context set are also included in another set.
*
* @param other the other set to check
* @param caseSensitive if the lookup should be case sensitive. see {@link #has(Map.Entry)} and {@link #hasIgnoreCase(Map.Entry)}.
* @return true if all entries in this set are also in the other set
* @since 3.4
*/
default boolean isSatisfiedBy(@Nonnull ContextSet other, boolean caseSensitive) {
Preconditions.checkNotNull(other, "other");
if (this.isEmpty()) {
// this is empty, so is therefore always satisfied.
@@ -256,8 +302,14 @@ public interface ContextSet {
} else {
// neither are empty, we need to compare the individual entries
for (Map.Entry<String, String> pair : toSet()) {
if (!other.has(pair.getKey(), pair.getValue())) {
return false;
if (caseSensitive) {
if (!other.has(pair)) {
return false;
}
} else {
if (!other.hasIgnoreCase(pair)) {
return false;
}
}
}
@@ -25,12 +25,10 @@
package me.lucko.luckperms.api.context;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.collect.Multimap;
import com.google.common.collect.SetMultimap;
import java.util.Collection;
import java.util.Map;
@@ -38,9 +36,14 @@ import java.util.Set;
import javax.annotation.Nonnull;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* An immutable implementation of {@link ContextSet}.
*
* <p>On construction, all keys/values are {@link String#intern()}ed, in order to increase
* comparison speed.</p>
*
* @since 2.16
*/
public final class ImmutableContextSet implements ContextSet {
@@ -56,9 +59,10 @@ public final class ImmutableContextSet implements ContextSet {
*/
@Nonnull
public static ImmutableContextSet singleton(@Nonnull String key, @Nonnull String value) {
Preconditions.checkNotNull(key, "key");
Preconditions.checkNotNull(value, "value");
return new ImmutableContextSet(ImmutableSetMultimap.of(key.toLowerCase(), value));
return new ImmutableContextSet(ImmutableSetMultimap.of(
checkNotNull(key, "key").toLowerCase().intern(),
checkNotNull(value, "value").intern()
));
}
/**
@@ -74,30 +78,12 @@ public final class ImmutableContextSet implements ContextSet {
*/
@Nonnull
public static ImmutableContextSet of(@Nonnull String key1, @Nonnull String value1, @Nonnull String key2, @Nonnull String value2) {
Preconditions.checkNotNull(key1, "key1");
Preconditions.checkNotNull(value1, "value1");
Preconditions.checkNotNull(key2, "key2");
Preconditions.checkNotNull(value2, "value2");
return new ImmutableContextSet(ImmutableSetMultimap.of(key1.toLowerCase(), value1, key2.toLowerCase(), value2));
}
/**
* Creates an ImmutableContextSet from an existing map
*
* @param map the map to copy from
* @return a new ImmutableContextSet representing the pairs from the map
* @throws NullPointerException if the map is null
*/
@Nonnull
public static ImmutableContextSet fromMap(@Nonnull Map<String, String> map) {
Preconditions.checkNotNull(map, "map");
ImmutableSetMultimap.Builder<String, String> b = ImmutableSetMultimap.builder();
for (Map.Entry<String, String> e : map.entrySet()) {
b.put(e.getKey().toLowerCase(), e.getValue());
}
return new ImmutableContextSet(b.build());
return new ImmutableContextSet(ImmutableSetMultimap.of(
checkNotNull(key1, "key1").toLowerCase().intern(),
checkNotNull(value1, "value1").intern(),
checkNotNull(key2, "key2").toLowerCase().intern(),
checkNotNull(value2, "value2").intern()
));
}
/**
@@ -109,8 +95,26 @@ public final class ImmutableContextSet implements ContextSet {
*/
@Nonnull
public static ImmutableContextSet fromEntries(@Nonnull Iterable<? extends Map.Entry<String, String>> iterable) {
Preconditions.checkNotNull(iterable, "iterable");
return MutableContextSet.fromEntries(iterable).makeImmutable();
checkNotNull(iterable, "iterable");
ImmutableSetMultimap.Builder<String, String> b = ImmutableSetMultimap.builder();
for (Map.Entry<String, String> e : iterable) {
b.put(checkNotNull(e.getKey()).toLowerCase().intern(), checkNotNull(e.getValue()).intern());
}
return new ImmutableContextSet(b.build());
}
/**
* Creates an ImmutableContextSet from an existing map
*
* @param map the map to copy from
* @return a new ImmutableContextSet representing the pairs from the map
* @throws NullPointerException if the map is null
*/
@Nonnull
public static ImmutableContextSet fromMap(@Nonnull Map<String, String> map) {
return fromEntries(checkNotNull(map, "map").entrySet());
}
/**
@@ -122,8 +126,7 @@ public final class ImmutableContextSet implements ContextSet {
*/
@Nonnull
public static ImmutableContextSet fromMultimap(@Nonnull Multimap<String, String> multimap) {
Preconditions.checkNotNull(multimap, "multimap");
return MutableContextSet.fromMultimap(multimap).makeImmutable();
return fromEntries(checkNotNull(multimap, "multimap").entries());
}
/**
@@ -136,7 +139,7 @@ public final class ImmutableContextSet implements ContextSet {
*/
@Nonnull
public static ImmutableContextSet fromSet(@Nonnull ContextSet contextSet) {
return Preconditions.checkNotNull(contextSet, "contextSet").makeImmutable();
return checkNotNull(contextSet, "contextSet").makeImmutable();
}
/**
@@ -149,10 +152,10 @@ public final class ImmutableContextSet implements ContextSet {
return EMPTY;
}
private final SetMultimap<String, String> map;
private final ImmutableSetMultimap<String, String> map;
ImmutableContextSet(Multimap<String, String> contexts) {
this.map = ImmutableSetMultimap.copyOf(contexts);
ImmutableContextSet(ImmutableSetMultimap<String, String> contexts) {
this.map = contexts;
}
@Override
@@ -176,7 +179,7 @@ public final class ImmutableContextSet implements ContextSet {
@Override
@Nonnull
public Set<Map.Entry<String, String>> toSet() {
return ImmutableSet.copyOf(map.entries());
return map.entries();
}
@Override
@@ -197,36 +200,35 @@ public final class ImmutableContextSet implements ContextSet {
}
@Override
@Nonnull
public boolean containsKey(@Nonnull String key) {
Preconditions.checkNotNull(key, "key");
return map.containsKey(key);
return map.containsKey(checkNotNull(key, "key").toLowerCase().intern());
}
@Override
@Nonnull
public Set<String> getValues(@Nonnull String key) {
Preconditions.checkNotNull(key, "key");
Collection<String> c = map.get(key);
return c != null && !c.isEmpty() ? ImmutableSet.copyOf(c) : ImmutableSet.of();
Collection<String> values = map.get(checkNotNull(key, "key").toLowerCase().intern());
return values != null ? ImmutableSet.copyOf(values) : ImmutableSet.of();
}
@Override
@Nonnull
public boolean has(@Nonnull String key, @Nonnull String value) {
Preconditions.checkNotNull(key, "key");
Preconditions.checkNotNull(value, "value");
return map.containsEntry(key, value);
return map.containsEntry(checkNotNull(key, "key").toLowerCase().intern(), checkNotNull(value, "value").intern());
}
@Override
@Nonnull
public boolean hasIgnoreCase(@Nonnull String key, @Nonnull String value) {
Preconditions.checkNotNull(key, "key");
Preconditions.checkNotNull(value, "value");
value = checkNotNull(value, "value").intern();
Collection<String> values = map.get(checkNotNull(key, "key").toLowerCase().intern());
Collection<String> c = map.get(key);
if (c == null || c.isEmpty()) {
if (values == null || values.isEmpty()) {
return false;
}
for (String val : c) {
for (String val : values) {
if (val.equalsIgnoreCase(value)) {
return true;
}
@@ -250,6 +252,11 @@ public final class ImmutableContextSet implements ContextSet {
if (!(o instanceof ContextSet)) return false;
final ContextSet other = (ContextSet) o;
// saves on copying the multimap
if (other instanceof MutableContextSet) {
return other.equals(this);
}
final Multimap<String, String> thisContexts = this.toMultimap();
final Multimap<String, String> otherContexts = other.toMultimap();
return thisContexts.equals(otherContexts);
@@ -257,7 +264,7 @@ public final class ImmutableContextSet implements ContextSet {
@Override
public int hashCode() {
return 59 + (this.map == null ? 43 : this.map.hashCode());
return map.hashCode();
}
@Override
@@ -40,9 +40,14 @@ import java.util.Set;
import javax.annotation.Nonnull;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* A mutable implementation of {@link ContextSet}.
*
* <p>On construction, all keys/values are {@link String#intern()}ed, in order to increase
* comparison speed.</p>
*
* @since 2.16
*/
public final class MutableContextSet implements ContextSet {
@@ -57,9 +62,9 @@ public final class MutableContextSet implements ContextSet {
*/
@Nonnull
public static MutableContextSet singleton(@Nonnull String key, @Nonnull String value) {
Preconditions.checkNotNull(key, "key");
Preconditions.checkNotNull(value, "value");
MutableContextSet set = new MutableContextSet();
checkNotNull(key, "key");
checkNotNull(value, "value");
MutableContextSet set = MutableContextSet.create();
set.add(key, value);
return set;
}
@@ -77,27 +82,13 @@ public final class MutableContextSet implements ContextSet {
*/
@Nonnull
public static MutableContextSet of(@Nonnull String key1, @Nonnull String value1, @Nonnull String key2, @Nonnull String value2) {
Preconditions.checkNotNull(key1, "key1");
Preconditions.checkNotNull(value1, "value1");
Preconditions.checkNotNull(key2, "key2");
Preconditions.checkNotNull(value2, "value2");
MutableContextSet ret = singleton(key1, value1);
ret.add(key2, value2);
return ret;
}
/**
* Creates a MutableContextSet from an existing map
*
* @param map the map to copy from
* @return a new MutableContextSet representing the pairs from the map
* @throws NullPointerException if the map is null
*/
@Nonnull
public static MutableContextSet fromMap(@Nonnull Map<String, String> map) {
Preconditions.checkNotNull(map, "map");
MutableContextSet set = new MutableContextSet();
set.addAll(map);
checkNotNull(key1, "key1");
checkNotNull(value1, "value1");
checkNotNull(key2, "key2");
checkNotNull(value2, "value2");
MutableContextSet set = MutableContextSet.create();
set.add(key1, value1);
set.add(key2, value2);
return set;
}
@@ -110,12 +101,27 @@ public final class MutableContextSet implements ContextSet {
*/
@Nonnull
public static MutableContextSet fromEntries(@Nonnull Iterable<? extends Map.Entry<String, String>> iterable) {
Preconditions.checkNotNull(iterable, "iterable");
MutableContextSet set = new MutableContextSet();
checkNotNull(iterable, "iterable");
MutableContextSet set = MutableContextSet.create();
set.addAll(iterable);
return set;
}
/**
* Creates a MutableContextSet from an existing map
*
* @param map the map to copy from
* @return a new MutableContextSet representing the pairs from the map
* @throws NullPointerException if the map is null
*/
@Nonnull
public static MutableContextSet fromMap(@Nonnull Map<String, String> map) {
checkNotNull(map, "map");
MutableContextSet set = MutableContextSet.create();
set.addAll(map);
return set;
}
/**
* Creates a MutableContextSet from an existing multimap
*
@@ -125,8 +131,10 @@ public final class MutableContextSet implements ContextSet {
*/
@Nonnull
public static MutableContextSet fromMultimap(@Nonnull Multimap<String, String> multimap) {
Preconditions.checkNotNull(multimap, "multimap");
return fromEntries(multimap.entries());
checkNotNull(multimap, "multimap");
MutableContextSet set = MutableContextSet.create();
set.addAll(multimap);
return set;
}
/**
@@ -161,8 +169,8 @@ public final class MutableContextSet implements ContextSet {
this.map = Multimaps.synchronizedSetMultimap(HashMultimap.create());
}
private MutableContextSet(Multimap<String, String> contexts) {
this.map = Multimaps.synchronizedSetMultimap(HashMultimap.create(contexts));
private MutableContextSet(MutableContextSet other) {
this.map = Multimaps.synchronizedSetMultimap(HashMultimap.create(other.map));
}
@Override
@@ -170,21 +178,25 @@ public final class MutableContextSet implements ContextSet {
return false;
}
@Nonnull
@Override
public ImmutableContextSet makeImmutable() {
return new ImmutableContextSet(map);
return new ImmutableContextSet(ImmutableSetMultimap.copyOf(map));
}
@Nonnull
@Override
public MutableContextSet mutableCopy() {
return new MutableContextSet(map);
return new MutableContextSet(this);
}
@Nonnull
@Override
public Set<Map.Entry<String, String>> toSet() {
return ImmutableSet.copyOf(map.entries());
}
@Nonnull
@Override
public Map<String, String> toMap() {
ImmutableMap.Builder<String, String> m = ImmutableMap.builder();
@@ -195,57 +207,42 @@ public final class MutableContextSet implements ContextSet {
return m.build();
}
@Nonnull
@Override
public Multimap<String, String> toMultimap() {
return ImmutableSetMultimap.copyOf(map);
}
@Nonnull
@Override
public boolean containsKey(String key) {
if (key == null) {
throw new NullPointerException("key");
}
return map.containsKey(key);
public boolean containsKey(@Nonnull String key) {
return map.containsKey(checkNotNull(key, "key").toLowerCase().intern());
}
@Nonnull
@Override
public Set<String> getValues(String key) {
if (key == null) {
throw new NullPointerException("key");
}
Collection<String> c = map.get(key);
return c != null && !c.isEmpty() ? ImmutableSet.copyOf(c) : ImmutableSet.of();
public Set<String> getValues(@Nonnull String key) {
Collection<String> values = map.get(checkNotNull(key, "key").toLowerCase().intern());
return values != null ? ImmutableSet.copyOf(values) : ImmutableSet.of();
}
@Nonnull
@Override
public boolean has(String key, String value) {
if (key == null) {
throw new NullPointerException("key");
}
if (value == null) {
throw new NullPointerException("value");
}
return map.containsEntry(key, value);
public boolean has(@Nonnull String key, @Nonnull String value) {
return map.containsEntry(checkNotNull(key, "key").toLowerCase().intern(), checkNotNull(value, "value").intern());
}
@Nonnull
@Override
public boolean hasIgnoreCase(String key, String value) {
if (key == null) {
throw new NullPointerException("key");
}
if (value == null) {
throw new NullPointerException("value");
}
public boolean hasIgnoreCase(@Nonnull String key, @Nonnull String value) {
value = checkNotNull(value, "value").intern();
Collection<String> values = map.get(checkNotNull(key, "key").toLowerCase().intern());
Collection<String> c = map.get(key);
if (c == null || c.isEmpty()) {
if (values == null || values.isEmpty()) {
return false;
}
for (String val : c) {
for (String val : values) {
if (val.equalsIgnoreCase(value)) {
return true;
}
@@ -270,15 +267,8 @@ public final class MutableContextSet implements ContextSet {
* @param value the value to add
* @throws NullPointerException if the key or value is null
*/
public void add(String key, String value) {
if (key == null) {
throw new NullPointerException("key");
}
if (value == null) {
throw new NullPointerException("value");
}
map.put(key.toLowerCase(), value);
public void add(@Nonnull String key, @Nonnull String value) {
map.put(checkNotNull(key, "key").toLowerCase().intern(), checkNotNull(value, "value").intern());
}
/**
@@ -287,12 +277,9 @@ public final class MutableContextSet implements ContextSet {
* @param entry the entry to add
* @throws NullPointerException if the entry is null
*/
public void add(Map.Entry<String, String> entry) {
if (entry == null) {
throw new NullPointerException("context");
}
map.put(entry.getKey().toLowerCase(), entry.getValue());
public void add(@Nonnull Map.Entry<String, String> entry) {
checkNotNull(entry, "entry");
add(entry.getKey(), entry.getValue());
}
/**
@@ -301,13 +288,9 @@ public final class MutableContextSet implements ContextSet {
* @param iterable an iterable of key value context pairs
* @throws NullPointerException if iterable is null
*/
public void addAll(Iterable<? extends Map.Entry<String, String>> iterable) {
if (iterable == null) {
throw new NullPointerException("iterable");
}
for (Map.Entry<String, String> e : iterable) {
this.map.put(e.getKey().toLowerCase(), e.getValue());
public void addAll(@Nonnull Iterable<? extends Map.Entry<String, String>> iterable) {
for (Map.Entry<String, String> e : checkNotNull(iterable, "iterable")) {
add(e);
}
}
@@ -317,11 +300,19 @@ public final class MutableContextSet implements ContextSet {
* @param map the map to add from
* @throws NullPointerException if the map is null
*/
public void addAll(Map<String, String> map) {
if (map == null) {
throw new NullPointerException("map");
}
addAll(map.entrySet());
public void addAll(@Nonnull Map<String, String> map) {
addAll(checkNotNull(map, "map").entrySet());
}
/**
* Adds the entries of a multimap to the set
*
* @param multimap the multimap to add from
* @throws NullPointerException if the map is null
* @since 3.4
*/
public void addAll(@Nonnull Multimap<String, String> multimap) {
addAll(checkNotNull(multimap, "multimap").entries());
}
/**
@@ -330,12 +321,14 @@ public final class MutableContextSet implements ContextSet {
* @param contextSet the set to add from
* @throws NullPointerException if the contextSet is null
*/
public void addAll(ContextSet contextSet) {
if (contextSet == null) {
throw new NullPointerException("contextSet");
public void addAll(@Nonnull ContextSet contextSet) {
checkNotNull(contextSet, "contextSet");
if (contextSet instanceof MutableContextSet) {
MutableContextSet other = ((MutableContextSet) contextSet);
this.map.putAll(other.map);
} else {
addAll(contextSet.toMultimap());
}
this.map.putAll(contextSet.toMultimap());
}
/**
@@ -345,15 +338,12 @@ public final class MutableContextSet implements ContextSet {
* @param value the value to remove (case sensitive)
* @throws NullPointerException if the key or value is null
*/
public void remove(String key, String value) {
if (key == null) {
throw new NullPointerException("key");
}
if (value == null) {
throw new NullPointerException("value");
}
public void remove(@Nonnull String key, @Nonnull String value) {
String k = checkNotNull(key, "key").toLowerCase().intern();
String v = checkNotNull(value, "value").intern();
map.entries().removeIf(entry -> entry.getKey().equals(key) && entry.getValue().equals(value));
//noinspection StringEquality
map.entries().removeIf(entry -> entry.getKey() == k && entry.getValue() == v);
}
/**
@@ -363,15 +353,12 @@ public final class MutableContextSet implements ContextSet {
* @param value the value to remove
* @throws NullPointerException if the key or value is null
*/
public void removeIgnoreCase(String key, String value) {
if (key == null) {
throw new NullPointerException("key");
}
if (value == null) {
throw new NullPointerException("value");
}
public void removeIgnoreCase(@Nonnull String key, @Nonnull String value) {
String k = checkNotNull(key, "key").toLowerCase().intern();
String v = checkNotNull(value, "value").intern();
map.entries().removeIf(e -> e.getKey().equalsIgnoreCase(key) && e.getValue().equalsIgnoreCase(value));
//noinspection StringEquality
map.entries().removeIf(e -> e.getKey() == k && e.getValue().equalsIgnoreCase(v));
}
/**
@@ -380,12 +367,8 @@ public final class MutableContextSet implements ContextSet {
* @param key the key to remove
* @throws NullPointerException if the key is null
*/
public void removeAll(String key) {
if (key == null) {
throw new NullPointerException("key");
}
map.removeAll(key.toLowerCase());
public void removeAll(@Nonnull String key) {
map.removeAll(checkNotNull(key, "key").toLowerCase());
}
/**
@@ -401,14 +384,20 @@ public final class MutableContextSet implements ContextSet {
if (!(o instanceof ContextSet)) return false;
final ContextSet other = (ContextSet) o;
final Multimap<String, String> thisContexts = this.toMultimap();
final Multimap<String, String> otherContexts = other.toMultimap();
return thisContexts == null ? otherContexts == null : thisContexts.equals(otherContexts);
final Multimap<String, String> otherContexts;
if (other instanceof MutableContextSet) {
otherContexts = ((MutableContextSet) other).map;
} else {
otherContexts = other.toMultimap();
}
return this.map.equals(otherContexts);
}
@Override
public int hashCode() {
return 59 + (this.map == null ? 43 : this.map.hashCode());
return map.hashCode();
}
@Override
@@ -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 me.lucko.luckperms.api.context;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* Extension of {@link ContextCalculator} which provides the same context
* regardless of the subject.
*
* @since 4.0
*/
@FunctionalInterface
public interface StaticContextCalculator extends ContextCalculator<Object> {
/**
* Adds this calculators context to the given accumulator.
*
* @param accumulator a map of contexts to add to
* @return the map
*/
@Nonnull
MutableContextSet giveApplicableContext(@Nonnull MutableContextSet accumulator);
/**
* Gives the subject all of the applicable contexts they meet
*
* @param subject the subject to add contexts to
* @param accumulator a map of contexts to add to
* @return the map
*/
@Nonnull
@Override
@Deprecated
default MutableContextSet giveApplicableContext(@Nullable Object subject, @Nonnull MutableContextSet accumulator) {
return giveApplicableContext(accumulator);
}
}
@@ -44,4 +44,31 @@ public interface LogBroadcastEvent extends LuckPermsEvent, Cancellable {
@Nonnull
LogEntry getEntry();
/**
* Gets where the log entry originated from.
*
* @return the origin of the log
* @since 3.3
*/
@Nonnull
Origin getOrigin();
/**
* Represents where a log entry is from
*
* @since 3.3
*/
enum Origin {
/**
* Represents a log entry which originated from the current server instance
*/
LOCAL,
/**
* Represents a log entry which was sent to this server via the messaging service
*/
REMOTE
}
}
@@ -0,0 +1,59 @@
/*
* 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.api.event.log;
import me.lucko.luckperms.api.LogEntry;
import me.lucko.luckperms.api.event.Cancellable;
import me.lucko.luckperms.api.event.LuckPermsEvent;
import java.util.UUID;
import javax.annotation.Nonnull;
/**
* Called when a log is about to be published to the network via the MessagingService
*
* @since 3.3
*/
public interface LogNetworkPublishEvent extends LuckPermsEvent, Cancellable {
/**
* Gets the ID of the log entry being published
*
* @return the id of the log entry being published
*/
@Nonnull
UUID getLogId();
/**
* Gets the log entry to be published
*
* @return the log entry to be published
*/
@Nonnull
LogEntry getEntry();
}
@@ -0,0 +1,58 @@
/*
* 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.api.event.log;
import me.lucko.luckperms.api.LogEntry;
import me.lucko.luckperms.api.event.LuckPermsEvent;
import java.util.UUID;
import javax.annotation.Nonnull;
/**
* Called when a log entry is received via the MessagingService
*
* @since 3.3
*/
public interface LogReceiveEvent extends LuckPermsEvent {
/**
* Gets the ID of the log entry being received
*
* @return the id of the log entry being received
*/
@Nonnull
UUID getLogId();
/**
* Gets the log entry being received
*
* @return the log entry being received
*/
@Nonnull
LogEntry getEntry();
}
@@ -33,7 +33,7 @@ import java.util.UUID;
import javax.annotation.Nonnull;
/**
* Called before a network sync task runs
* Called before a received network sync task runs
*/
public interface PreNetworkSyncEvent extends LuckPermsEvent, Cancellable {
@@ -0,0 +1,72 @@
/*
* 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.api.event.user;
import me.lucko.luckperms.api.User;
import me.lucko.luckperms.api.event.LuckPermsEvent;
import java.util.UUID;
import javax.annotation.Nonnull;
/**
* Called when LuckPerms has finished processing a certain Player's connection.
*
* <p>This event will always execute during the platforms async login/auth event.
* All handlers will be called instantly.</p>
*
* <p>This, among other things, allows you to wait until permission data is loaded
* for a User during the BungeeCord 'LoginEvent', as event priorities are ignored
* by the current implementation.</p>
*
* @since 3.4
*/
public interface UserLoginProcessEvent extends LuckPermsEvent {
/**
* Gets the UUID of the connection which was processed
*
* @return the uuid of the connection which was processed
*/
@Nonnull
UUID getUuid();
/**
* Gets the username of the connection which was processed
*
* @return the username of the connection which was processed
*/
@Nonnull
String getUsername();
/**
* Gets the resultant User instance which was loaded.
*
* @return the user instance
*/
User getUser();
}
@@ -0,0 +1,84 @@
/*
* 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.api.manager;
import me.lucko.luckperms.api.Group;
import java.util.Optional;
import java.util.Set;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* Represents the object responsible for managing {@link Group} instances.
*
* @since 4.0
*/
public interface GroupManager {
/**
* Gets a wrapped group object from the group storage
*
* @param name the name of the group to get
* @return a {@link Group} object, if one matching the name exists, or null if not
* @throws NullPointerException if the name is null
*/
@Nullable
Group getGroup(@Nonnull String name);
/**
* Gets a wrapped group object from the group storage.
*
* <p>This method does not return null, unlike {@link #getGroup}</p>
*
* @param name the name of the group to get
* @return an optional {@link Group} object
* @throws NullPointerException if the name is null
*/
@Nonnull
default Optional<Group> getGroupOpt(@Nonnull String name) {
return Optional.ofNullable(getGroup(name));
}
/**
* Gets a set of all loaded groups.
*
* @return a {@link Set} of {@link Group} objects
*/
@Nonnull
Set<Group> getLoadedGroups();
/**
* Check if a group is loaded in memory
*
* @param name the name to check for
* @return true if the group is loaded
* @throws NullPointerException if the name is null
*/
boolean isLoaded(@Nonnull String name);
}
@@ -0,0 +1,84 @@
/*
* 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.api.manager;
import me.lucko.luckperms.api.Track;
import java.util.Optional;
import java.util.Set;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* Represents the object responsible for managing {@link Track} instances.
*
* @since 4.0
*/
public interface TrackManager {
/**
* Gets a wrapped track object from the track storage
*
* @param name the name of the track to get
* @return a {@link Track} object, if one matching the name exists, or null if not
* @throws NullPointerException if the name is null
*/
@Nullable
Track getTrack(@Nonnull String name);
/**
* Gets a wrapped track object from the track storage.
*
* <p>This method does not return null, unlike {@link #getTrack}</p>
*
* @param name the name of the track to get
* @return an optional {@link Track} object
* @throws NullPointerException if the name is null
*/
@Nonnull
default Optional<Track> getTrackOpt(@Nonnull String name) {
return Optional.ofNullable(getTrack(name));
}
/**
* Gets a set of all loaded tracks.
*
* @return a {@link Set} of {@link Track} objects
*/
@Nonnull
Set<Track> getLoadedTracks();
/**
* Check if a track is loaded in memory
*
* @param name the name to check for
* @return true if the track is loaded
* @throws NullPointerException if the name is null
*/
boolean isLoaded(@Nonnull String name);
}
@@ -0,0 +1,113 @@
/*
* 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.api.manager;
import me.lucko.luckperms.api.User;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* Represents the object responsible for managing {@link User} instances.
*
* @since 4.0
*/
public interface UserManager {
/**
* Gets a wrapped user object from the user storage
*
* @param uuid the uuid of the user to get
* @return a {@link User} object, if one matching the uuid is loaded, or null if not
* @throws NullPointerException if the uuid is null
*/
@Nullable
User getUser(@Nonnull UUID uuid);
/**
* Gets a wrapped user object from the user storage.
*
* @param uuid the uuid of the user to get
* @return an optional {@link User} object
* @throws NullPointerException if the uuid is null
*/
@Nonnull
default Optional<User> getUserOpt(@Nonnull UUID uuid) {
return Optional.ofNullable(getUser(uuid));
}
/**
* Gets a wrapped user object from the user storage
*
* @param name the username of the user to get
* @return a {@link User} object, if one matching the uuid is loaded, or null if not
* @throws NullPointerException if the name is null
*/
@Nullable
User getUser(@Nonnull String name);
/**
* Gets a wrapped user object from the user storage.
*
* @param name the username of the user to get
* @return an optional {@link User} object
* @throws NullPointerException if the name is null
*/
@Nonnull
default Optional<User> getUserOpt(@Nonnull String name) {
return Optional.ofNullable(getUser(name));
}
/**
* Gets a set of all loaded users.
*
* @return a {@link Set} of {@link User} objects
*/
@Nonnull
Set<User> getLoadedUsers();
/**
* Check if a user is loaded in memory
*
* @param uuid the uuid to check for
* @return true if the user is loaded
* @throws NullPointerException if the uuid is null
*/
boolean isLoaded(@Nonnull UUID uuid);
/**
* Unload a user from the internal storage, if they're not currently online.
*
* @param user the user to unload
* @throws NullPointerException if the user is null
*/
void cleanupUser(@Nonnull User user);
}
@@ -0,0 +1,78 @@
/*
* 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.api.platform;
import java.util.Set;
import java.util.UUID;
import javax.annotation.Nonnull;
/**
* Provides information about the platform LuckPerms is running on.
*
* @since 4.0
*/
public interface PlatformInfo {
/**
* Gets the plugin version
*
* @return the version of the plugin running on the platform
*/
@Nonnull
String getVersion();
/**
* Gets the API version
*
* @return the version of the API running on the platform
*/
double getApiVersion();
/**
* Gets the type of platform LuckPerms is running on
*
* @return the type of platform LuckPerms is running on
*/
@Nonnull
PlatformType getType();
/**
* Gets the unique players which have connected to the server since it started.
*
* @return the unique connections
*/
@Nonnull
Set<UUID> getUniqueConnections();
/**
* Gets the time when the plugin first started in milliseconds.
*
* @return the enable time
*/
long getStartTime();
}
@@ -23,12 +23,12 @@
* SOFTWARE.
*/
package me.lucko.luckperms.api;
package me.lucko.luckperms.api.platform;
import javax.annotation.Nonnull;
/**
* The platforms which LuckPerms can run on
* Represents a type of platform which LuckPerms can run on.
*
* @since 2.7
*/
@@ -44,6 +44,11 @@ public enum PlatformType {
this.friendlyName = friendlyName;
}
/**
* Gets a readable name for the platform type.
*
* @return a readable name
*/
@Nonnull
public String getFriendlyName() {
return friendlyName;
+1 -1
View File
@@ -5,7 +5,7 @@
<parent>
<artifactId>luckperms</artifactId>
<groupId>me.lucko.luckperms</groupId>
<version>3.3-SNAPSHOT</version>
<version>4.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
+1 -1
View File
@@ -5,7 +5,7 @@
<parent>
<artifactId>luckperms</artifactId>
<groupId>me.lucko.luckperms</groupId>
<version>3.3-SNAPSHOT</version>
<version>4.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -29,6 +29,7 @@ import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import me.lucko.luckperms.common.commands.CommandManager;
import me.lucko.luckperms.common.commands.sender.Sender;
import me.lucko.luckperms.common.commands.utils.Util;
import org.bukkit.command.Command;
@@ -36,29 +37,33 @@ import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabExecutor;
import java.util.Arrays;
import java.util.List;
public class BukkitCommand extends CommandManager implements CommandExecutor, TabExecutor {
public class BukkitCommandExecutor extends CommandManager implements CommandExecutor, TabExecutor {
private static final Splitter ARGUMENT_SPLITTER = Splitter.on(COMMAND_SEPARATOR_PATTERN).omitEmptyStrings();
private static final Joiner ARGUMENT_JOINER = Joiner.on(' ');
private final LPBukkitPlugin plugin;
BukkitCommand(LPBukkitPlugin plugin) {
BukkitCommandExecutor(LPBukkitPlugin plugin) {
super(plugin);
this.plugin = plugin;
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
onCommand(
plugin.getSenderFactory().wrap(sender),
label,
Util.stripQuotes(Splitter.on(COMMAND_SEPARATOR_PATTERN).omitEmptyStrings().splitToList(Joiner.on(' ').join(args)))
);
Sender lpSender = plugin.getSenderFactory().wrap(sender);
List<String> arguments = Util.stripQuotes(ARGUMENT_SPLITTER.splitToList(ARGUMENT_JOINER.join(args)));
onCommand(lpSender, label, arguments);
return true;
}
@Override
public List<String> onTabComplete(CommandSender sender, Command command, String label, String[] args) {
return onTabComplete(plugin.getSenderFactory().wrap(sender), Arrays.asList(args));
Sender lpSender = plugin.getSenderFactory().wrap(sender);
List<String> arguments = Util.stripQuotes(ARGUMENT_SPLITTER.splitToList(ARGUMENT_JOINER.join(args)));
return onTabComplete(lpSender, arguments);
}
}
@@ -28,7 +28,7 @@ package me.lucko.luckperms.bukkit;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import me.lucko.luckperms.common.config.AbstractConfiguration;
import me.lucko.luckperms.common.config.ConfigurationAdapter;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration;
@@ -41,7 +41,7 @@ import java.util.Map;
import java.util.Set;
@RequiredArgsConstructor
public class BukkitConfig extends AbstractConfiguration {
public class BukkitConfigAdapter implements ConfigurationAdapter {
@Getter
private final LPBukkitPlugin plugin;
@@ -27,8 +27,9 @@ package me.lucko.luckperms.bukkit;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import me.lucko.luckperms.common.plugin.LuckPermsScheduler;
import me.lucko.luckperms.common.plugin.SchedulerAdapter;
import org.bukkit.scheduler.BukkitTask;
@@ -39,37 +40,38 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class LPBukkitScheduler implements LuckPermsScheduler {
public class BukkitSchedulerAdapter implements SchedulerAdapter {
private final LPBukkitPlugin plugin;
@Getter
private ExecutorService asyncLpExecutor;
@Accessors(fluent = true)
private ExecutorService asyncLp;
@Getter
private Executor asyncBukkitExecutor;
private Executor syncExecutor;
@Accessors(fluent = true)
private Executor asyncBukkit;
@Getter
@Accessors(fluent = true)
private Executor sync;
@Getter
@Accessors(fluent = true)
private Executor async;
@Getter
@Setter
private boolean useBukkitAsync = false;
private Set<BukkitTask> tasks = ConcurrentHashMap.newKeySet();
private final Set<BukkitTask> tasks = ConcurrentHashMap.newKeySet();
public LPBukkitScheduler(LPBukkitPlugin plugin) {
public BukkitSchedulerAdapter(LPBukkitPlugin plugin) {
this.plugin = plugin;
this.asyncLpExecutor = Executors.newCachedThreadPool();
this.asyncBukkitExecutor = r -> plugin.getServer().getScheduler().runTaskAsynchronously(plugin, r);
this.syncExecutor = r -> plugin.getServer().getScheduler().runTask(plugin, r);
}
@Override
public Executor async() {
return useBukkitAsync ? asyncBukkitExecutor : asyncLpExecutor;
}
@Override
public Executor sync() {
return syncExecutor;
this.asyncLp = Executors.newCachedThreadPool();
this.asyncBukkit = r -> plugin.getServer().getScheduler().runTaskAsynchronously(plugin, r);
this.sync = r -> plugin.getServer().getScheduler().runTask(plugin, r);
this.async = r -> (useBukkitAsync ? asyncBukkit : asyncLp).execute(r);
}
@Override
@@ -108,9 +110,9 @@ public class LPBukkitScheduler implements LuckPermsScheduler {
public void shutdown() {
tasks.forEach(BukkitTask::cancel);
// wait for executor
asyncLpExecutor.shutdown();
asyncLp.shutdown();
try {
asyncLpExecutor.awaitTermination(30, TimeUnit.SECONDS);
asyncLp.awaitTermination(30, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
@@ -25,6 +25,8 @@
package me.lucko.luckperms.bukkit;
import lombok.AllArgsConstructor;
import me.lucko.luckperms.api.Tristate;
import me.lucko.luckperms.bukkit.compat.MessageHandler;
import me.lucko.luckperms.common.commands.sender.SenderFactory;
@@ -68,7 +70,7 @@ public class BukkitSenderFactory extends SenderFactory<CommandSender> {
// send sync if command block
if (sender instanceof BlockCommandSender) {
getPlugin().getScheduler().doSync(() -> sender.sendMessage(s));
getPlugin().getScheduler().doSync(new BlockMessageAgent(((BlockCommandSender) sender), s));
return;
}
@@ -92,4 +94,16 @@ public class BukkitSenderFactory extends SenderFactory<CommandSender> {
protected boolean hasPermission(CommandSender sender, String node) {
return sender.hasPermission(node);
}
@AllArgsConstructor
private static final class BlockMessageAgent implements Runnable {
private final BlockCommandSender block;
private final String message;
@Override
public void run() {
block.sendMessage(message);
}
}
}
@@ -28,27 +28,30 @@ package me.lucko.luckperms.bukkit;
import lombok.Getter;
import me.lucko.luckperms.api.Contexts;
import me.lucko.luckperms.api.Logger;
import me.lucko.luckperms.api.LuckPermsApi;
import me.lucko.luckperms.api.PlatformType;
import me.lucko.luckperms.api.context.ContextSet;
import me.lucko.luckperms.api.context.ImmutableContextSet;
import me.lucko.luckperms.api.context.MutableContextSet;
import me.lucko.luckperms.api.platform.PlatformType;
import me.lucko.luckperms.bukkit.calculators.BukkitCalculatorFactory;
import me.lucko.luckperms.bukkit.contexts.BukkitContextManager;
import me.lucko.luckperms.bukkit.contexts.WorldCalculator;
import me.lucko.luckperms.bukkit.messaging.BungeeMessagingService;
import me.lucko.luckperms.bukkit.messaging.LilyPadMessagingService;
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.model.Injector;
import me.lucko.luckperms.bukkit.model.LPPermissible;
import me.lucko.luckperms.bukkit.model.SubscriptionMapInjector;
import me.lucko.luckperms.bukkit.processors.BukkitProcessorsSetupTask;
import me.lucko.luckperms.bukkit.processors.ChildPermissionProvider;
import me.lucko.luckperms.bukkit.processors.DefaultsProvider;
import me.lucko.luckperms.bukkit.vault.VaultHookManager;
import me.lucko.luckperms.common.api.ApiHandler;
import me.lucko.luckperms.common.actionlog.LogDispatcher;
import me.lucko.luckperms.common.api.ApiProvider;
import me.lucko.luckperms.common.api.ApiSingletonUtils;
import me.lucko.luckperms.common.buffers.BufferedRequest;
import me.lucko.luckperms.common.buffers.UpdateTaskBuffer;
import me.lucko.luckperms.common.caching.handlers.CachedStateManager;
import me.lucko.luckperms.common.calculators.CalculatorFactory;
import me.lucko.luckperms.common.commands.sender.Sender;
import me.lucko.luckperms.common.config.AbstractConfiguration;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.config.LuckPermsConfiguration;
import me.lucko.luckperms.common.constants.CommandPermission;
@@ -59,6 +62,7 @@ import me.lucko.luckperms.common.dependencies.DependencyManager;
import me.lucko.luckperms.common.locale.LocaleManager;
import me.lucko.luckperms.common.locale.NoopLocaleManager;
import me.lucko.luckperms.common.locale.SimpleLocaleManager;
import me.lucko.luckperms.common.logging.Logger;
import me.lucko.luckperms.common.logging.SenderLogger;
import me.lucko.luckperms.common.managers.GenericGroupManager;
import me.lucko.luckperms.common.managers.GenericTrackManager;
@@ -66,15 +70,13 @@ import me.lucko.luckperms.common.managers.GenericUserManager;
import me.lucko.luckperms.common.managers.GroupManager;
import me.lucko.luckperms.common.managers.TrackManager;
import me.lucko.luckperms.common.managers.UserManager;
import me.lucko.luckperms.common.messaging.InternalMessagingService;
import me.lucko.luckperms.common.messaging.NoopMessagingService;
import me.lucko.luckperms.common.messaging.RedisMessagingService;
import me.lucko.luckperms.common.messaging.ExtendedMessagingService;
import me.lucko.luckperms.common.model.User;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.storage.Storage;
import me.lucko.luckperms.common.storage.StorageFactory;
import me.lucko.luckperms.common.storage.StorageType;
import me.lucko.luckperms.common.storage.backing.file.FileWatcher;
import me.lucko.luckperms.common.storage.dao.file.FileWatcher;
import me.lucko.luckperms.common.tasks.CacheHousekeepingTask;
import me.lucko.luckperms.common.tasks.ExpireTemporaryTask;
import me.lucko.luckperms.common.tasks.UpdateTask;
@@ -83,7 +85,6 @@ import me.lucko.luckperms.common.utils.LoginHelper;
import me.lucko.luckperms.common.utils.UuidCache;
import me.lucko.luckperms.common.verbose.VerboseHandler;
import org.bukkit.World;
import org.bukkit.command.PluginCommand;
import org.bukkit.entity.Player;
import org.bukkit.event.HandlerList;
@@ -96,24 +97,24 @@ import java.io.File;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* LuckPerms implementation for the Bukkit API.
*/
@Getter
public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
private Set<UUID> ignoringLogs;
private Set<UUID> uniqueConnections;
private long startTime;
private LPBukkitScheduler scheduler;
private BukkitCommand commandManager;
private BukkitSchedulerAdapter scheduler;
private BukkitCommandExecutor commandManager;
private VaultHookManager vaultHookManager = null;
private LuckPermsConfiguration configuration;
private UserManager userManager;
@@ -121,9 +122,8 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
private TrackManager trackManager;
private Storage storage;
private FileWatcher fileWatcher = null;
private InternalMessagingService messagingService = null;
private ExtendedMessagingService messagingService = null;
private UuidCache uuidCache;
private BukkitListener listener;
private ApiProvider apiProvider;
private Logger log;
private DefaultsProvider defaultsProvider;
@@ -131,7 +131,6 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
private LocaleManager localeManager;
private CachedStateManager cachedStateManager;
private ContextManager<Player> contextManager;
private WorldCalculator worldCalculator;
private CalculatorFactory calculatorFactory;
private BufferedRequest<Void> updateTaskBuffer;
private boolean started = false;
@@ -139,20 +138,38 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
private VerboseHandler verboseHandler;
private BukkitSenderFactory senderFactory;
private PermissionVault permissionVault;
private LogDispatcher logDispatcher;
private Set<UUID> uniqueConnections = ConcurrentHashMap.newKeySet();
@Override
public void onLoad() {
if (checkInvalidVersion()) {
return;
}
// setup minimal functionality in order to load initial dependencies
scheduler = new LPBukkitScheduler(this);
scheduler = new BukkitSchedulerAdapter(this);
localeManager = new NoopLocaleManager();
senderFactory = new BukkitSenderFactory(this);
log = new SenderLogger(this, getConsoleSender());
DependencyManager.loadDependencies(this, Collections.singletonList(Dependency.CAFFEINE));
DependencyManager.loadDependencies(this, Collections.singleton(Dependency.CAFFEINE));
}
@Override
public void onEnable() {
if (checkInvalidVersion()) {
getLogger().severe("----------------------------------------------------------------------");
getLogger().severe("Your server version is not compatible with this build of LuckPerms. :(");
getLogger().severe("");
getLogger().severe("If your server is running 1.8, please update to 1.8.8 or higher.");
getLogger().severe("If your server is running 1.7.10, please download the Bukkit-Legacy version of LuckPerms from here:");
getLogger().severe("==> https://ci.lucko.me/job/LuckPerms/");
getLogger().severe("----------------------------------------------------------------------");
getServer().getPluginManager().disablePlugin(this);
return;
}
try {
enable();
started = true;
@@ -166,41 +183,27 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
private void enable() {
startTime = System.currentTimeMillis();
LuckPermsPlugin.sendStartupBanner(getConsoleSender(), this);
ignoringLogs = ConcurrentHashMap.newKeySet();
uniqueConnections = ConcurrentHashMap.newKeySet();
verboseHandler = new VerboseHandler(scheduler.getAsyncBukkitExecutor(), getVersion());
permissionVault = new PermissionVault(scheduler.getAsyncBukkitExecutor());
verboseHandler = new VerboseHandler(scheduler.asyncBukkit(), getVersion());
permissionVault = new PermissionVault(scheduler.asyncBukkit());
logDispatcher = new LogDispatcher(this);
getLog().info("Loading configuration...");
configuration = new BukkitConfig(this);
configuration = new AbstractConfiguration(this, new BukkitConfigAdapter(this));
configuration.init();
configuration.loadAll();
Set<StorageType> storageTypes = StorageFactory.getRequiredTypes(this, StorageType.H2);
DependencyManager.loadDependencies(this, storageTypes);
DependencyManager.loadStorageDependencies(this, storageTypes);
// setup the Bukkit defaults hook
defaultsProvider = new DefaultsProvider();
childPermissionProvider = new ChildPermissionProvider();
// give all plugins a chance to load their permissions, then refresh.
scheduler.syncLater(() -> {
defaultsProvider.refresh();
childPermissionProvider.setup();
Set<String> perms = new HashSet<>();
getServer().getPluginManager().getPermissions().forEach(p -> {
perms.add(p.getName());
perms.addAll(p.getChildren().keySet());
});
perms.forEach(p -> permissionVault.offer(p));
}, 1L);
scheduler.syncLater(new BukkitProcessorsSetupTask(this), 1L);
// register events
listener = new BukkitListener(this);
getServer().getPluginManager().registerEvents(listener, this);
getServer().getPluginManager().registerEvents(new BukkitConnectionListener(this), this);
getServer().getPluginManager().registerEvents(new BukkitPlatformListener(this), this);
if (getConfiguration().get(ConfigKeys.WATCH_FILES)) {
fileWatcher = new FileWatcher(this);
@@ -211,71 +214,17 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
storage = StorageFactory.getInstance(this, StorageType.H2);
// initialise messaging
String messagingType = getConfiguration().get(ConfigKeys.MESSAGING_SERVICE).toLowerCase();
if (messagingType.equals("none") && getConfiguration().get(ConfigKeys.REDIS_ENABLED)) {
messagingType = "redis";
}
if (!messagingType.equals("none")) {
getLog().info("Loading messaging service... [" + messagingType.toUpperCase() + "]");
}
if (messagingType.equals("redis")) {
if (getConfiguration().get(ConfigKeys.REDIS_ENABLED)) {
RedisMessagingService redis = new RedisMessagingService(this);
try {
redis.init(getConfiguration().get(ConfigKeys.REDIS_ADDRESS), getConfiguration().get(ConfigKeys.REDIS_PASSWORD));
messagingService = redis;
} catch (Exception e) {
getLog().warn("Couldn't load redis...");
e.printStackTrace();
}
} else {
getLog().warn("Messaging Service was set to redis, but redis is not enabled!");
}
} else if (messagingType.equals("bungee")) {
BungeeMessagingService bungeeMessaging = new BungeeMessagingService(this);
bungeeMessaging.init();
messagingService = bungeeMessaging;
} else if (messagingType.equals("lilypad")) {
if (getServer().getPluginManager().getPlugin("LilyPad-Connect") == null) {
getLog().warn("LilyPad-Connect plugin not present.");
} else {
LilyPadMessagingService lilyPadMessaging = new LilyPadMessagingService(this);
lilyPadMessaging.init();
messagingService = lilyPadMessaging;
}
} else if (!messagingType.equals("none")) {
getLog().warn("Messaging service '" + messagingType + "' not recognised.");
}
if (messagingService == null) {
messagingService = new NoopMessagingService();
}
messagingService = new BukkitMessagingFactory(this).getInstance();
// setup the update task buffer
updateTaskBuffer = new BufferedRequest<Void>(1000L, this::doAsync) {
@Override
protected Void perform() {
new UpdateTask(LPBukkitPlugin.this).run();
return null;
}
};
updateTaskBuffer = new UpdateTaskBuffer(this);
// load locale
localeManager = new SimpleLocaleManager();
File locale = new File(getDataFolder(), "lang.yml");
if (locale.exists()) {
getLog().info("Found lang.yml - loading messages...");
try {
localeManager.loadFromFile(locale);
} catch (Exception e) {
e.printStackTrace();
}
}
localeManager.tryLoad(this, new File(getDataFolder(), "lang.yml"));
// register commands
commandManager = new BukkitCommand(this);
commandManager = new BukkitCommandExecutor(this);
PluginCommand main = getServer().getPluginCommand("luckperms");
main.setExecutor(commandManager);
main.setTabCompleter(commandManager);
@@ -291,33 +240,24 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
calculatorFactory = new BukkitCalculatorFactory(this);
cachedStateManager = new CachedStateManager();
contextManager = new ContextManager<Player>() {
@Override
public Contexts formContexts(Player player, ImmutableContextSet contextSet) {
return new Contexts(
contextSet,
getConfiguration().get(ConfigKeys.INCLUDING_GLOBAL_PERMS),
getConfiguration().get(ConfigKeys.INCLUDING_GLOBAL_WORLD_PERMS),
true,
getConfiguration().get(ConfigKeys.APPLYING_GLOBAL_GROUPS),
getConfiguration().get(ConfigKeys.APPLYING_GLOBAL_WORLD_GROUPS),
player.isOp()
);
}
};
// setup context manager
contextManager = new BukkitContextManager(this);
contextManager.registerCalculator(new WorldCalculator(this));
contextManager.registerStaticCalculator(new LuckPermsCalculator(getConfiguration()));
worldCalculator = new WorldCalculator(this);
contextManager.registerCalculator(worldCalculator);
// inject our own subscription map
new SubscriptionMapInjector(this).run();
LuckPermsCalculator<Player> staticCalculator = new LuckPermsCalculator<>(getConfiguration());
contextManager.registerCalculator(staticCalculator, true);
// schedule another injection after all plugins have loaded - the entire pluginmanager instance
// is replaced by some plugins :(
scheduler.asyncLater(new SubscriptionMapInjector(this), 2L);
// Provide vault support
tryVaultHook(false);
// register with the LP API
apiProvider = new ApiProvider(this);
ApiHandler.registerProvider(apiProvider);
ApiSingletonUtils.registerProvider(apiProvider);
getServer().getServicesManager().register(LuckPermsApi.class, apiProvider, this, ServicePriority.Normal);
@@ -331,7 +271,7 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
// run an update instantly.
getLog().info("Performing initial data load...");
updateTaskBuffer.requestDirectly();
new UpdateTask(this, true).run();
// register tasks
scheduler.asyncRepeating(new ExpireTemporaryTask(this), 60L);
@@ -339,7 +279,12 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
// register permissions
try {
registerPermissions(getConfiguration().get(ConfigKeys.COMMANDS_ALLOW_OP) ? PermissionDefault.OP : PermissionDefault.FALSE);
PluginManager pm = getServer().getPluginManager();
PermissionDefault permDefault = getConfiguration().get(ConfigKeys.COMMANDS_ALLOW_OP) ? PermissionDefault.OP : PermissionDefault.FALSE;
for (CommandPermission p : CommandPermission.values()) {
pm.addPermission(new org.bukkit.permissions.Permission(p.getPermission(), permDefault));
}
} catch (Exception e) {
// this throws an exception if the plugin is /reloaded, grr
}
@@ -374,18 +319,23 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
@Override
public void onDisable() {
if (checkInvalidVersion()) {
return;
}
// Switch back to the LP executor, the bukkit one won't allow new tasks
scheduler.setUseBukkitAsync(false);
started = false;
defaultsProvider.close();
permissionVault.setShutdown(true);
verboseHandler.setShutdown(true);
permissionVault.shutdown();
verboseHandler.shutdown();
// uninject from players
for (Player player : getServer().getOnlinePlayers()) {
try {
Injector.unInject(player, false, false);
Injector.unInject(player, false);
} catch (Exception e) {
e.printStackTrace();
}
@@ -396,11 +346,14 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
final User user = getUserManager().getIfLoaded(getUuidCache().getUUID(player.getUniqueId()));
if (user != null) {
user.unregisterData();
user.getUserData().invalidateCaches();
getUserManager().unload(user);
}
}
// uninject subscription map
SubscriptionMapInjector.uninject();
getLog().info("Closing storage...");
storage.shutdown();
@@ -413,7 +366,7 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
messagingService.close();
}
ApiHandler.unregisterProvider();
ApiSingletonUtils.unregisterProvider();
getServer().getServicesManager().unregisterAll(this);
if (vaultHookManager != null) {
@@ -426,32 +379,7 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
// Bukkit will do this again when #onDisable completes, but we do it early to prevent NPEs elsewhere.
getServer().getScheduler().cancelTasks(this);
HandlerList.unregisterAll(this);
// Null everything
ignoringLogs = null;
vaultHookManager = null;
configuration = null;
userManager = null;
groupManager = null;
trackManager = null;
storage = null;
fileWatcher = null;
messagingService = null;
uuidCache = null;
listener = null;
apiProvider = null;
log = null;
defaultsProvider = null;
childPermissionProvider = null;
localeManager = null;
cachedStateManager = null;
contextManager = null;
worldCalculator = null;
calculatorFactory = null;
updateTaskBuffer = null;
verboseHandler = null;
senderFactory = null;
permissionVault = null;
getLog().info("Goodbye!");
}
public void tryVaultHook(boolean force) {
@@ -472,32 +400,25 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
}
}
@Override
public void onUserRefresh(User user) {
LPPermissible lpp = Injector.getPermissible(uuidCache.getExternalUUID(user.getUuid()));
if (lpp != null) {
lpp.updateSubscriptions();
public void refreshAutoOp(User user, Player player) {
if (user == null) {
return;
}
if (getConfiguration().get(ConfigKeys.AUTO_OP)) {
Map<String, Boolean> backing = user.getUserData().getPermissionData(contextManager.getApplicableContexts(player)).getImmutableBacking();
boolean op = Optional.ofNullable(backing.get("luckperms.autoop")).orElse(false);
player.setOp(op);
}
}
public void refreshAutoOp(Player player) {
if (getConfiguration().get(ConfigKeys.AUTO_OP)) {
try {
LPPermissible permissible = Injector.getPermissible(player.getUniqueId());
if (permissible == null || !permissible.getActive().get()) {
return;
}
@Override
public Optional<ExtendedMessagingService> getMessagingService() {
return Optional.ofNullable(messagingService);
}
User user = permissible.getUser();
if (user == null) {
return;
}
Map<String, Boolean> backing = user.getUserData().getPermissionData(permissible.calculateContexts()).getImmutableBacking();
boolean op = Optional.ofNullable(backing.get("luckperms.autoop")).orElse(false);
player.setOp(op);
} catch (Exception ignored) {}
}
public Optional<FileWatcher> getFileWatcher() {
return Optional.ofNullable(fileWatcher);
}
@Override
@@ -561,13 +482,13 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
}
@Override
public List<String> getPlayerList() {
return getServer().getOnlinePlayers().stream().map(Player::getName).collect(Collectors.toList());
public Stream<String> getPlayerList() {
return getServer().getOnlinePlayers().stream().map(Player::getName);
}
@Override
public Set<UUID> getOnlinePlayers() {
return getServer().getOnlinePlayers().stream().map(Player::getUniqueId).collect(Collectors.toSet());
public Stream<UUID> getOnlinePlayers() {
return getServer().getOnlinePlayers().stream().map(Player::getUniqueId);
}
@Override
@@ -577,10 +498,11 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
}
@Override
public List<Sender> getOnlineSenders() {
return getServer().getOnlinePlayers().stream()
.map(p -> getSenderFactory().wrap(p))
.collect(Collectors.toList());
public Stream<Sender> getOnlineSenders() {
return Stream.concat(
Stream.of(getConsoleSender()),
getServer().getOnlinePlayers().stream().map(p -> getSenderFactory().wrap(p))
);
}
@Override
@@ -589,86 +511,20 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
}
@Override
public Set<Contexts> getPreProcessContexts(boolean op) {
Set<ContextSet> c = new HashSet<>();
c.add(ContextSet.empty());
c.add(ContextSet.singleton("server", getConfiguration().get(ConfigKeys.SERVER)));
// Pre process all worlds
c.addAll(getServer().getWorlds().stream()
.map(World::getName)
.map(s -> {
MutableContextSet set = MutableContextSet.create();
set.add("server", getConfiguration().get(ConfigKeys.SERVER));
set.add("world", s);
set.addAll(configuration.getContextsFile().getStaticContexts());
return set.makeImmutable();
})
.collect(Collectors.toList())
);
// Pre process the separate Vault server, if any
if (!getConfiguration().get(ConfigKeys.SERVER).equals(getConfiguration().get(ConfigKeys.VAULT_SERVER))) {
c.add(ContextSet.singleton("server", getConfiguration().get(ConfigKeys.VAULT_SERVER)));
c.addAll(getServer().getWorlds().stream()
.map(World::getName)
.map(s -> {
MutableContextSet set = MutableContextSet.create();
set.add("server", getConfiguration().get(ConfigKeys.VAULT_SERVER));
set.add("world", s);
set.addAll(configuration.getContextsFile().getStaticContexts());
return set.makeImmutable();
})
.collect(Collectors.toList())
);
}
Set<Contexts> contexts = new HashSet<>();
// Convert to full Contexts
contexts.addAll(c.stream()
.map(set -> new Contexts(
set,
getConfiguration().get(ConfigKeys.INCLUDING_GLOBAL_PERMS),
getConfiguration().get(ConfigKeys.INCLUDING_GLOBAL_WORLD_PERMS),
true,
getConfiguration().get(ConfigKeys.APPLYING_GLOBAL_GROUPS),
getConfiguration().get(ConfigKeys.APPLYING_GLOBAL_WORLD_GROUPS),
op
))
.collect(Collectors.toSet())
);
// Check for and include varying Vault config options
boolean vaultDiff = getConfiguration().get(ConfigKeys.VAULT_INCLUDING_GLOBAL) != getConfiguration().get(ConfigKeys.INCLUDING_GLOBAL_PERMS) ||
!getConfiguration().get(ConfigKeys.INCLUDING_GLOBAL_WORLD_PERMS) ||
!getConfiguration().get(ConfigKeys.APPLYING_GLOBAL_GROUPS) ||
!getConfiguration().get(ConfigKeys.APPLYING_GLOBAL_WORLD_GROUPS);
if (vaultDiff) {
contexts.addAll(c.stream()
.map(map -> new Contexts(map, getConfiguration().get(ConfigKeys.VAULT_INCLUDING_GLOBAL), true, true, true, true, op))
.collect(Collectors.toSet())
);
}
return contexts;
}
@Override
public LinkedHashMap<String, Object> getExtraInfo() {
LinkedHashMap<String, Object> map = new LinkedHashMap<>();
public Map<String, Object> getExtraInfo() {
Map<String, Object> map = new LinkedHashMap<>();
map.put("Vault Enabled", vaultHookManager != null);
map.put("Bukkit Defaults count", defaultsProvider.size());
map.put("Bukkit Child Permissions count", childPermissionProvider.getPermissions().size());
return map;
}
private void registerPermissions(PermissionDefault def) {
PluginManager pm = getServer().getPluginManager();
for (CommandPermission p : CommandPermission.values()) {
pm.addPermission(new org.bukkit.permissions.Permission(p.getPermission(), def));
private static boolean checkInvalidVersion() {
try {
Class.forName("com.google.gson.JsonElement");
return false;
} catch (ClassNotFoundException e) {
return true;
}
}
}
@@ -31,13 +31,11 @@ import com.google.common.collect.ImmutableList;
import me.lucko.luckperms.api.Contexts;
import me.lucko.luckperms.bukkit.LPBukkitPlugin;
import me.lucko.luckperms.bukkit.model.Injector;
import me.lucko.luckperms.bukkit.model.LPPermissible;
import me.lucko.luckperms.bukkit.processors.AttachmentProcessor;
import me.lucko.luckperms.bukkit.processors.ChildProcessor;
import me.lucko.luckperms.bukkit.processors.DefaultsProcessor;
import me.lucko.luckperms.common.calculators.AbstractCalculatorFactory;
import me.lucko.luckperms.common.calculators.PermissionCalculator;
import me.lucko.luckperms.common.calculators.PermissionCalculatorMetadata;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.model.User;
import me.lucko.luckperms.common.processors.MapProcessor;
@@ -46,7 +44,6 @@ import me.lucko.luckperms.common.processors.RegexProcessor;
import me.lucko.luckperms.common.processors.WildcardProcessor;
import java.util.List;
import java.util.UUID;
@AllArgsConstructor
public class BukkitCalculatorFactory extends AbstractCalculatorFactory {
@@ -62,14 +59,6 @@ public class BukkitCalculatorFactory extends AbstractCalculatorFactory {
processors.add(new ChildProcessor(plugin.getChildPermissionProvider()));
}
if (plugin.getConfiguration().get(ConfigKeys.APPLY_BUKKIT_ATTACHMENT_PERMISSIONS)) {
final UUID uuid = plugin.getUuidCache().getExternalUUID(user.getUuid());
processors.add(new AttachmentProcessor(() -> {
LPPermissible permissible = Injector.getPermissible(uuid);
return permissible == null ? null : permissible.getAttachmentPermissions();
}));
}
if (plugin.getConfiguration().get(ConfigKeys.APPLYING_REGEX)) {
processors.add(new RegexProcessor());
}
@@ -82,7 +71,8 @@ public class BukkitCalculatorFactory extends AbstractCalculatorFactory {
processors.add(new DefaultsProcessor(contexts.isOp(), plugin.getDefaultsProvider()));
}
return registerCalculator(new PermissionCalculator(plugin, user.getFriendlyName(), processors.build()));
PermissionCalculatorMetadata meta = PermissionCalculatorMetadata.of(user.getFriendlyName(), contexts.getContexts());
return registerCalculator(new PermissionCalculator(plugin, meta, processors.build()));
}
@Override
@@ -91,7 +91,7 @@ public class BukkitJsonMessageHandler {
setup(player);
setup = true;
return true;
} catch (Exception e) {
} catch (Throwable e) {
triedAndFailed = true;
return false;
}
@@ -25,10 +25,10 @@
package me.lucko.luckperms.bukkit.compat;
import me.lucko.luckperms.common.constants.Constants;
import me.lucko.luckperms.common.utils.TextUtils;
import net.kyori.text.Component;
import net.kyori.text.serializer.ComponentSerializer;
import net.kyori.text.serializer.ComponentSerializers;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
@@ -48,7 +48,7 @@ public class MessageHandler {
public void sendJsonMessage(CommandSender sender, Component message) {
if (ReflectionUtil.isChatCompatible() && sender instanceof Player) {
Player player = (Player) sender;
String json = ComponentSerializer.serialize(message);
String json = ComponentSerializers.JSON.serialize(message);
// Try Bukkit.
if (bukkitHandler.sendJsonMessage(player, json)) {
@@ -62,7 +62,7 @@ public class MessageHandler {
}
// Fallback to Bukkit
sender.sendMessage(ComponentSerializer.toLegacy(message, Constants.COLOR_CHAR));
sender.sendMessage(TextUtils.toLegacy(message));
}
private static boolean isSpigot() {
@@ -0,0 +1,53 @@
/*
* 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.contexts;
import me.lucko.luckperms.api.Contexts;
import me.lucko.luckperms.api.context.ImmutableContextSet;
import me.lucko.luckperms.bukkit.LPBukkitPlugin;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.contexts.AbstractContextManager;
import org.bukkit.entity.Player;
public class BukkitContextManager extends AbstractContextManager<Player> {
public BukkitContextManager(LPBukkitPlugin plugin) {
super(plugin, Player.class);
}
@Override
public Contexts formContexts(Player subject, ImmutableContextSet contextSet) {
return new Contexts(
contextSet,
plugin.getConfiguration().get(ConfigKeys.INCLUDING_GLOBAL_PERMS),
plugin.getConfiguration().get(ConfigKeys.INCLUDING_GLOBAL_WORLD_PERMS),
true,
plugin.getConfiguration().get(ConfigKeys.APPLYING_GLOBAL_GROUPS),
plugin.getConfiguration().get(ConfigKeys.APPLYING_GLOBAL_WORLD_GROUPS),
subject.isOp()
);
}
}
@@ -23,10 +23,11 @@
* SOFTWARE.
*/
package me.lucko.luckperms.bukkit;
package me.lucko.luckperms.bukkit.listeners;
import lombok.RequiredArgsConstructor;
import me.lucko.luckperms.bukkit.LPBukkitPlugin;
import me.lucko.luckperms.bukkit.model.Injector;
import me.lucko.luckperms.bukkit.model.LPPermissible;
import me.lucko.luckperms.common.config.ConfigKeys;
@@ -39,11 +40,8 @@ import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.AsyncPlayerPreLoginEvent;
import org.bukkit.event.player.PlayerChangedWorldEvent;
import org.bukkit.event.player.PlayerCommandPreprocessEvent;
import org.bukkit.event.player.PlayerLoginEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.event.server.PluginEnableEvent;
import java.util.Collections;
import java.util.HashSet;
@@ -52,7 +50,7 @@ import java.util.UUID;
import java.util.concurrent.TimeUnit;
@RequiredArgsConstructor
public class BukkitListener implements Listener {
public class BukkitConnectionListener implements Listener {
private final LPBukkitPlugin plugin;
private final Set<UUID> deniedAsyncLogin = Collections.synchronizedSet(new HashSet<>());
@@ -71,6 +69,10 @@ public class BukkitListener implements Listener {
ex.printStackTrace();
}
if (plugin.getConfiguration().get(ConfigKeys.DEBUG_LOGINS)) {
plugin.getLog().info("Processing pre-login for " + e.getUniqueId() + " - " + e.getName());
}
/* there was an issue connecting to the DB, performing file i/o, etc.
we don't let players join in this case, because it means they can connect to the server without their permissions data.
some server admins rely on negating perms to stop users from causing damage etc, so it's really important that
@@ -98,7 +100,8 @@ public class BukkitListener implements Listener {
- creating a user instance in the UserManager for this connection.
- setting up cached data. */
try {
LoginHelper.loadUser(plugin, e.getUniqueId(), e.getName(), false);
User user = LoginHelper.loadUser(plugin, e.getUniqueId(), e.getName(), false);
plugin.getApiProvider().getEventFactory().handleUserLoginProcess(e.getUniqueId(), e.getName(), user);
} catch (Exception ex) {
ex.printStackTrace();
@@ -139,6 +142,11 @@ public class BukkitListener implements Listener {
At this point, the users data should be present and loaded. */
final Player player = e.getPlayer();
if (plugin.getConfiguration().get(ConfigKeys.DEBUG_LOGINS)) {
plugin.getLog().info("Processing login for " + player.getUniqueId() + " - " + player.getName());
}
final User user = plugin.getUserManager().getIfLoaded(plugin.getUuidCache().getUUID(player.getUniqueId()));
/* User instance is null for whatever reason. Could be that it was unloaded between asyncpre and now. */
@@ -163,12 +171,7 @@ public class BukkitListener implements Listener {
t.printStackTrace();
}
plugin.refreshAutoOp(player);
// We assume all users are not op, but those who are need extra calculation.
if (player.isOp()) {
plugin.doAsync(() -> user.getUserData().preCalculate(plugin.getPreProcessContexts(true)));
}
plugin.refreshAutoOp(user, player);
}
@EventHandler(priority = EventPriority.MONITOR)
@@ -196,7 +199,7 @@ public class BukkitListener implements Listener {
}
// everything is going well. login was processed ok, this is just to refresh auto-op status.
plugin.refreshAutoOp(e.getPlayer());
plugin.refreshAutoOp(plugin.getUserManager().getIfLoaded(e.getPlayer().getUniqueId()), e.getPlayer());
}
// Wait until the last priority to unload, so plugins can still perform permission checks on this event
@@ -206,7 +209,7 @@ public class BukkitListener implements Listener {
// Remove the custom permissible
try {
Injector.unInject(player, true, true);
Injector.unInject(player, true);
} catch (Exception ex) {
ex.printStackTrace();
}
@@ -220,33 +223,4 @@ public class BukkitListener implements Listener {
plugin.getUserManager().scheduleUnload(player.getUniqueId());
}
@EventHandler
public void onPlayerCommand(PlayerCommandPreprocessEvent e) {
if (plugin.getConfiguration().get(ConfigKeys.OPS_ENABLED)) {
return;
}
String s = e.getMessage().substring(1).toLowerCase()
.replace("bukkit:", "")
.replace("spigot:", "")
.replace("minecraft:", "");
if (s.equals("op") || s.startsWith("op ") || s.equals("deop") || s.startsWith("deop ")) {
e.setCancelled(true);
e.getPlayer().sendMessage(Message.OP_DISABLED.asString(plugin.getLocaleManager()));
}
}
@EventHandler
public void onPluginEnable(PluginEnableEvent e) {
if (e.getPlugin().getName().equalsIgnoreCase("Vault")) {
plugin.tryVaultHook(true);
}
}
@EventHandler(priority = EventPriority.LOWEST)
public void onWorldChange(PlayerChangedWorldEvent e) {
plugin.getContextManager().invalidateCache(e.getPlayer());
plugin.refreshAutoOp(e.getPlayer());
}
}
@@ -0,0 +1,100 @@
/*
* 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 lombok.RequiredArgsConstructor;
import me.lucko.luckperms.bukkit.LPBukkitPlugin;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.locale.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;
@RequiredArgsConstructor
public class BukkitPlatformListener implements Listener {
private final LPBukkitPlugin plugin;
@EventHandler
public void onPlayerCommand(PlayerCommandPreprocessEvent e) {
handleCommand(e.getPlayer(), e.getMessage().toLowerCase(), e);
}
@EventHandler
public void onServerCommand(ServerCommandEvent e) {
handleCommand(e.getSender(), e.getCommand().toLowerCase(), e);
}
@EventHandler
public void onRemoteServerCommand(RemoteServerCommandEvent e) {
handleCommand(e.getSender(), e.getCommand().toLowerCase(), e);
}
private void handleCommand(CommandSender sender, String s, Cancellable event) {
if (s.isEmpty()) {
return;
}
if (plugin.getConfiguration().get(ConfigKeys.OPS_ENABLED)) {
return;
}
if (s.charAt(0) == '/') {
s = s.substring(1);
}
if (s.startsWith("minecraft:")) {
s = s.substring("minecraft:".length());
}
if (s.equals("op") || s.startsWith("op ") || s.equals("deop") || s.startsWith("deop ")) {
event.setCancelled(true);
sender.sendMessage(Message.OP_DISABLED.asString(plugin.getLocaleManager()));
}
}
@EventHandler
public void onPluginEnable(PluginEnableEvent e) {
if (e.getPlugin().getName().equalsIgnoreCase("Vault")) {
plugin.tryVaultHook(true);
}
}
@EventHandler(priority = EventPriority.LOWEST)
public void onWorldChange(PlayerChangedWorldEvent e) {
plugin.getContextManager().invalidateCache(e.getPlayer());
plugin.refreshAutoOp(plugin.getUserManager().getIfLoaded(e.getPlayer().getUniqueId()), e.getPlayer());
}
}
@@ -0,0 +1,55 @@
/*
* 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.messaging;
import me.lucko.luckperms.bukkit.LPBukkitPlugin;
import me.lucko.luckperms.common.messaging.ExtendedMessagingService;
import me.lucko.luckperms.common.messaging.MessagingFactory;
public class BukkitMessagingFactory extends MessagingFactory<LPBukkitPlugin> {
public BukkitMessagingFactory(LPBukkitPlugin plugin) {
super(plugin);
}
@Override
protected ExtendedMessagingService getServiceFor(String messagingType) {
if (messagingType.equals("bungee")) {
BungeeMessagingService bungeeMessaging = new BungeeMessagingService(getPlugin());
bungeeMessaging.init();
return bungeeMessaging;
} else if (messagingType.equals("lilypad")) {
if (getPlugin().getServer().getPluginManager().getPlugin("LilyPad-Connect") == null) {
getPlugin().getLog().warn("LilyPad-Connect plugin not present.");
} else {
LilyPadMessagingService lilyPadMessaging = new LilyPadMessagingService(getPlugin());
lilyPadMessaging.init();
return lilyPadMessaging;
}
}
return super.getServiceFor(messagingType);
}
}
@@ -32,6 +32,7 @@ import com.google.common.io.ByteStreams;
import me.lucko.luckperms.bukkit.LPBukkitPlugin;
import me.lucko.luckperms.common.messaging.AbstractMessagingService;
import me.lucko.luckperms.common.messaging.ExtendedMessagingService;
import org.bukkit.entity.Player;
import org.bukkit.plugin.messaging.PluginMessageListener;
@@ -40,7 +41,7 @@ import org.bukkit.scheduler.BukkitRunnable;
import java.util.Collection;
/**
* An implementation of {@link me.lucko.luckperms.api.MessagingService} using the plugin messaging channels.
* An implementation of {@link ExtendedMessagingService} using the plugin messaging channels.
*/
public class BungeeMessagingService extends AbstractMessagingService implements PluginMessageListener {
private final LPBukkitPlugin plugin;
@@ -62,7 +63,7 @@ public class BungeeMessagingService extends AbstractMessagingService implements
}
@Override
protected void sendMessage(String channel, String message) {
protected void sendMessage(String message) {
new BukkitRunnable() {
@Override
public void run() {
@@ -77,7 +78,7 @@ public class BungeeMessagingService extends AbstractMessagingService implements
byte[] data = out.toByteArray();
p.sendPluginMessage(plugin, channel, data);
p.sendPluginMessage(plugin, CHANNEL, data);
cancel();
}
}.runTaskTimer(plugin, 1L, 100L);
@@ -92,6 +93,6 @@ public class BungeeMessagingService extends AbstractMessagingService implements
ByteArrayDataInput in = ByteStreams.newDataInput(bytes);
String msg = in.readUTF();
onMessage(s, msg, null);
onMessage(msg, null);
}
}
@@ -27,6 +27,7 @@ package me.lucko.luckperms.bukkit.messaging;
import me.lucko.luckperms.bukkit.LPBukkitPlugin;
import me.lucko.luckperms.common.messaging.AbstractMessagingService;
import me.lucko.luckperms.common.messaging.ExtendedMessagingService;
import lilypad.client.connect.api.Connect;
import lilypad.client.connect.api.event.EventListener;
@@ -38,7 +39,7 @@ import java.io.UnsupportedEncodingException;
import java.util.Collections;
/**
* An implementation of {@link me.lucko.luckperms.api.MessagingService} using LilyPad.
* An implementation of {@link ExtendedMessagingService} using LilyPad.
*/
public class LilyPadMessagingService extends AbstractMessagingService {
private final LPBukkitPlugin plugin;
@@ -60,11 +61,11 @@ public class LilyPadMessagingService extends AbstractMessagingService {
}
@Override
protected void sendMessage(String channel, String message) {
protected void sendMessage(String message) {
MessageRequest request;
try {
request = new MessageRequest(Collections.emptyList(), channel, message);
request = new MessageRequest(Collections.emptyList(), CHANNEL, message);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
return;
@@ -79,12 +80,17 @@ public class LilyPadMessagingService extends AbstractMessagingService {
@EventListener
public void onMessage(MessageEvent event) {
plugin.doAsync(() -> {
plugin.getScheduler().doAsync(() -> {
try {
String channel = event.getChannel();
if (!channel.equals(CHANNEL)) {
return;
}
String message = event.getMessageAsString();
onMessage(channel, message, null);
onMessage(message, null);
} catch (Exception e) {
e.printStackTrace();
}
@@ -174,7 +174,7 @@ public class MigrationBPermissions extends SubCommand<Object> {
}
// Make a LuckPerms user for the one being migrated.
plugin.getStorage().loadUser(uuid, "null").join();
plugin.getStorage().loadUser(uuid, null).join();
User lpUser = plugin.getUserManager().getIfLoaded(uuid);
migrateHolder(world, user, lpUser);
@@ -262,7 +262,7 @@ public class MigrationGroupManager extends SubCommand<Object> {
log.log("Starting user migration.");
AtomicInteger userCount = new AtomicInteger(0);
for (Map.Entry<UUID, Set<Node>> e : users.entrySet()) {
plugin.getStorage().loadUser(e.getKey(), "null").join();
plugin.getStorage().loadUser(e.getKey(), null).join();
me.lucko.luckperms.common.model.User user = plugin.getUserManager().getIfLoaded(e.getKey());
for (Node node : e.getValue()) {
@@ -143,6 +143,7 @@ public class MigrationPermissionsEx extends SubCommand<Object> {
continue;
}
// load in a user instance
plugin.getStorage().loadUser(u, user.getName()).join();
User lpUser = plugin.getUserManager().getIfLoaded(u);
@@ -32,6 +32,7 @@ import com.github.cheesesoftware.PowerfulPermsAPI.Group;
import com.github.cheesesoftware.PowerfulPermsAPI.Permission;
import com.github.cheesesoftware.PowerfulPermsAPI.PermissionManager;
import com.github.cheesesoftware.PowerfulPermsAPI.PowerfulPermsPlugin;
import com.google.common.collect.ImmutableSet;
import me.lucko.luckperms.api.Node;
import me.lucko.luckperms.api.event.cause.CreationCause;
@@ -94,7 +95,7 @@ public class MigrationPowerfulPerms extends SubCommand<Object> {
if (type == null || type != StorageType.MYSQL) {
// We need to load the Hikari/MySQL stuff.
DependencyManager.loadDependencies(plugin, DependencyManager.STORAGE_DEPENDENCIES.get(StorageType.MYSQL));
DependencyManager.loadStorageDependencies(plugin, ImmutableSet.of(StorageType.MYSQL));
}
String address = args.get(0);
@@ -230,7 +231,7 @@ public class MigrationPowerfulPerms extends SubCommand<Object> {
for (UUID uuid : uuids) {
// Create a LuckPerms user for the UUID
plugin.getStorage().loadUser(uuid, "null").join();
plugin.getStorage().loadUser(uuid, null).join();
User user = plugin.getUserManager().getIfLoaded(uuid);
List<Permission> permissions = joinFuture(pm.getPlayerOwnPermissions(uuid));
@@ -263,10 +264,13 @@ public class MigrationPowerfulPerms extends SubCommand<Object> {
user.setPermission(NodeFactory.makeSuffixNode(maxWeight, suffix).build());
}
String primary = joinFuture(pm.getPlayerPrimaryGroup(uuid)).getName().toLowerCase();
if (!primary.equals("default")) {
user.setPermission(NodeFactory.make("group." + primary));
user.getPrimaryGroup().setStoredValue(primary);
Group primaryGroup = joinFuture(pm.getPlayerPrimaryGroup(uuid));
if (primaryGroup != null && primaryGroup.getName() != null) {
String primary = primaryGroup.getName().toLowerCase();
if (!primary.equals("default")) {
user.setPermission(NodeFactory.make("group." + primary));
user.getPrimaryGroup().setStoredValue(primary);
}
}
plugin.getUserManager().cleanup(user);
@@ -45,63 +45,17 @@ public class DummyPermissible implements Permissible {
onRefresh.run();
}
@Override
public Set<PermissionAttachmentInfo> getEffectivePermissions() {
return Collections.emptySet();
}
@Override public Set<PermissionAttachmentInfo> getEffectivePermissions() { return Collections.emptySet(); }
@Override public boolean isPermissionSet(String name) { return false; }
@Override public boolean isPermissionSet(Permission perm) { return false; }
@Override public boolean hasPermission(String name) { return false; }
@Override public boolean hasPermission(Permission perm) { return false; }
@Override public PermissionAttachment addAttachment(Plugin plugin, String name, boolean value) { return null; }
@Override public PermissionAttachment addAttachment(Plugin plugin) { return null; }
@Override public PermissionAttachment addAttachment(Plugin plugin, String name, boolean value, int ticks) { return null; }
@Override public PermissionAttachment addAttachment(Plugin plugin, int ticks) { return null; }
@Override public void removeAttachment(PermissionAttachment attachment) {}
@Override public boolean isOp() { return false; }
@Override public void setOp(boolean value) {}
@Override
public boolean isPermissionSet(String name) {
return false;
}
@Override
public boolean isPermissionSet(Permission perm) {
return false;
}
@Override
public boolean hasPermission(String name) {
return false;
}
@Override
public boolean hasPermission(Permission perm) {
return false;
}
@Override
public PermissionAttachment addAttachment(Plugin plugin, String name, boolean value) {
return null;
}
@Override
public PermissionAttachment addAttachment(Plugin plugin) {
return null;
}
@Override
public PermissionAttachment addAttachment(Plugin plugin, String name, boolean value, int ticks) {
return null;
}
@Override
public PermissionAttachment addAttachment(Plugin plugin, int ticks) {
return null;
}
@Override
public void removeAttachment(PermissionAttachment attachment) {
}
@Override
public boolean isOp() {
return false;
}
@Override
public void setOp(boolean value) {
}
}
@@ -35,77 +35,25 @@ import java.util.Collections;
import java.util.Set;
public class DummyPermissibleBase extends PermissibleBase {
public DummyPermissibleBase() {
public static final DummyPermissibleBase INSTANCE = new DummyPermissibleBase();
private DummyPermissibleBase() {
super(null);
}
@Override
public boolean isOp() {
return false;
}
@Override public boolean isOp() { return false; }
@Override public void setOp(boolean value) {}
@Override public boolean isPermissionSet(String name) { return false; }
@Override public boolean isPermissionSet(Permission perm) { return false; }
@Override public boolean hasPermission(String inName) { return false; }
@Override public boolean hasPermission(Permission perm) { return false; }
@Override public PermissionAttachment addAttachment(Plugin plugin, String name, boolean value) { return null; }
@Override public PermissionAttachment addAttachment(Plugin plugin) { return null; }
@Override public void removeAttachment(PermissionAttachment attachment) {}
@Override public void recalculatePermissions() {}
@Override public void clearPermissions() {}
@Override public PermissionAttachment addAttachment(Plugin plugin, String name, boolean value, int ticks) { return null; }
@Override public PermissionAttachment addAttachment(Plugin plugin, int ticks) { return null; }
@Override public Set<PermissionAttachmentInfo> getEffectivePermissions() { return Collections.emptySet(); }
@Override
public void setOp(boolean value) {
}
@Override
public boolean isPermissionSet(String name) {
return false;
}
@Override
public boolean isPermissionSet(Permission perm) {
return false;
}
@Override
public boolean hasPermission(String inName) {
return false;
}
@Override
public boolean hasPermission(Permission perm) {
return false;
}
@Override
public PermissionAttachment addAttachment(Plugin plugin, String name, boolean value) {
return null;
}
@Override
public PermissionAttachment addAttachment(Plugin plugin) {
return null;
}
@Override
public void removeAttachment(PermissionAttachment attachment) {
}
@Override
public void recalculatePermissions() {
}
@Override
public synchronized void clearPermissions() {
}
@Override
public PermissionAttachment addAttachment(Plugin plugin, String name, boolean value, int ticks) {
return null;
}
@Override
public PermissionAttachment addAttachment(Plugin plugin, int ticks) {
return null;
}
@Override
public Set<PermissionAttachmentInfo> getEffectivePermissions() {
return Collections.emptySet();
}
}
@@ -0,0 +1,78 @@
/*
* 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.model;
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 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 File getDataFolder() { return null; }
@Override public PluginDescriptionFile getDescription() { return null; }
@Override public FileConfiguration getConfig() { return null; }
@Override public InputStream getResource(String s) { return null; }
@Override public void saveConfig() {}
@Override public void saveDefaultConfig() {}
@Override public void saveResource(String s, boolean b) {}
@Override public void reloadConfig() {}
@Override public PluginLoader getPluginLoader() { return null; }
@Override public 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(String s, String s1) { return null; }
@Override public Logger getLogger() { return null; }
@Override public String getName() { return null; }
@Override public boolean onCommand(CommandSender commandSender, Command command, String s, String[] strings) { return false; }
@Override public List<String> onTabComplete(CommandSender commandSender, Command command, String s, String[] strings) { return null; }
}
@@ -35,9 +35,6 @@ import org.bukkit.permissions.PermissionAttachment;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
/**
* Injects a {@link LPPermissible} into a {@link Player}.
@@ -47,7 +44,6 @@ import java.util.concurrent.ConcurrentHashMap;
*/
@UtilityClass
public class Injector {
private static final Map<UUID, LPPermissible> INJECTED_PERMISSIBLES = new ConcurrentHashMap<>();
/**
* All permission checks made on standard Bukkit objects are effectively proxied to a
@@ -55,15 +51,17 @@ public class Injector {
*
* This field is where the permissible is stored on a HumanEntity.
*/
private static Field humanEntityPermissibleField;
private static final Field HUMAN_ENTITY_PERMISSIBLE_FIELD;
/**
* The field where attachments are stored on a permissible base.
*/
private static Field permissibleBaseAttachmentsField;
private static final Field PERMISSIBLE_BASE_ATTACHMENTS_FIELD;
private static Throwable cachedThrowable = null;
static {
Field humanEntityPermissibleField;
Field permissibleBaseAttachmentsField;
try {
// Catch all. If this setup doesn't fully complete without
// exceptions, then the Injector will not work.
@@ -85,9 +83,11 @@ public class Injector {
permissibleBaseAttachmentsField.setAccessible(true);
} catch (Throwable t) {
cachedThrowable = t;
t.printStackTrace();
throw new RuntimeException("Injector did not init successfully.", t);
}
HUMAN_ENTITY_PERMISSIBLE_FIELD = humanEntityPermissibleField;
PERMISSIBLE_BASE_ATTACHMENTS_FIELD = permissibleBaseAttachmentsField;
}
/**
@@ -99,13 +99,8 @@ public class Injector {
*/
public static void inject(Player player, LPPermissible newPermissible) throws Exception {
// make sure the class inited without errors, otherwise, print a trace
if (cachedThrowable != null) {
throw new RuntimeException("Injector did not init successfully.", cachedThrowable);
}
// get the existing PermissibleBase held by the player
PermissibleBase oldPermissible = (PermissibleBase) humanEntityPermissibleField.get(player);
PermissibleBase oldPermissible = (PermissibleBase) HUMAN_ENTITY_PERMISSIBLE_FIELD.get(player);
// seems we have already injected into this player.
if (oldPermissible instanceof LPPermissible) {
@@ -115,23 +110,18 @@ public class Injector {
// Move attachments over from the old permissible
//noinspection unchecked
List<PermissionAttachment> attachments = (List<PermissionAttachment>) permissibleBaseAttachmentsField.get(oldPermissible);
List<PermissionAttachment> attachments = (List<PermissionAttachment>) PERMISSIBLE_BASE_ATTACHMENTS_FIELD.get(oldPermissible);
newPermissible.addAttachments(attachments);
newPermissible.convertAndAddAttachments(attachments);
attachments.clear();
oldPermissible.clearPermissions();
// Setup the new permissible
newPermissible.getActive().set(true);
newPermissible.recalculatePermissions(false);
newPermissible.setOldPermissible(oldPermissible);
newPermissible.updateSubscriptionsAsync();
// inject the new instance
humanEntityPermissibleField.set(player, newPermissible);
// register the injection with the map
INJECTED_PERMISSIBLES.put(player.getUniqueId(), newPermissible);
HUMAN_ENTITY_PERMISSIBLE_FIELD.set(player, newPermissible);
}
/**
@@ -139,17 +129,12 @@ public class Injector {
*
* @param player the player to uninject from
* @param dummy if the replacement permissible should be a dummy.
* @param unsubscribe if the extracted permissible should unsubscribe itself. see {@link SubscriptionManager}.
* @throws Exception propagates any exceptions which were thrown during uninjection
*/
public static void unInject(Player player, boolean dummy, boolean unsubscribe) throws Exception {
// make sure the class inited without errors, otherwise, print a trace
if (cachedThrowable != null) {
throw new RuntimeException("Injector did not init successfully.", cachedThrowable);
}
public static void unInject(Player player, boolean dummy) throws Exception {
// gets the players current permissible.
PermissibleBase permissible = (PermissibleBase) humanEntityPermissibleField.get(player);
PermissibleBase permissible = (PermissibleBase) HUMAN_ENTITY_PERMISSIBLE_FIELD.get(player);
// only uninject if the permissible was a luckperms one.
if (permissible instanceof LPPermissible) {
@@ -158,43 +143,23 @@ public class Injector {
// clear all permissions
lpPermissible.clearPermissions();
// try to unsubscribe
if (unsubscribe) {
lpPermissible.unsubscribeFromAllAsync();
}
// set to inactive
lpPermissible.getActive().set(false);
// handle the replacement permissible.
if (dummy) {
// just inject a dummy class. this is used when we know the player is about to quit the server.
humanEntityPermissibleField.set(player, new DummyPermissibleBase());
HUMAN_ENTITY_PERMISSIBLE_FIELD.set(player, DummyPermissibleBase.INSTANCE);
} else {
// otherwise, inject the permissible they had when we first injected.
List<PermissionAttachment> lpAttachments = lpPermissible.getAttachments();
PermissibleBase newPb = lpPermissible.getOldPermissible();
if (newPb == null) {
newPb = new PermissibleBase(player);
}
//noinspection unchecked
List<PermissionAttachment> newPbAttachments = (List<PermissionAttachment>) permissibleBaseAttachmentsField.get(newPb);
newPbAttachments.addAll(lpAttachments);
lpAttachments.clear();
humanEntityPermissibleField.set(player, newPb);
HUMAN_ENTITY_PERMISSIBLE_FIELD.set(player, newPb);
}
}
INJECTED_PERMISSIBLES.remove(player.getUniqueId());
}
public static LPPermissible getPermissible(UUID uuid) {
return INJECTED_PERMISSIBLES.get(uuid);
}
}
@@ -32,29 +32,22 @@ import lombok.Setter;
import me.lucko.luckperms.api.Contexts;
import me.lucko.luckperms.api.Tristate;
import me.lucko.luckperms.bukkit.LPBukkitPlugin;
import me.lucko.luckperms.common.caching.UserCache;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.model.User;
import me.lucko.luckperms.common.verbose.CheckOrigin;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.permissions.PermissibleBase;
import org.bukkit.permissions.Permission;
import org.bukkit.permissions.PermissionAttachment;
import org.bukkit.permissions.PermissionAttachmentInfo;
import org.bukkit.permissions.PermissionRemovedExecutor;
import org.bukkit.plugin.Plugin;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.stream.Collectors;
/**
@@ -78,14 +71,11 @@ public class LPPermissible extends PermissibleBase {
private final User user;
// the player this permissible is injected into.
private final Player parent;
private final Player player;
// the luckperms plugin instance
private final LPBukkitPlugin plugin;
// the subscription manager, handling the players permission subscriptions.
private final SubscriptionManager subscriptions;
// the players previous permissible. (the one they had before this one was injected)
@Setter
private PermissibleBase oldPermissible = null;
@@ -93,30 +83,26 @@ public class LPPermissible extends PermissibleBase {
// if the permissible is currently active.
private final AtomicBoolean active = new AtomicBoolean(false);
// the permissions registered by PermissionAttachments.
// stored in this format, as that's what is used by #getEffectivePermissions
private final Map<String, PermissionAttachmentInfo> attachmentPermissions = new ConcurrentHashMap<>();
// the attachments hooked onto the permissible.
private final List<PermissionAttachment> attachments = Collections.synchronizedList(new ArrayList<>());
// this collection is only modified by the attachments themselves
final Set<LPPermissionAttachment> attachments = ConcurrentHashMap.newKeySet();
public LPPermissible(@NonNull Player parent, @NonNull User user, @NonNull LPBukkitPlugin plugin) {
super(parent);
public LPPermissible(@NonNull Player player, @NonNull User user, @NonNull LPBukkitPlugin plugin) {
super(player);
this.user = user;
this.parent = parent;
this.player = player;
this.plugin = plugin;
this.subscriptions = new SubscriptionManager(this);
}
@Override
public boolean isPermissionSet(@NonNull String permission) {
Tristate ts = user.getUserData().getPermissionData(calculateContexts()).getPermissionValue(permission);
Tristate ts = user.getUserData().getPermissionData(calculateContexts()).getPermissionValue(permission, CheckOrigin.PLATFORM_LOOKUP_CHECK);
return ts != Tristate.UNDEFINED || Permission.DEFAULT_PERMISSION.getValue(isOp());
}
@Override
public boolean isPermissionSet(@NonNull Permission permission) {
Tristate ts = user.getUserData().getPermissionData(calculateContexts()).getPermissionValue(permission.getName());
Tristate ts = user.getUserData().getPermissionData(calculateContexts()).getPermissionValue(permission.getName(), CheckOrigin.PLATFORM_LOOKUP_CHECK);
if (ts != Tristate.UNDEFINED) {
return true;
}
@@ -130,13 +116,13 @@ public class LPPermissible extends PermissibleBase {
@Override
public boolean hasPermission(@NonNull String permission) {
Tristate ts = user.getUserData().getPermissionData(calculateContexts()).getPermissionValue(permission);
Tristate ts = user.getUserData().getPermissionData(calculateContexts()).getPermissionValue(permission, CheckOrigin.PLATFORM_PERMISSION_CHECK);
return ts != Tristate.UNDEFINED ? ts.asBoolean() : Permission.DEFAULT_PERMISSION.getValue(isOp());
}
@Override
public boolean hasPermission(@NonNull Permission permission) {
Tristate ts = user.getUserData().getPermissionData(calculateContexts()).getPermissionValue(permission.getName());
Tristate ts = user.getUserData().getPermissionData(calculateContexts()).getPermissionValue(permission.getName(), CheckOrigin.PLATFORM_PERMISSION_CHECK);
if (ts != Tristate.UNDEFINED) {
return ts.asBoolean();
}
@@ -148,63 +134,15 @@ public class LPPermissible extends PermissibleBase {
}
}
/**
* Updates the players subscriptions asynchronously
*/
public void updateSubscriptionsAsync() {
if (!active.get()) {
return;
}
plugin.doAsync(this::updateSubscriptions);
}
/**
* Updates the players subscriptions
*/
public void updateSubscriptions() {
if (!active.get()) {
return;
}
UserCache cache = user.getUserData();
// calculate their "active" permissions
Set<String> ent = new HashSet<>(cache.getPermissionData(calculateContexts()).getImmutableBacking().keySet());
// include defaults, if enabled.
if (plugin.getConfiguration().get(ConfigKeys.APPLY_BUKKIT_DEFAULT_PERMISSIONS)) {
if (parent.isOp()) {
ent.addAll(plugin.getDefaultsProvider().getOpDefaults().keySet());
} else {
ent.addAll(plugin.getDefaultsProvider().getNonOpDefaults().keySet());
}
}
subscriptions.subscribe(ent);
}
/**
* Unsubscribes from all permissions asynchronously
*/
public void unsubscribeFromAllAsync() {
plugin.doAsync(this::unsubscribeFromAll);
}
/**
* Unsubscribes from all permissions
*/
public void unsubscribeFromAll() {
subscriptions.subscribe(Collections.emptySet());
}
/**
* Adds attachments to this permissible.
*
* @param attachments the attachments to add
*/
public void addAttachments(Collection<PermissionAttachment> attachments) {
this.attachments.addAll(attachments);
public void convertAndAddAttachments(Collection<PermissionAttachment> attachments) {
for (PermissionAttachment attachment : attachments) {
new LPPermissionAttachment(this, attachment).hook();
}
}
/**
@@ -213,147 +151,82 @@ public class LPPermissible extends PermissibleBase {
*
* @return the calculated contexts for the player.
*/
public Contexts calculateContexts() {
return plugin.getContextManager().getApplicableContexts(parent);
private Contexts calculateContexts() {
return plugin.getContextManager().getApplicableContexts(player);
}
@Override
public void setOp(boolean value) {
parent.setOp(value);
player.setOp(value);
}
@Override
public Set<PermissionAttachmentInfo> getEffectivePermissions() {
Set<PermissionAttachmentInfo> perms = new HashSet<>();
perms.addAll(attachmentPermissions.values());
perms.addAll(
user.getUserData().getPermissionData(calculateContexts()).getImmutableBacking().entrySet().stream()
.map(e -> new PermissionAttachmentInfo(parent, e.getKey(), null, e.getValue()))
.map(e -> new PermissionAttachmentInfo(player, e.getKey(), null, e.getValue()))
.collect(Collectors.toList())
);
return perms;
}
@Override
public PermissionAttachment addAttachment(@NonNull Plugin plugin, @NonNull String name, boolean value) {
if (!plugin.isEnabled()) {
throw new IllegalArgumentException("Plugin " + plugin.getDescription().getFullName() + " is not enabled");
}
PermissionAttachment result = addAttachment(plugin);
result.setPermission(name, value);
recalculatePermissions();
return result;
public LPPermissionAttachment addAttachment(Plugin plugin) {
LPPermissionAttachment ret = new LPPermissionAttachment(this, plugin);
ret.hook();
return ret;
}
@Override
public PermissionAttachment addAttachment(@NonNull Plugin plugin) {
if (!plugin.isEnabled()) {
throw new IllegalArgumentException("Plugin " + plugin.getDescription().getFullName() + " is not enabled");
}
PermissionAttachment result = new PermissionAttachment(plugin, parent);
attachments.add(result);
recalculatePermissions();
return result;
public PermissionAttachment addAttachment(Plugin plugin, @NonNull String name, boolean value) {
PermissionAttachment ret = addAttachment(plugin);
ret.setPermission(name, value);
return ret;
}
@Override
public PermissionAttachment addAttachment(@NonNull Plugin plugin, @NonNull String name, boolean value, int ticks) {
public LPPermissionAttachment addAttachment(@NonNull Plugin plugin, int ticks) {
if (!plugin.isEnabled()) {
throw new IllegalArgumentException("Plugin " + plugin.getDescription().getFullName() + " is not enabled");
}
PermissionAttachment result = addAttachment(plugin, ticks);
if (result != null) {
result.setPermission(name, value);
LPPermissionAttachment ret = addAttachment(plugin);
if (getPlugin().getServer().getScheduler().scheduleSyncDelayedTask(plugin, ret::remove, ticks) == -1) {
ret.remove();
throw new RuntimeException("Could not add PermissionAttachment to " + player + " for plugin " + plugin.getDescription().getFullName() + ": Scheduler returned -1");
}
return result;
return ret;
}
@Override
public PermissionAttachment addAttachment(@NonNull Plugin plugin, int ticks) {
if (!plugin.isEnabled()) {
throw new IllegalArgumentException("Plugin " + plugin.getDescription().getFullName() + " is not enabled");
}
PermissionAttachment result = addAttachment(plugin);
if (Bukkit.getServer().getScheduler().scheduleSyncDelayedTask(plugin, result::remove, ticks) == -1) {
Bukkit.getServer().getLogger().log(Level.WARNING, "Could not add PermissionAttachment to " + parent + " for plugin " + plugin.getDescription().getFullName() + ": Scheduler returned -1");
result.remove();
return null;
} else {
return result;
}
public LPPermissionAttachment addAttachment(Plugin plugin, @NonNull String name, boolean value, int ticks) {
LPPermissionAttachment ret = addAttachment(plugin, ticks);
ret.setPermission(name, value);
return ret;
}
@Override
public void removeAttachment(@NonNull PermissionAttachment attachment) {
if (attachments.contains(attachment)) {
attachments.remove(attachment);
PermissionRemovedExecutor ex = attachment.getRemovalCallback();
if (ex != null) {
ex.attachmentRemoved(attachment);
}
recalculatePermissions();
} else {
throw new IllegalArgumentException("Given attachment is not part of Permissible object " + parent);
if (!(attachment instanceof LPPermissionAttachment)) {
throw new IllegalArgumentException("Given attachment is not a LPPermissionAttachment.");
}
LPPermissionAttachment a = ((LPPermissionAttachment) attachment);
if (a.getPermissible() != this) {
throw new IllegalArgumentException("Attachment does not belong to this permissible.");
}
a.remove();
}
@Override
public void recalculatePermissions() {
recalculatePermissions(true);
}
public void recalculatePermissions(boolean invalidate) {
if (attachmentPermissions == null) {
return;
}
attachmentPermissions.clear();
for (PermissionAttachment attachment : attachments) {
calculateChildPermissions(attachment.getPermissions(), false, attachment);
}
if (invalidate) {
user.getUserData().invalidatePermissionCalculators();
}
// do nothing
}
@Override
public synchronized void clearPermissions() {
Set<String> perms = attachmentPermissions.keySet();
for (String name : perms) {
Bukkit.getServer().getPluginManager().unsubscribeFromPermission(name, parent);
}
attachmentPermissions.clear();
}
private void calculateChildPermissions(Map<String, Boolean> children, boolean invert, PermissionAttachment attachment) {
for (Map.Entry<String, Boolean> e : children.entrySet()) {
Permission perm = Bukkit.getServer().getPluginManager().getPermission(e.getKey());
boolean value = e.getValue() ^ invert;
String name = e.getKey().toLowerCase();
attachmentPermissions.put(name, new PermissionAttachmentInfo(parent, name, attachment, value));
Bukkit.getServer().getPluginManager().subscribeToPermission(name, parent);
if (perm != null) {
calculateChildPermissions(perm.getChildren(), !value, attachment);
}
}
public void clearPermissions() {
attachments.forEach(LPPermissionAttachment::remove);
}
}
@@ -0,0 +1,396 @@
/*
* 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.model;
import lombok.Getter;
import lombok.Setter;
import com.google.common.base.Preconditions;
import me.lucko.luckperms.api.Node;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.model.User;
import me.lucko.luckperms.common.node.ImmutableTransientNode;
import me.lucko.luckperms.common.node.NodeFactory;
import org.bukkit.permissions.PermissibleBase;
import org.bukkit.permissions.PermissionAttachment;
import org.bukkit.permissions.PermissionRemovedExecutor;
import org.bukkit.plugin.Plugin;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
* PermissionAttachment for LuckPerms.
*
* Applies all permissions directly to the backing user instance via transient nodes.
*/
public class LPPermissionAttachment extends PermissionAttachment {
/**
* The field in PermissionAttachment where the attachments applied permissions
* are *usually* held.
*/
private static final Field PERMISSION_ATTACHMENT_PERMISSIONS_FIELD;
static {
Field permissionAttachmentPermissionsField;
try {
permissionAttachmentPermissionsField = PermissibleBase.class.getDeclaredField("permissions");
permissionAttachmentPermissionsField.setAccessible(true);
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
}
PERMISSION_ATTACHMENT_PERMISSIONS_FIELD = permissionAttachmentPermissionsField;
}
/**
* The parent LPPermissible
*/
@Getter
private final LPPermissible permissible;
/**
* The plugin which "owns" this attachment, may be null
*/
private final Plugin owner;
/**
* The permissions being applied by this attachment
*/
private final Map<String, Boolean> perms = Collections.synchronizedMap(new HashMap<>());
/**
* If the attachment has been applied to the user
*/
private boolean hooked = false;
/**
* Callback to run when the attachment is removed
*/
@Getter
@Setter
private PermissionRemovedExecutor removalCallback = null;
public LPPermissionAttachment(LPPermissible permissible, Plugin owner) {
super(DummyPlugin.INSTANCE, null);
this.permissible = permissible;
this.owner = owner;
injectFakeMap();
}
public LPPermissionAttachment(LPPermissible permissible, PermissionAttachment bukkit) {
super(DummyPlugin.INSTANCE, null);
this.permissible = permissible;
this.owner = null;
// copy
perms.putAll(bukkit.getPermissions());
injectFakeMap();
}
/**
* Injects a fake 'permissions' map into the superclass, for (clever/dumb??) plugins
* which attempt to modify attachment permissions using reflection to get around the slow bukkit
* behaviour in the base PermissionAttachment implementation.
*
* The fake map proxies calls back to the methods on this attachment
*/
private void injectFakeMap() {
// inner class - this proxies calls back to us
FakeBackingMap fakeMap = new FakeBackingMap();
try {
// what's this doing, ay?
// the field we need to modify is in the superclass - it's set to private
// so we have to use reflection to modify it.
PERMISSION_ATTACHMENT_PERMISSIONS_FIELD.set(this, fakeMap);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Hooks this attachment with the parent {@link User} instance.
*/
public void hook() {
hooked = true;
permissible.attachments.add(this);
for (Map.Entry<String, Boolean> entry : perms.entrySet()) {
if (entry.getKey() == null || entry.getKey().isEmpty()) {
continue;
}
setPermissionInternal(entry.getKey(), entry.getValue());
}
}
private void setPermissionInternal(String name, boolean value) {
if (!permissible.getPlugin().getConfiguration().get(ConfigKeys.APPLY_BUKKIT_ATTACHMENT_PERMISSIONS)) {
return;
}
// 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 = NodeFactory.newBuilder(name)
.setValue(value)
.withExtraContext(permissible.getPlugin().getContextManager().getStaticContext())
.build();
// convert the constructed node to a transient node instance to refer back to this attachment
ImmutableTransientNode transientNode = ImmutableTransientNode.of(node, this);
// set the transient node
User user = permissible.getUser();
if (user.setTransientPermission(transientNode).asBoolean()) {
user.getRefreshBuffer().request();
}
}
private void unsetPermissionInternal(String name) {
if (!permissible.getPlugin().getConfiguration().get(ConfigKeys.APPLY_BUKKIT_ATTACHMENT_PERMISSIONS)) {
return;
}
// remove transient permissions from the holder which were added by this attachment & equal the permission
User user = permissible.getUser();
if (user.removeIfTransient(n -> n instanceof ImmutableTransientNode && ((ImmutableTransientNode) n).getOwner() == this && n.getPermission().equals(name))) {
user.getRefreshBuffer().request();
}
}
private void clearInternal() {
// remove all transient permissions added by this attachment
User user = permissible.getUser();
if (user.removeIfTransient(n -> n instanceof ImmutableTransientNode && ((ImmutableTransientNode) n).getOwner() == this)) {
user.getRefreshBuffer().request();
}
}
@Override
public boolean remove() {
if (!hooked) {
return false;
}
// clear the internal permissions
clearInternal();
// run the callback
if (removalCallback != null) {
removalCallback.attachmentRemoved(this);
}
// unhook from the permissible
hooked = false;
permissible.attachments.remove(this);
return true;
}
@Override
public void setPermission(String name, boolean value) {
Preconditions.checkNotNull(name, "name is null");
Preconditions.checkArgument(!name.isEmpty(), "name is empty");
String permission = name.toLowerCase();
Boolean previous = perms.put(permission, value);
if (previous != null && previous == value) {
return;
}
// if we're not hooked, thn don't actually apply the change
// it will get applied on hook - if that ever happens
if (!hooked) {
return;
}
if (previous != null) {
unsetPermissionInternal(permission);
}
setPermissionInternal(permission, value);
}
@Override
public void unsetPermission(String name) {
Preconditions.checkNotNull(name, "name is null");
Preconditions.checkArgument(!name.isEmpty(), "name is empty");
String permission = name.toLowerCase();
Boolean previous = perms.remove(permission);
if (previous == null) {
return;
}
// if we're not hooked, thn don't actually apply the change
// it will get applied on hook - if that ever happens
if (!hooked) {
return;
}
unsetPermissionInternal(permission);
}
@Override
public Map<String, Boolean> getPermissions() {
return perms;
}
@Override
public Plugin getPlugin() {
return owner != null ? owner : permissible.getPlugin();
}
@Override
public boolean equals(Object obj) {
return this == obj;
}
@Override
public int hashCode() {
return System.identityHashCode(this);
}
/**
* A fake map to be injected into the superclass. This implementation simply
* proxies calls back to this attachment instance.
*
* Some (clever/dumb??) plugins attempt to modify attachment permissions using reflection
* to get around the slow bukkit behaviour in the base PermissionAttachment implementation.
*
* An instance of this map is injected into the super instance so these plugins continue
* to work with LuckPerms.
*/
private final class FakeBackingMap implements Map<String, Boolean> {
@Override
public Boolean put(String key, Boolean value) {
// grab the previous result, so we can still satisfy the method signature of Map
Boolean previous = perms.get(key);
// proxy the call back through the PermissionAttachment instance
setPermission(key, value);
// return the previous value
return previous;
}
@Override
public Boolean remove(Object key) {
// we only accept string keys
if (!(key instanceof String)) {
return null;
}
String permission = ((String) key);
// grab the previous result, so we can still satisfy the method signature of Map
Boolean previous = perms.get(permission);
// proxy the call back through the PermissionAttachment instance
unsetPermission(permission);
// return the previous value
return previous;
}
@Override
public void putAll(Map<? extends String, ? extends Boolean> m) {
for (Map.Entry<? extends String, ? extends Boolean> entry : m.entrySet()) {
put(entry.getKey(), entry.getValue());
}
}
@Override
public void clear() {
// remove the permissions which have already been applied
if (hooked) {
clearInternal();
}
// clear the backing map
perms.clear();
}
@Override
public int size() {
// return the size of the permissions map - probably the most accurate value we have
return perms.size();
}
@Override
public boolean isEmpty() {
// return if the permissions map is empty - again probably the most accurate thing
// we can return
return perms.isEmpty();
}
@Override
public boolean containsKey(Object key) {
// just proxy
return perms.containsKey(key);
}
@Override
public boolean containsValue(Object value) {
// just proxy
return perms.containsValue(value);
}
@Override
public Boolean get(Object key) {
// just proxy
return perms.get(key);
}
@Override
public Set<String> keySet() {
// just proxy
return perms.keySet();
}
@Override
public Collection<Boolean> values() {
// just proxy
return perms.values();
}
@Override
public Set<Entry<String, Boolean>> entrySet() {
// just proxy
return perms.entrySet();
}
}
}
@@ -0,0 +1,270 @@
/*
* 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.model;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import me.lucko.luckperms.bukkit.LPBukkitPlugin;
import me.lucko.luckperms.common.utils.ImmutableCollectors;
import org.bukkit.entity.Player;
import org.bukkit.permissions.Permissible;
import org.bukkit.permissions.Permission;
import org.bukkit.plugin.PluginManager;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.stream.Collectors;
/**
* A replacement map for the 'permSubs' instance in Bukkit's SimplePluginManager.
*
* This instance allows LuckPerms to intercept calls to
* {@link PluginManager#subscribeToPermission(String, Permissible)},
* {@link PluginManager#unsubscribeFromPermission(String, Permissible)} and
* {@link PluginManager#getPermissionSubscriptions(String)}.
*
* Bukkit for some reason sometimes uses subscription status to determine whether
* a permissible has a given node, instead of checking directly with
* {@link Permissible#hasPermission(Permission)}.
*
* {@link org.bukkit.Server#broadcast(String, String)} is a good example of this.
*
* In order to implement predicable Bukkit behaviour, LP has two options:
* 1) register subscriptions for all players as normal, or
* 2) inject it's own map instance to proxy calls to {@link PluginManager#getPermissionSubscriptions(String)} back to LuckPerms.
*
* This class implements option 2 above. It is preferred because it is faster & uses less memory
*/
public class LPSubscriptionMap extends HashMap<String, Map<Permissible, Boolean>> {
// the plugin instance
private final LPBukkitPlugin plugin;
public LPSubscriptionMap(LPBukkitPlugin plugin, Map<String, Map<Permissible, Boolean>> existingData) {
super(existingData);
this.plugin = plugin;
}
/* The get method is the only one which is actually used by SimplePluginManager
* we override it to always return a value - which means the null check in
* subscribeToDefaultPerms always fails - soo, we don't have to worry too much
* about implementing #put.
*
* we also ensure all returns are LPSubscriptionValueMaps. this extension
* will also delegate checks to online players - meaning we don't ever
* have to register their subscriptions with the plugin manager.
*/
@Override
public Map<Permissible, Boolean> get(Object key) {
if (key == null || !(key instanceof String)) {
return null;
}
String permission = ((String) key);
Map<Permissible, Boolean> result = 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;
}
@Override
public Map<Permissible, Boolean> put(String key, Map<Permissible, Boolean> value) {
if (value == null) {
throw new NullPointerException("Map value cannot be null");
}
// ensure values are LP subscription maps
if (!(value instanceof LPSubscriptionValueMap)) {
value = new LPSubscriptionValueMap(key, value);
}
return super.put(key, value);
}
// if the key isn't null and is a string, #get will always return a value for it
@Override
public boolean containsKey(Object key) {
return key != null && key instanceof String;
}
/**
* Converts this map back to a standard HashMap
*
* @return a standard representation of this map
*/
public Map<String, Map<Permissible, Boolean>> detach() {
Map<String, Map<Permissible, Boolean>> ret = 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());
}
}
return ret;
}
/**
* Value map extension which includes LP objects in Permissible related queries.
*/
public class LPSubscriptionValueMap implements Map<Permissible, Boolean> {
// the permission being mapped to this value map
private final String permission;
// the backing map
private final Map<Permissible, Boolean> backing;
public LPSubscriptionValueMap(String permission, Map<Permissible, Boolean> backing) {
this.permission = permission;
this.backing = backing;
}
public LPSubscriptionValueMap(String permission) {
this(permission, new WeakHashMap<>());
}
@Override
public Boolean get(Object key) {
boolean isPlayer = key instanceof Player;
// if the key is a player, check their LPPermissible first
if (isPlayer) {
Permissible p = (Permissible) key;
if (p.isPermissionSet(permission)) {
return p.hasPermission(permission);
}
}
// then try the map
Boolean result = backing.get(key);
if (result != null) {
return result;
}
// then try the map, if we haven't already
if (!isPlayer && key instanceof Permissible) {
Permissible p = (Permissible) key;
if (p.isPermissionSet(permission)) {
return p.hasPermission(permission);
}
}
// no result
return null;
}
@Override
public boolean containsKey(Object key) {
// check the backing map, as well as if the permissible has the perm set
return backing.containsKey(key) || (key instanceof Permissible && ((Permissible) key).isPermissionSet(permission));
}
@Override
public Set<Permissible> keySet() {
// gather players (LPPermissibles)
Set<Permissible> players = plugin.getServer().getOnlinePlayers().stream()
.filter(player -> player.isPermissionSet(permission))
.collect(Collectors.toSet());
// then combine the players with the backing map
return Sets.union(players, backing.keySet());
}
@Override
public Set<Entry<Permissible, Boolean>> entrySet() {
return keySet().stream()
.map(p -> {
Boolean ret = get(p);
return ret != null ? Maps.immutableEntry(p, ret) : null;
})
.filter(Objects::nonNull)
.collect(ImmutableCollectors.toImmutableSet());
}
@Override
public boolean isEmpty() {
// we never want to remove this map from the parent - since it just gets recreated
// on subsequent calls
return false;
}
// just delegate to the backing map
@Override
public Boolean put(Permissible key, Boolean value) {
return backing.put(key, value);
}
@Override
public Boolean remove(Object key) {
return backing.remove(key);
}
// the following methods are not used in the current impls of PluginManager, but just delegate them for now
@Override
public int size() {
return backing.size();
}
@Override
public boolean containsValue(Object value) {
return backing.containsValue(value);
}
@Override
public void putAll(Map<? extends Permissible, ? extends Boolean> m) {
backing.putAll(m);
}
@Override
public void clear() {
backing.clear();
}
@Override
public Collection<Boolean> values() {
return backing.values();
}
}
}
@@ -1,94 +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.model;
import lombok.RequiredArgsConstructor;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import org.bukkit.permissions.Permission;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* Handles permission subscriptions with Bukkits plugin manager, for a given LPPermissible.
*
* Bukkit for some reason sometimes uses subscription status to determine whether a permissible has a given node, instead
* of checking directly with {@link org.bukkit.permissions.Permissible#hasPermission(Permission)}.
*
* {@link org.bukkit.Bukkit#broadcast(String, String)} is a good example of this.
*/
@RequiredArgsConstructor
public class SubscriptionManager {
private final LPPermissible permissible;
private Set<String> currentSubscriptions = ImmutableSet.of();
public synchronized void subscribe(Set<String> perms) {
Set<String> newPerms = ImmutableSet.copyOf(perms);
// we compare changes to avoid unnecessary time wasted on the main thread mutating this data.
// the changes can be calculated here async, and then only the needed changes can be applied.
Map.Entry<Set<String>, Set<String>> changes = compareSets(newPerms, currentSubscriptions);
Set<String> toAdd = changes.getKey();
Set<String> toRemove = changes.getValue();
permissible.getPlugin().doSync(() -> {
for (String s : toAdd) {
permissible.getPlugin().getServer().getPluginManager().subscribeToPermission(s, permissible.getParent());
}
for (String s : toRemove) {
permissible.getPlugin().getServer().getPluginManager().unsubscribeFromPermission(s, permissible.getParent());
}
});
this.currentSubscriptions = newPerms;
}
/**
* Compares two sets
* @param local the local set
* @param remote the remote set
* @return the entries to add to remote, and the entries to remove from remote
*/
private static Map.Entry<Set<String>, Set<String>> compareSets(Set<String> local, Set<String> remote) {
// entries in local but not remote need to be added
// entries in remote but not local need to be removed
Set<String> toAdd = new HashSet<>(local);
toAdd.removeAll(remote);
Set<String> toRemove = new HashSet<>(remote);
toRemove.removeAll(local);
return Maps.immutableEntry(toAdd, toRemove);
}
}
@@ -0,0 +1,108 @@
/*
* 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.model;
import lombok.RequiredArgsConstructor;
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;
import java.lang.reflect.Field;
import java.util.Map;
@RequiredArgsConstructor
public class SubscriptionMapInjector implements Runnable {
private static final Field PERM_SUBS_FIELD;
static {
Field permSubsField;
try {
permSubsField = SimplePluginManager.class.getDeclaredField("permSubs");
permSubsField.setAccessible(true);
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
}
PERM_SUBS_FIELD = permSubsField;
}
private final LPBukkitPlugin plugin;
@Override
public void run() {
try {
inject();
} catch (Exception e) {
plugin.getLog().severe("Exception occurred whilst injecting LuckPerms Permission Subscription map.");
e.printStackTrace();
}
}
private void inject() throws Exception {
PluginManager pluginManager = plugin.getServer().getPluginManager();
if (!(pluginManager instanceof SimplePluginManager)) {
plugin.getLog().severe("PluginManager instance is not a 'SimplePluginManager', instead: " + pluginManager.getClass());
plugin.getLog().severe("Unable to inject LuckPerms Permission Subscription map.");
return;
}
Object map = PERM_SUBS_FIELD.get(pluginManager);
if (map instanceof LPSubscriptionMap) {
return; // already injected
}
//noinspection unchecked
Map<String, Map<Permissible, Boolean>> castedMap = (Map<String, Map<Permissible, Boolean>>) map;
// make a new subscription map
LPSubscriptionMap newMap = new LPSubscriptionMap(plugin, castedMap);
// inject it
PERM_SUBS_FIELD.set(pluginManager, newMap);
}
public static void uninject() {
try {
PluginManager pluginManager = Bukkit.getServer().getPluginManager();
if (!(pluginManager instanceof SimplePluginManager)) {
return;
}
Object map = PERM_SUBS_FIELD.get(pluginManager);
if (map instanceof LPSubscriptionMap) {
LPSubscriptionMap lpMap = (LPSubscriptionMap) map;
PERM_SUBS_FIELD.set(pluginManager, lpMap.detach());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
@@ -0,0 +1,55 @@
/*
* 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.processors;
import lombok.RequiredArgsConstructor;
import me.lucko.luckperms.bukkit.LPBukkitPlugin;
import java.util.HashSet;
import java.util.Set;
/**
* Performs the initial setup for Bukkit permission processors
*/
@RequiredArgsConstructor
public class BukkitProcessorsSetupTask implements Runnable {
private final LPBukkitPlugin plugin;
@Override
public void run() {
plugin.getDefaultsProvider().refresh();
plugin.getChildPermissionProvider().setup();
Set<String> perms = new HashSet<>();
plugin.getServer().getPluginManager().getPermissions().forEach(p -> {
perms.add(p.getName());
perms.addAll(p.getChildren().keySet());
});
perms.forEach(p -> plugin.getPermissionVault().offer(p));
}
}
@@ -41,7 +41,7 @@ import java.util.concurrent.ConcurrentHashMap;
@RequiredArgsConstructor
public class ChildProcessor implements PermissionProcessor {
private final ChildPermissionProvider provider;
private Map<String, Boolean> childPermissions = new ConcurrentHashMap<>();
private final Map<String, Boolean> childPermissions = new ConcurrentHashMap<>();
@Override
public Tristate hasPermission(String permission) {
@@ -45,7 +45,7 @@ public class DefaultsProcessor implements PermissionProcessor {
@Override
public Tristate hasPermission(String permission) {
Tristate t = defaultsProvider.hasDefault(permission, isOp);
Tristate t = defaultsProvider.lookup(permission, isOp);
if (t != Tristate.UNDEFINED) {
return t;
}
@@ -78,7 +78,7 @@ public class DefaultsProvider {
* @param isOp if the player is op
* @return a tristate result
*/
public Tristate hasDefault(String permission, boolean isOp) {
public Tristate lookup(String permission, boolean isOp) {
Map<String, Boolean> map = isOp ? opDefaults : nonOpDefaults;
Boolean b = map.get(permission);
@@ -126,6 +126,10 @@ public class DefaultsProvider {
unregisterDefaults(nonOpDefaults, nonOpDummy, false);
}
private static PluginManager pm() {
return Bukkit.getServer().getPluginManager();
}
/**
* Unregisters defaults for a given permissible.
*
@@ -136,21 +140,21 @@ public class DefaultsProvider {
Set<String> perms = map.keySet();
for (String name : perms) {
Bukkit.getServer().getPluginManager().unsubscribeFromPermission(name, p);
pm().unsubscribeFromPermission(name, p);
}
Bukkit.getServer().getPluginManager().unsubscribeFromDefaultPerms(op, p);
pm().unsubscribeFromDefaultPerms(op, p);
}
private static void calculateDefaults(Map<String, Boolean> map, DummyPermissible p, boolean op) {
Bukkit.getServer().getPluginManager().subscribeToDefaultPerms(op, p);
pm().subscribeToDefaultPerms(op, p);
Set<Permission> defaults = Bukkit.getServer().getPluginManager().getDefaultPermissions(op);
Set<Permission> defaults = pm().getDefaultPermissions(op);
for (Permission perm : defaults) {
String name = perm.getName().toLowerCase();
map.put(name, true);
Bukkit.getServer().getPluginManager().subscribeToPermission(name, p);
pm().subscribeToPermission(name, p);
// register defaults for any children too
calculateChildPermissions(map, p, perm.getChildren(), false);
@@ -167,10 +171,10 @@ public class DefaultsProvider {
boolean value = e.getValue() ^ invert;
accumulator.put(e.getKey().toLowerCase(), value);
Bukkit.getServer().getPluginManager().subscribeToPermission(e.getKey(), p);
pm().subscribeToPermission(e.getKey(), p);
// lookup any deeper children & resolve if present
Permission perm = Bukkit.getServer().getPluginManager().getPermission(e.getKey());
Permission perm = pm().getPermission(e.getKey());
if (perm != null) {
calculateChildPermissions(accumulator, p, perm.getChildren(), !value);
}
@@ -32,7 +32,6 @@ import me.lucko.luckperms.api.Contexts;
import me.lucko.luckperms.api.Node;
import me.lucko.luckperms.api.caching.MetaData;
import me.lucko.luckperms.common.caching.MetaAccumulator;
import me.lucko.luckperms.common.contexts.ExtractedContexts;
import me.lucko.luckperms.common.model.Group;
import me.lucko.luckperms.common.model.PermissionHolder;
import me.lucko.luckperms.common.model.User;
@@ -109,25 +108,25 @@ public class VaultChatHook extends Chat {
@Override
public String getGroupPrefix(String world, @NonNull String group) {
final Group g = perms.getPlugin().getGroupManager().getIfLoaded(group);
final Group g = perms.getPlugin().getGroupManager().getByDisplayName(group);
return getGroupChatMeta(g, ChatMetaType.PREFIX, world);
}
@Override
public void setGroupPrefix(String world, @NonNull String group, @NonNull String prefix) {
final Group g = perms.getPlugin().getGroupManager().getIfLoaded(group);
final Group g = perms.getPlugin().getGroupManager().getByDisplayName(group);
setChatMeta(g, ChatMetaType.PREFIX, prefix, world);
}
@Override
public String getGroupSuffix(String world, @NonNull String group) {
final Group g = perms.getPlugin().getGroupManager().getIfLoaded(group);
final Group g = perms.getPlugin().getGroupManager().getByDisplayName(group);
return getGroupChatMeta(g, ChatMetaType.SUFFIX, world);
}
@Override
public void setGroupSuffix(String world, @NonNull String group, @NonNull String suffix) {
final Group g = perms.getPlugin().getGroupManager().getIfLoaded(group);
final Group g = perms.getPlugin().getGroupManager().getByDisplayName(group);
setChatMeta(g, ChatMetaType.SUFFIX, suffix, world);
}
@@ -149,7 +148,7 @@ public class VaultChatHook extends Chat {
@Override
public int getGroupInfoInteger(String world, @NonNull String group, @NonNull String node, int defaultValue) {
final Group g = perms.getPlugin().getGroupManager().getIfLoaded(group);
final Group g = perms.getPlugin().getGroupManager().getByDisplayName(group);
try {
return Integer.parseInt(getGroupMeta(g, node, world, String.valueOf(defaultValue)));
} catch (NumberFormatException e) {
@@ -159,7 +158,7 @@ public class VaultChatHook extends Chat {
@Override
public void setGroupInfoInteger(String world, @NonNull String group, @NonNull String node, int value) {
final Group g = perms.getPlugin().getGroupManager().getIfLoaded(group);
final Group g = perms.getPlugin().getGroupManager().getByDisplayName(group);
setMeta(g, node, String.valueOf(value), world);
}
@@ -181,7 +180,7 @@ public class VaultChatHook extends Chat {
@Override
public double getGroupInfoDouble(String world, @NonNull String group, @NonNull String node, double defaultValue) {
final Group g = perms.getPlugin().getGroupManager().getIfLoaded(group);
final Group g = perms.getPlugin().getGroupManager().getByDisplayName(group);
try {
return Double.parseDouble(getGroupMeta(g, node, world, String.valueOf(defaultValue)));
} catch (NumberFormatException e) {
@@ -191,7 +190,7 @@ public class VaultChatHook extends Chat {
@Override
public void setGroupInfoDouble(String world, @NonNull String group, @NonNull String node, double value) {
final Group g = perms.getPlugin().getGroupManager().getIfLoaded(group);
final Group g = perms.getPlugin().getGroupManager().getByDisplayName(group);
setMeta(g, node, String.valueOf(value), world);
}
@@ -213,7 +212,7 @@ public class VaultChatHook extends Chat {
@Override
public boolean getGroupInfoBoolean(String world, @NonNull String group, @NonNull String node, boolean defaultValue) {
final Group g = perms.getPlugin().getGroupManager().getIfLoaded(group);
final Group g = perms.getPlugin().getGroupManager().getByDisplayName(group);
String s = getGroupMeta(g, node, world, String.valueOf(defaultValue));
if (!s.equalsIgnoreCase("true") && !s.equalsIgnoreCase("false")) {
return defaultValue;
@@ -223,7 +222,7 @@ public class VaultChatHook extends Chat {
@Override
public void setGroupInfoBoolean(String world, @NonNull String group, @NonNull String node, boolean value) {
final Group g = perms.getPlugin().getGroupManager().getIfLoaded(group);
final Group g = perms.getPlugin().getGroupManager().getByDisplayName(group);
setMeta(g, node, String.valueOf(value), world);
}
@@ -241,13 +240,13 @@ public class VaultChatHook extends Chat {
@Override
public String getGroupInfoString(String world, @NonNull String group, @NonNull String node, String defaultValue) {
final Group g = perms.getPlugin().getGroupManager().getIfLoaded(group);
final Group g = perms.getPlugin().getGroupManager().getByDisplayName(group);
return getGroupMeta(g, node, world, defaultValue);
}
@Override
public void setGroupInfoString(String world, @NonNull String group, @NonNull String node, String value) {
final Group g = perms.getPlugin().getGroupManager().getIfLoaded(group);
final Group g = perms.getPlugin().getGroupManager().getByDisplayName(group);
setMeta(g, node, value, world);
}
@@ -284,7 +283,7 @@ public class VaultChatHook extends Chat {
holder.removeIf(type::matches);
// find the max inherited priority & add 10
MetaAccumulator metaAccumulator = holder.accumulateMeta(null, null, ExtractedContexts.generate(perms.createContextForWorldSet(finalWorld)));
MetaAccumulator metaAccumulator = holder.accumulateMeta(null, null, perms.createContextForWorldSet(finalWorld));
int priority = (type == ChatMetaType.PREFIX ? metaAccumulator.getPrefixes() : metaAccumulator.getSuffixes()).keySet().stream()
.mapToInt(e -> e).max().orElse(0) + 10;
@@ -331,7 +330,7 @@ public class VaultChatHook extends Chat {
perms.log("Getting meta: '" + node + "' for group " + group.getName() + " on world " + world + ", server " + perms.getServer());
for (Node n : group.getOwnNodes()) {
if (!n.getValue()) continue;
if (!n.getValuePrimitive()) continue;
if (!n.isMeta()) continue;
if (!n.shouldApplyWithContext(perms.createContextForWorldLookup(world).getContexts())) continue;
@@ -355,9 +354,9 @@ public class VaultChatHook extends Chat {
int priority = Integer.MIN_VALUE;
String meta = null;
ExtractedContexts ec = ExtractedContexts.generate(Contexts.of(perms.createContextForWorldLookup(world).getContexts(), perms.isIncludeGlobal(), true, true, true, true, false));
for (Node n : group.getAllNodes(ec)) {
if (!n.getValue()) continue;
Contexts contexts = Contexts.of(perms.createContextForWorldLookup(world).getContexts(), perms.isIncludeGlobal(), true, true, true, true, false);
for (Node n : group.getAllNodes(contexts)) {
if (!n.getValuePrimitive()) continue;
if (type.shouldIgnore(n)) continue;
if (!n.shouldApplyWithContext(perms.createContextForWorldLookup(world).getContexts())) continue;
@@ -28,20 +28,19 @@ package me.lucko.luckperms.bukkit.vault;
import lombok.Getter;
import lombok.NonNull;
import com.google.common.base.Preconditions;
import me.lucko.luckperms.api.Contexts;
import me.lucko.luckperms.api.DataMutateResult;
import me.lucko.luckperms.api.Node;
import me.lucko.luckperms.api.Tristate;
import me.lucko.luckperms.api.caching.PermissionData;
import me.lucko.luckperms.api.context.ImmutableContextSet;
import me.lucko.luckperms.api.context.MutableContextSet;
import me.lucko.luckperms.bukkit.LPBukkitPlugin;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.contexts.ExtractedContexts;
import me.lucko.luckperms.common.model.Group;
import me.lucko.luckperms.common.model.PermissionHolder;
import me.lucko.luckperms.common.model.User;
import me.lucko.luckperms.common.node.NodeFactory;
import me.lucko.luckperms.common.verbose.CheckOrigin;
import net.milkbowl.vault.permission.Permission;
@@ -52,9 +51,7 @@ import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* An implementation of the Vault {@link Permission} API using LuckPerms.
@@ -110,7 +107,7 @@ public class VaultPermissionHook extends Permission {
@Override
public String[] getGroups() {
return plugin.getGroupManager().getAll().keySet().toArray(new String[0]);
return plugin.getGroupManager().getAll().values().stream().map(g -> g.getDisplayName().orElse(g.getName())).toArray(String[]::new);
}
@Override
@@ -147,7 +144,7 @@ public class VaultPermissionHook extends Permission {
}
// Effectively fallback to the standard Bukkit #hasPermission check.
return user.getUserData().getPermissionData(createContextForWorldLookup(player, world)).getPermissionValue(permission).asBoolean();
return user.getUserData().getPermissionData(createContextForWorldLookup(player, world)).getPermissionValue(permission, CheckOrigin.INTERNAL).asBoolean();
}
@Override
@@ -221,11 +218,11 @@ public class VaultPermissionHook extends Permission {
world = correctWorld(world);
log("Checking if group " + groupName + " has permission: " + permission + " on world " + world + ", server " + getServer());
final Group group = plugin.getGroupManager().getIfLoaded(groupName);
final Group group = plugin.getGroupManager().getByDisplayName(groupName);
if (group == null) return false;
// This is a nasty call. Groups aren't cached. :(
Map<String, Boolean> permissions = group.exportNodesAndShorthand(ExtractedContexts.generate(createContextForWorldLookup(world)), true);
Map<String, Boolean> permissions = group.exportNodesAndShorthand(createContextForWorldLookup(world), true);
return permissions.containsKey(permission.toLowerCase()) && permissions.get(permission.toLowerCase());
}
@@ -234,7 +231,7 @@ public class VaultPermissionHook extends Permission {
world = correctWorld(world);
log("Adding permission to group " + groupName + ": '" + permission + "' on world " + world + ", server " + getServer());
final Group group = plugin.getGroupManager().getIfLoaded(groupName);
final Group group = plugin.getGroupManager().getByDisplayName(groupName);
if (group == null) return false;
holderAddPermission(group, permission, world);
@@ -246,7 +243,7 @@ public class VaultPermissionHook extends Permission {
world = correctWorld(world);
log("Removing permission from group " + groupName + ": '" + permission + "' on world " + world + ", server " + getServer());
final Group group = plugin.getGroupManager().getIfLoaded(groupName);
final Group group = plugin.getGroupManager().getByDisplayName(groupName);
if (group == null) return false;
holderRemovePermission(group, permission, world);
@@ -255,69 +252,77 @@ public class VaultPermissionHook extends Permission {
@Override
public boolean playerInGroup(String world, String player, @NonNull String group) {
return playerHas(world, player, "group." + group);
return playerHas(world, player, "group." + rewriteGroupName(group));
}
@SuppressWarnings("deprecation")
@Override
public boolean playerInGroup(World world, String player, @NonNull String group) {
return playerHas(world, player, "group." + group);
return playerHas(world, player, "group." + rewriteGroupName(group));
}
@Override
public boolean playerInGroup(String world, OfflinePlayer player, @NonNull String group) {
return playerHas(world, player, "group." + group);
return playerHas(world, player, "group." + rewriteGroupName(group));
}
@Override
public boolean playerInGroup(Player player, @NonNull String group) {
return playerHas(player, "group." + group);
return playerHas(player, "group." + rewriteGroupName(group));
}
private boolean checkGroupExists(String group) {
return plugin.getGroupManager().isLoaded(group);
return plugin.getGroupManager().getByDisplayName(group) != null;
}
private String rewriteGroupName(String name) {
Group group = plugin.getGroupManager().getByDisplayName(name);
if (group != null) {
return group.getName();
}
return name;
}
@Override
public boolean playerAddGroup(String world, String player, @NonNull String group) {
return checkGroupExists(group) && playerAdd(world, player, "group." + group);
return checkGroupExists(group) && playerAdd(world, player, "group." + rewriteGroupName(group));
}
@SuppressWarnings("deprecation")
@Override
public boolean playerAddGroup(World world, String player, @NonNull String group) {
return checkGroupExists(group) && playerAdd(world, player, "group." + group);
return checkGroupExists(group) && playerAdd(world, player, "group." + rewriteGroupName(group));
}
@Override
public boolean playerAddGroup(String world, OfflinePlayer player, @NonNull String group) {
return checkGroupExists(group) && playerAdd(world, player, "group." + group);
return checkGroupExists(group) && playerAdd(world, player, "group." + rewriteGroupName(group));
}
@Override
public boolean playerAddGroup(Player player, @NonNull String group) {
return checkGroupExists(group) && playerAdd(player, "group." + group);
return checkGroupExists(group) && playerAdd(player, "group." + rewriteGroupName(group));
}
@Override
public boolean playerRemoveGroup(String world, String player, @NonNull String group) {
return checkGroupExists(group) && playerRemove(world, player, "group." + group);
return checkGroupExists(group) && playerRemove(world, player, "group." + rewriteGroupName(group));
}
@SuppressWarnings("deprecation")
@Override
public boolean playerRemoveGroup(World world, String player, @NonNull String group) {
return checkGroupExists(group) && playerRemove(world, player, "group." + group);
return checkGroupExists(group) && playerRemove(world, player, "group." + rewriteGroupName(group));
}
@Override
public boolean playerRemoveGroup(String world, OfflinePlayer player, @NonNull String group) {
return checkGroupExists(group) && playerRemove(world, player, "group." + group);
return checkGroupExists(group) && playerRemove(world, player, "group." + rewriteGroupName(group));
}
@Override
public boolean playerRemoveGroup(Player player, @NonNull String group) {
return checkGroupExists(group) && playerRemove(player, "group." + group);
return checkGroupExists(group) && playerRemove(player, "group." + rewriteGroupName(group));
}
@Override
@@ -353,28 +358,34 @@ public class VaultPermissionHook extends Permission {
return user.getEnduringNodes().values().stream()
.filter(Node::isGroupNode)
.filter(n -> n.shouldApplyWithContext(createContextForWorldLookup(player, w).getContexts()))
.map(Node::getGroupName)
.map(n -> {
Group group = plugin.getGroupManager().getIfLoaded(n.getGroupName());
if (group != null) {
return group.getDisplayName().orElse(group.getName());
}
return n.getGroupName();
})
.toArray(String[]::new);
}
@Override
public String getPrimaryGroup(String world, @NonNull String player) {
return getPrimaryGroup(world, Bukkit.getPlayerExact(player));
return getPrimaryGroup(Bukkit.getPlayerExact(player));
}
@SuppressWarnings("deprecation")
@Override
public String getPrimaryGroup(World world, @NonNull String player) {
return getPrimaryGroup(world == null ? null : world.getName(), Bukkit.getPlayerExact(player));
return getPrimaryGroup(Bukkit.getPlayerExact(player));
}
@Override
public String getPrimaryGroup(String world, @NonNull OfflinePlayer player) {
return getPrimaryGroup(world, player.getPlayer());
return getPrimaryGroup(player.getPlayer());
}
private String getPrimaryGroup(String world, Player player) {
world = correctWorld(world);
@Override
public String getPrimaryGroup(Player player) {
log("Getting primary group of player: " + player);
if (player == null) {
@@ -387,71 +398,13 @@ public class VaultPermissionHook extends Permission {
return null;
}
// nothing special, just return the value.
if (!isPgo()) {
String g = user.getPrimaryGroup().getValue();
return plugin.getConfiguration().get(ConfigKeys.GROUP_NAME_REWRITES).getOrDefault(g, g);
}
// we need to do the complex PGO checking. (it's been enabled in the config.)
if (isPgoCheckInherited()) {
// we can just check the cached data
PermissionData data = user.getUserData().getPermissionData(createContextForWorldLookup(plugin.getPlayer(user), world));
for (Map.Entry<String, Boolean> e : data.getImmutableBacking().entrySet()) {
if (!e.getValue()) continue;
if (!e.getKey().toLowerCase().startsWith("vault.primarygroup.")) continue;
String group = e.getKey().substring("vault.primarygroup.".length());
if (isPgoCheckExists()) {
if (!plugin.getGroupManager().isLoaded(group)) {
continue;
}
}
if (isPgoCheckMemberOf()) {
if (data.getPermissionValue("group." + group) != Tristate.TRUE) {
continue;
}
}
return group;
}
} else {
// we need to check the users permissions only
for (Node node : user.getOwnNodes()) {
if (!node.getValue()) continue;
if (!node.getPermission().toLowerCase().startsWith("vault.primarygroup.")) continue;
if (!node.shouldApplyOnServer(getServer(), isIncludeGlobal(), false)) continue;
if (!node.shouldApplyOnWorld(world, true, false)) continue;
String group = node.getPermission().substring("vault.primarygroup.".length());
if (isPgoCheckExists()) {
if (!plugin.getGroupManager().isLoaded(group)) {
continue;
}
}
if (isPgoCheckMemberOf()) {
String finalWorld = world;
List<String> localGroups = user.getOwnNodes().stream()
.filter(Node::isGroupNode)
.filter(n -> n.shouldApplyOnWorld(finalWorld, isIncludeGlobal(), true))
.filter(n -> n.shouldApplyOnServer(getServer(), isIncludeGlobal(), true))
.map(Node::getGroupName)
.collect(Collectors.toList());
if (!localGroups.contains(group.toLowerCase())) {
continue;
}
}
return group;
}
}
// Fallback
String g = user.getPrimaryGroup().getValue();
return plugin.getConfiguration().get(ConfigKeys.GROUP_NAME_REWRITES).getOrDefault(g, g);
Group group = plugin.getGroupManager().getByDisplayName(g);
if (group != null) {
return group.getDisplayName().orElse(group.getName());
}
return g;
}
public void log(String s) {
@@ -467,36 +420,26 @@ public class VaultPermissionHook extends Permission {
// utility methods for modifying the state of PermissionHolders
private void holderAddPermission(PermissionHolder holder, String permission, String world) {
Preconditions.checkNotNull(permission, "permission is null");
Preconditions.checkArgument(!permission.isEmpty(), "permission is an empty string");
executor.execute(() -> {
DataMutateResult result;
if (world != null && !world.equals("") && !world.equalsIgnoreCase("global")) {
result = holder.setPermission(NodeFactory.make(permission, true, getServer(), world));
} else {
result = holder.setPermission(NodeFactory.make(permission, true, getServer()));
}
if (result.asBoolean()) {
if (holder.setPermission(NodeFactory.make(permission, true, getServer(), world)).asBoolean()) {
holderSave(holder);
}
});
}
private void holderRemovePermission(PermissionHolder holder, String permission, String world) {
Preconditions.checkNotNull(permission, "permission is null");
Preconditions.checkArgument(!permission.isEmpty(), "permission is an empty string");
executor.execute(() -> {
DataMutateResult result;
if (world != null && !world.equals("") && !world.equalsIgnoreCase("global")) {
result = holder.unsetPermission(NodeFactory.make(permission, getServer(), world));
} else {
result = holder.unsetPermission(NodeFactory.make(permission, getServer()));
}
if (result.asBoolean()) {
if (holder.unsetPermission(NodeFactory.make(permission, getServer(), world)).asBoolean()) {
holderSave(holder);
}
});
}
public void holderSave(PermissionHolder holder) {
void holderSave(PermissionHolder holder) {
if (holder instanceof User) {
User u = (User) holder;
plugin.getStorage().saveUser(u).thenRunAsync(() -> u.getRefreshBuffer().request(), plugin.getScheduler().async());
@@ -561,20 +504,4 @@ public class VaultPermissionHook extends Permission {
boolean isIgnoreWorld() {
return plugin.getConfiguration().get(ConfigKeys.VAULT_IGNORE_WORLD);
}
private boolean isPgo() {
return plugin.getConfiguration().get(ConfigKeys.VAULT_PRIMARY_GROUP_OVERRIDES);
}
private boolean isPgoCheckInherited() {
return plugin.getConfiguration().get(ConfigKeys.VAULT_PRIMARY_GROUP_OVERRIDES_CHECK_INHERITED);
}
private boolean isPgoCheckExists() {
return plugin.getConfiguration().get(ConfigKeys.VAULT_PRIMARY_GROUP_OVERRIDES_CHECK_EXISTS);
}
private boolean isPgoCheckMemberOf() {
return plugin.getConfiguration().get(ConfigKeys.VAULT_PRIMARY_GROUP_OVERRIDES_CHECK_MEMBER_OF);
}
}
+28 -7
View File
@@ -70,6 +70,16 @@ use-server-uuids: true
# in the LuckPerms cache.
use-server-uuid-cache: false
# If set to true, LuckPerms will allow usernames with non alphanumeric characters.
#
# Note that due to the design of the storage implementation, usernames must still be
# 16 characters or less.
allow-invalid-usernames: false
# If LuckPerms should produce extra logging output when it handles logins.
# Useful if you're having issues with UUID forwarding or data not being loaded.
debug-logins: false
# If the plugin should send log notifications to users whenever permissions are modified.
log-notify: true
@@ -79,11 +89,6 @@ world-rewrite:
# world_nether: world
# world_the_end: world
# Rewrites group names. The underlying name of the group does not change, just the output in
# commands / placeholders / Vault.
group-name-rewrite:
# default: Member
# Controls how temporary permissions/parents/meta should be accumulated
#
# The default behaviour is "deny"
@@ -99,7 +104,11 @@ temporary-add-behaviour: deny
# -> parents-by-weight just use the users most highly weighted parent
# -> all-parents-by-weight same as above, but calculates based upon all parents inherited from both
# directly and indirectly
primary-group-calculation: stored
primary-group-calculation: parents-by-weight
# If set to false, the plugin will allow a Users primary group to be removed with the
# 'parent remove' command, and will set their primary group back to default.
prevent-primary-group-removal: false
# If the plugin should check for "extra" permissions with users run LP commands.
#
@@ -168,6 +177,8 @@ group-weight:
# - lowest
# - highest_own
# - lowest_own
# - highest_inherited
# - lowest_inherited
# - highest_on_track_<track>
# - lowest_on_track_<track>
# - highest_not_on_track_<track>
@@ -253,7 +264,7 @@ vault-debug: false
# Which storage method the plugin should use.
#
# See: https://github.com/lucko/LuckPerms/wiki/Choosing-a-Storage-type
# Currently supported: mysql, mariadb, postgresql, sqlite, h2, json, yaml, mongodb
# Currently supported: mysql, mariadb, postgresql, sqlite, h2, json, yaml, hocon, mongodb
#
# Fill out connection info below if you're using MySQL, MariaDB, PostgreSQL or MongoDB
# If your MySQL server supports it, the "mariadb" option is preferred over "mysql".
@@ -328,6 +339,16 @@ messaging-service: none
# If LuckPerms should automatically push updates after a change has been made with a command.
auto-push-updates: true
# If LuckPerms should push logging entries to connected servers via the messaging service.
push-log-entries: true
# If LuckPerms should broadcast received logging entries to players on this platform.
#
# If you have LuckPerms installed on your backend servers as well as a BungeeCord proxy, you should
# set this option to false on either your backends or your proxies, to avoid players being messaged
# twice about log entries.
broadcast-received-log-entries: true
# Settings for Redis.
# Port 6379 is used by default; set address to "host:port" if differs
redis:
+5 -1
View File
@@ -5,7 +5,7 @@
<parent>
<artifactId>luckperms</artifactId>
<groupId>me.lucko.luckperms</groupId>
<version>3.3-SNAPSHOT</version>
<version>4.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -150,6 +150,10 @@
<groupId>net.ess3</groupId>
<artifactId>Essentials</artifactId>
</exclusion>
<exclusion>
<groupId>net.ess3</groupId>
<artifactId>EssentialsX</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
@@ -29,19 +29,23 @@ import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import me.lucko.luckperms.common.commands.CommandManager;
import me.lucko.luckperms.common.commands.sender.Sender;
import me.lucko.luckperms.common.commands.utils.Util;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.plugin.Command;
import net.md_5.bungee.api.plugin.TabExecutor;
import java.util.Arrays;
import java.util.List;
public class BungeeCommandExecutor extends Command implements TabExecutor {
private static final Splitter ARGUMENT_SPLITTER = Splitter.on(CommandManager.COMMAND_SEPARATOR_PATTERN).omitEmptyStrings();
private static final Joiner ARGUMENT_JOINER = Joiner.on(' ');
class BungeeCommand extends Command implements TabExecutor {
private final LPBungeePlugin plugin;
private final CommandManager manager;
BungeeCommand(LPBungeePlugin plugin, CommandManager manager) {
BungeeCommandExecutor(LPBungeePlugin plugin, CommandManager manager) {
super("luckpermsbungee", null, "lpb", "bperm", "bperms", "bpermission", "bpermissions");
this.plugin = plugin;
this.manager = manager;
@@ -49,15 +53,17 @@ class BungeeCommand extends Command implements TabExecutor {
@Override
public void execute(CommandSender sender, String[] args) {
manager.onCommand(
plugin.getSenderFactory().wrap(sender),
"lpb",
Util.stripQuotes(Splitter.on(CommandManager.COMMAND_SEPARATOR_PATTERN).omitEmptyStrings().splitToList(Joiner.on(' ').join(args)))
);
Sender lpSender = plugin.getSenderFactory().wrap(sender);
List<String> arguments = Util.stripQuotes(ARGUMENT_SPLITTER.splitToList(ARGUMENT_JOINER.join(args)));
manager.onCommand(lpSender, "lpb", arguments);
}
@Override
public Iterable<String> onTabComplete(CommandSender sender, String[] args) {
return manager.onTabComplete(plugin.getSenderFactory().wrap(sender), Arrays.asList(args));
Sender lpSender = plugin.getSenderFactory().wrap(sender);
List<String> arguments = Util.stripQuotes(ARGUMENT_SPLITTER.splitToList(ARGUMENT_JOINER.join(args)));
return manager.onTabComplete(lpSender, arguments);
}
}
@@ -28,7 +28,7 @@ package me.lucko.luckperms.bungee;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import me.lucko.luckperms.common.config.AbstractConfiguration;
import me.lucko.luckperms.common.config.ConfigurationAdapter;
import net.md_5.bungee.config.Configuration;
import net.md_5.bungee.config.ConfigurationProvider;
@@ -45,7 +45,7 @@ import java.util.Map;
import java.util.Optional;
@RequiredArgsConstructor
public class BungeeConfig extends AbstractConfiguration {
public class BungeeConfigAdapter implements ConfigurationAdapter {
@Getter
private final LPBungeePlugin plugin;
@@ -25,7 +25,7 @@
package me.lucko.luckperms.bungee;
import me.lucko.luckperms.common.plugin.LuckPermsScheduler;
import me.lucko.luckperms.common.plugin.SchedulerAdapter;
import net.md_5.bungee.api.scheduler.ScheduledTask;
@@ -34,13 +34,13 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
public class LPBungeeScheduler implements LuckPermsScheduler {
public class BungeeSchedulerAdapter implements SchedulerAdapter {
private final LPBungeePlugin plugin;
private Executor asyncExecutor;
private Set<ScheduledTask> tasks = ConcurrentHashMap.newKeySet();
private final Executor asyncExecutor;
private final Set<ScheduledTask> tasks = ConcurrentHashMap.newKeySet();
public LPBungeeScheduler(LPBungeePlugin plugin) {
public BungeeSchedulerAdapter(LPBungeePlugin plugin) {
this.plugin = plugin;
this.asyncExecutor = r -> plugin.getProxy().getScheduler().runAsync(plugin, r);
}
@@ -30,9 +30,10 @@ import me.lucko.luckperms.bungee.event.TristateCheckEvent;
import me.lucko.luckperms.common.commands.sender.SenderFactory;
import me.lucko.luckperms.common.constants.Constants;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.utils.TextUtils;
import net.kyori.text.Component;
import net.kyori.text.serializer.ComponentSerializer;
import net.kyori.text.serializer.ComponentSerializers;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.connection.ProxiedPlayer;
@@ -68,9 +69,10 @@ public class BungeeSenderFactory extends SenderFactory<CommandSender> {
@Override
protected void sendMessage(CommandSender sender, Component message) {
try {
sender.sendMessage(net.md_5.bungee.chat.ComponentSerializer.parse(ComponentSerializer.serialize(message)));
sender.sendMessage(net.md_5.bungee.chat.ComponentSerializer.parse(ComponentSerializers.JSON.serialize(message)));
} catch (Exception e) {
sendMessage(sender, ComponentSerializer.toLegacy(message, Constants.COLOR_CHAR));
//noinspection deprecation
sendMessage(sender, TextUtils.toLegacy(message));
}
}
@@ -28,22 +28,25 @@ package me.lucko.luckperms.bungee;
import lombok.Getter;
import me.lucko.luckperms.api.Contexts;
import me.lucko.luckperms.api.Logger;
import me.lucko.luckperms.api.PlatformType;
import me.lucko.luckperms.api.context.ContextSet;
import me.lucko.luckperms.api.context.ImmutableContextSet;
import me.lucko.luckperms.api.platform.PlatformType;
import me.lucko.luckperms.bungee.calculators.BungeeCalculatorFactory;
import me.lucko.luckperms.bungee.contexts.BackendServerCalculator;
import me.lucko.luckperms.bungee.messaging.BungeeMessagingService;
import me.lucko.luckperms.bungee.messaging.RedisBungeeMessagingService;
import me.lucko.luckperms.bungee.contexts.BungeeContextManager;
import me.lucko.luckperms.bungee.contexts.RedisBungeeCalculator;
import me.lucko.luckperms.bungee.listeners.BungeeConnectionListener;
import me.lucko.luckperms.bungee.listeners.BungeePermissionCheckListener;
import me.lucko.luckperms.bungee.messaging.BungeeMessagingFactory;
import me.lucko.luckperms.bungee.util.RedisBungeeUtil;
import me.lucko.luckperms.common.api.ApiHandler;
import me.lucko.luckperms.common.actionlog.LogDispatcher;
import me.lucko.luckperms.common.api.ApiProvider;
import me.lucko.luckperms.common.api.ApiSingletonUtils;
import me.lucko.luckperms.common.buffers.BufferedRequest;
import me.lucko.luckperms.common.buffers.UpdateTaskBuffer;
import me.lucko.luckperms.common.caching.handlers.CachedStateManager;
import me.lucko.luckperms.common.calculators.CalculatorFactory;
import me.lucko.luckperms.common.commands.CommandManager;
import me.lucko.luckperms.common.commands.sender.Sender;
import me.lucko.luckperms.common.config.AbstractConfiguration;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.config.LuckPermsConfiguration;
import me.lucko.luckperms.common.contexts.ContextManager;
@@ -53,6 +56,7 @@ import me.lucko.luckperms.common.dependencies.DependencyManager;
import me.lucko.luckperms.common.locale.LocaleManager;
import me.lucko.luckperms.common.locale.NoopLocaleManager;
import me.lucko.luckperms.common.locale.SimpleLocaleManager;
import me.lucko.luckperms.common.logging.Logger;
import me.lucko.luckperms.common.logging.SenderLogger;
import me.lucko.luckperms.common.managers.GenericGroupManager;
import me.lucko.luckperms.common.managers.GenericTrackManager;
@@ -60,16 +64,14 @@ import me.lucko.luckperms.common.managers.GenericUserManager;
import me.lucko.luckperms.common.managers.GroupManager;
import me.lucko.luckperms.common.managers.TrackManager;
import me.lucko.luckperms.common.managers.UserManager;
import me.lucko.luckperms.common.messaging.InternalMessagingService;
import me.lucko.luckperms.common.messaging.NoopMessagingService;
import me.lucko.luckperms.common.messaging.RedisMessagingService;
import me.lucko.luckperms.common.messaging.ExtendedMessagingService;
import me.lucko.luckperms.common.model.User;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.plugin.LuckPermsScheduler;
import me.lucko.luckperms.common.plugin.SchedulerAdapter;
import me.lucko.luckperms.common.storage.Storage;
import me.lucko.luckperms.common.storage.StorageFactory;
import me.lucko.luckperms.common.storage.StorageType;
import me.lucko.luckperms.common.storage.backing.file.FileWatcher;
import me.lucko.luckperms.common.storage.dao.file.FileWatcher;
import me.lucko.luckperms.common.tasks.CacheHousekeepingTask;
import me.lucko.luckperms.common.tasks.ExpireTemporaryTask;
import me.lucko.luckperms.common.tasks.UpdateTask;
@@ -83,20 +85,20 @@ import net.md_5.bungee.api.plugin.Plugin;
import java.io.File;
import java.io.InputStream;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* LuckPerms implementation for the BungeeCord API.
*/
@Getter
public class LPBungeePlugin extends Plugin implements LuckPermsPlugin {
private final Set<UUID> ignoringLogs = ConcurrentHashMap.newKeySet();
private Set<UUID> uniqueConnections = ConcurrentHashMap.newKeySet();
private long startTime;
private LuckPermsScheduler scheduler;
private SchedulerAdapter scheduler;
private CommandManager commandManager;
private LuckPermsConfiguration configuration;
private UserManager userManager;
@@ -104,7 +106,7 @@ public class LPBungeePlugin extends Plugin implements LuckPermsPlugin {
private TrackManager trackManager;
private Storage storage;
private FileWatcher fileWatcher = null;
private InternalMessagingService messagingService = null;
private ExtendedMessagingService messagingService = null;
private UuidCache uuidCache;
private ApiProvider apiProvider;
private Logger log;
@@ -116,16 +118,18 @@ public class LPBungeePlugin extends Plugin implements LuckPermsPlugin {
private VerboseHandler verboseHandler;
private BungeeSenderFactory senderFactory;
private PermissionVault permissionVault;
private LogDispatcher logDispatcher;
private Set<UUID> uniqueConnections = ConcurrentHashMap.newKeySet();
@Override
public void onLoad() {
// setup minimal functionality in order to load initial dependencies
scheduler = new LPBungeeScheduler(this);
scheduler = new BungeeSchedulerAdapter(this);
localeManager = new NoopLocaleManager();
senderFactory = new BungeeSenderFactory(this);
log = new SenderLogger(this, getConsoleSender());
DependencyManager.loadDependencies(this, Collections.singletonList(Dependency.CAFFEINE));
DependencyManager.loadDependencies(this, Collections.singleton(Dependency.CAFFEINE));
}
@Override
@@ -134,17 +138,18 @@ public class LPBungeePlugin extends Plugin implements LuckPermsPlugin {
LuckPermsPlugin.sendStartupBanner(getConsoleSender(), this);
verboseHandler = new VerboseHandler(scheduler.async(), getVersion());
permissionVault = new PermissionVault(scheduler.async());
logDispatcher = new LogDispatcher(this);
getLog().info("Loading configuration...");
configuration = new BungeeConfig(this);
configuration = new AbstractConfiguration(this, new BungeeConfigAdapter(this));
configuration.init();
configuration.loadAll();
Set<StorageType> storageTypes = StorageFactory.getRequiredTypes(this, StorageType.H2);
DependencyManager.loadDependencies(this, storageTypes);
DependencyManager.loadStorageDependencies(this, storageTypes);
// register events
getProxy().getPluginManager().registerListener(this, new BungeeListener(this));
getProxy().getPluginManager().registerListener(this, new BungeeConnectionListener(this));
getProxy().getPluginManager().registerListener(this, new BungeePermissionCheckListener(this));
if (getConfiguration().get(ConfigKeys.WATCH_FILES)) {
fileWatcher = new FileWatcher(this);
@@ -155,72 +160,18 @@ public class LPBungeePlugin extends Plugin implements LuckPermsPlugin {
storage = StorageFactory.getInstance(this, StorageType.H2);
// initialise messaging
String messagingType = getConfiguration().get(ConfigKeys.MESSAGING_SERVICE).toLowerCase();
if (messagingType.equals("none") && getConfiguration().get(ConfigKeys.REDIS_ENABLED)) {
messagingType = "redis";
}
if (!messagingType.equals("none")) {
getLog().info("Loading messaging service... [" + messagingType.toUpperCase() + "]");
}
if (messagingType.equals("redis")) {
if (getConfiguration().get(ConfigKeys.REDIS_ENABLED)) {
RedisMessagingService redis = new RedisMessagingService(this);
try {
redis.init(getConfiguration().get(ConfigKeys.REDIS_ADDRESS), getConfiguration().get(ConfigKeys.REDIS_PASSWORD));
messagingService = redis;
} catch (Exception e) {
getLog().warn("Couldn't load redis...");
e.printStackTrace();
}
} else {
getLog().warn("Messaging Service was set to redis, but redis is not enabled!");
}
} else if (messagingType.equals("bungee")) {
BungeeMessagingService bungeeMessaging = new BungeeMessagingService(this);
bungeeMessaging.init();
messagingService = bungeeMessaging;
} else if (messagingType.equals("redisbungee")) {
if (getProxy().getPluginManager().getPlugin("RedisBungee") == null) {
getLog().warn("RedisBungee plugin not present.");
} else {
RedisBungeeMessagingService redisBungeeMessaging = new RedisBungeeMessagingService(this);
redisBungeeMessaging.init();
messagingService = redisBungeeMessaging;
}
} else if (!messagingType.equals("none")) {
getLog().warn("Messaging service '" + messagingType + "' not recognised.");
}
if (messagingService == null) {
messagingService = new NoopMessagingService();
}
messagingService = new BungeeMessagingFactory(this).getInstance();
// setup the update task buffer
updateTaskBuffer = new BufferedRequest<Void>(1000L, this::doAsync) {
@Override
protected Void perform() {
new UpdateTask(LPBungeePlugin.this).run();
return null;
}
};
updateTaskBuffer = new UpdateTaskBuffer(this);
// load locale
localeManager = new SimpleLocaleManager();
File locale = new File(getDataFolder(), "lang.yml");
if (locale.exists()) {
getLog().info("Found lang.yml - loading messages...");
try {
localeManager.loadFromFile(locale);
} catch (Exception e) {
e.printStackTrace();
}
}
localeManager.tryLoad(this, new File(getDataFolder(), "lang.yml"));
// register commands
commandManager = new CommandManager(this);
getProxy().getPluginManager().registerCommand(this, new BungeeCommand(this, commandManager));
getProxy().getPluginManager().registerCommand(this, new BungeeCommandExecutor(this, commandManager));
// disable the default Bungee /perms command so it gets handled by the Bukkit plugin
getProxy().getDisabledCommands().add("perms");
@@ -234,30 +185,18 @@ public class LPBungeePlugin extends Plugin implements LuckPermsPlugin {
calculatorFactory = new BungeeCalculatorFactory(this);
cachedStateManager = new CachedStateManager();
contextManager = new ContextManager<ProxiedPlayer>() {
@Override
public Contexts formContexts(ProxiedPlayer player, ImmutableContextSet contextSet) {
return new Contexts(
contextSet,
getConfiguration().get(ConfigKeys.INCLUDING_GLOBAL_PERMS),
getConfiguration().get(ConfigKeys.INCLUDING_GLOBAL_WORLD_PERMS),
true,
getConfiguration().get(ConfigKeys.APPLYING_GLOBAL_GROUPS),
getConfiguration().get(ConfigKeys.APPLYING_GLOBAL_WORLD_GROUPS),
false
);
}
};
// setup context manager
contextManager = new BungeeContextManager(this);
contextManager.registerCalculator(new BackendServerCalculator(this));
contextManager.registerStaticCalculator(new LuckPermsCalculator(getConfiguration()));
BackendServerCalculator serverCalculator = new BackendServerCalculator(this);
contextManager.registerCalculator(serverCalculator);
LuckPermsCalculator<ProxiedPlayer> staticCalculator = new LuckPermsCalculator<>(getConfiguration());
contextManager.registerCalculator(staticCalculator, true);
if (getProxy().getPluginManager().getPlugin("RedisBungee") != null) {
contextManager.registerStaticCalculator(new RedisBungeeCalculator());
}
// register with the LP API
apiProvider = new ApiProvider(this);
ApiHandler.registerProvider(apiProvider);
ApiSingletonUtils.registerProvider(apiProvider);
// schedule update tasks
int mins = getConfiguration().get(ConfigKeys.SYNC_TIME);
@@ -269,7 +208,7 @@ public class LPBungeePlugin extends Plugin implements LuckPermsPlugin {
// run an update instantly.
getLog().info("Performing initial data load...");
updateTaskBuffer.requestDirectly();
new UpdateTask(this, true).run();
// register tasks
scheduler.asyncRepeating(new ExpireTemporaryTask(this), 60L);
@@ -280,6 +219,9 @@ public class LPBungeePlugin extends Plugin implements LuckPermsPlugin {
@Override
public void onDisable() {
permissionVault.shutdown();
verboseHandler.shutdown();
getLog().info("Closing storage...");
storage.shutdown();
@@ -292,12 +234,23 @@ public class LPBungeePlugin extends Plugin implements LuckPermsPlugin {
messagingService.close();
}
ApiHandler.unregisterProvider();
ApiSingletonUtils.unregisterProvider();
getLog().info("Shutting down internal scheduler...");
scheduler.shutdown();
getProxy().getScheduler().cancel(this);
getProxy().getPluginManager().unregisterListeners(this);
getLog().info("Goodbye!");
}
@Override
public Optional<ExtendedMessagingService> getMessagingService() {
return Optional.ofNullable(messagingService);
}
public Optional<FileWatcher> getFileWatcher() {
return Optional.ofNullable(fileWatcher);
}
@Override
@@ -363,13 +316,13 @@ public class LPBungeePlugin extends Plugin implements LuckPermsPlugin {
}
@Override
public List<String> getPlayerList() {
return getProxy().getPlayers().stream().map(ProxiedPlayer::getName).collect(Collectors.toList());
public Stream<String> getPlayerList() {
return getProxy().getPlayers().stream().map(ProxiedPlayer::getName);
}
@Override
public Set<UUID> getOnlinePlayers() {
return getProxy().getPlayers().stream().map(ProxiedPlayer::getUniqueId).collect(Collectors.toSet());
public Stream<UUID> getOnlinePlayers() {
return getProxy().getPlayers().stream().map(ProxiedPlayer::getUniqueId);
}
@Override
@@ -379,24 +332,15 @@ public class LPBungeePlugin extends Plugin implements LuckPermsPlugin {
}
@Override
public List<Sender> getOnlineSenders() {
return getProxy().getPlayers().stream()
.map(p -> getSenderFactory().wrap(p))
.collect(Collectors.toList());
public Stream<Sender> getOnlineSenders() {
return Stream.concat(
Stream.of(getConsoleSender()),
getProxy().getPlayers().stream().map(p -> getSenderFactory().wrap(p))
);
}
@Override
public Sender getConsoleSender() {
return getSenderFactory().wrap(getProxy().getConsole());
}
@Override
public Set<Contexts> getPreProcessContexts(boolean op) {
Set<ImmutableContextSet> c = new HashSet<>();
c.add(ContextSet.empty());
c.add(ContextSet.singleton("server", getConfiguration().get(ConfigKeys.SERVER)));
return c.stream()
.map(set -> contextManager.formContexts(null, set))
.collect(Collectors.toSet());
}
}
@@ -33,6 +33,7 @@ import me.lucko.luckperms.api.Contexts;
import me.lucko.luckperms.bungee.LPBungeePlugin;
import me.lucko.luckperms.common.calculators.AbstractCalculatorFactory;
import me.lucko.luckperms.common.calculators.PermissionCalculator;
import me.lucko.luckperms.common.calculators.PermissionCalculatorMetadata;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.model.User;
import me.lucko.luckperms.common.processors.MapProcessor;
@@ -60,7 +61,8 @@ public class BungeeCalculatorFactory extends AbstractCalculatorFactory {
processors.add(new WildcardProcessor());
}
return registerCalculator(new PermissionCalculator(plugin, user.getFriendlyName(), processors.build()));
PermissionCalculatorMetadata meta = PermissionCalculatorMetadata.of(user.getFriendlyName(), contexts.getContexts());
return registerCalculator(new PermissionCalculator(plugin, meta, processors.build()));
}
@Override
@@ -39,7 +39,7 @@ public class BackendServerCalculator implements ContextCalculator<ProxiedPlayer>
private static final String WORLD_KEY = "world";
private static String getServer(ProxiedPlayer player) {
return player.getServer() == null ? null : (player.getServer().getInfo() == null ? null : player.getServer().getInfo().getName());
return player.getServer() == null ? null : (player.getServer().getInfo() == null ? null : player.getServer().getInfo().getName().toLowerCase());
}
private final LuckPermsPlugin plugin;
@@ -49,7 +49,7 @@ public class BackendServerCalculator implements ContextCalculator<ProxiedPlayer>
String server = getServer(subject);
while (server != null && !accumulator.has(WORLD_KEY, server)) {
accumulator.add(WORLD_KEY, server);
server = plugin.getConfiguration().get(ConfigKeys.WORLD_REWRITES).getOrDefault(server, server);
server = plugin.getConfiguration().get(ConfigKeys.WORLD_REWRITES).getOrDefault(server, server).toLowerCase();
}
return accumulator;
@@ -0,0 +1,53 @@
/*
* 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.bungee.contexts;
import me.lucko.luckperms.api.Contexts;
import me.lucko.luckperms.api.context.ImmutableContextSet;
import me.lucko.luckperms.bungee.LPBungeePlugin;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.contexts.AbstractContextManager;
import net.md_5.bungee.api.connection.ProxiedPlayer;
public class BungeeContextManager extends AbstractContextManager<ProxiedPlayer> {
public BungeeContextManager(LPBungeePlugin plugin) {
super(plugin, ProxiedPlayer.class);
}
@Override
public Contexts formContexts(ProxiedPlayer subject, ImmutableContextSet contextSet) {
return new Contexts(
contextSet,
plugin.getConfiguration().get(ConfigKeys.INCLUDING_GLOBAL_PERMS),
plugin.getConfiguration().get(ConfigKeys.INCLUDING_GLOBAL_WORLD_PERMS),
true,
plugin.getConfiguration().get(ConfigKeys.APPLYING_GLOBAL_GROUPS),
plugin.getConfiguration().get(ConfigKeys.APPLYING_GLOBAL_WORLD_GROUPS),
false
);
}
}
@@ -0,0 +1,45 @@
/*
* 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.bungee.contexts;
import com.imaginarycode.minecraft.redisbungee.RedisBungee;
import com.imaginarycode.minecraft.redisbungee.RedisBungeeAPI;
import me.lucko.luckperms.api.context.MutableContextSet;
import me.lucko.luckperms.api.context.StaticContextCalculator;
public class RedisBungeeCalculator implements StaticContextCalculator {
private static final String PROXY_KEY = "proxy";
@Override
public MutableContextSet giveApplicableContext(MutableContextSet accumulator) {
RedisBungeeAPI redisBungee = RedisBungee.getApi();
if (redisBungee != null) {
accumulator.add(PROXY_KEY, redisBungee.getServerId());
}
return accumulator;
}
}
@@ -23,14 +23,11 @@
* SOFTWARE.
*/
package me.lucko.luckperms.bungee;
package me.lucko.luckperms.bungee.listeners;
import lombok.RequiredArgsConstructor;
import me.lucko.luckperms.api.Contexts;
import me.lucko.luckperms.api.Tristate;
import me.lucko.luckperms.api.context.MutableContextSet;
import me.lucko.luckperms.bungee.event.TristateCheckEvent;
import me.lucko.luckperms.bungee.LPBungeePlugin;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.locale.Message;
import me.lucko.luckperms.common.model.User;
@@ -40,19 +37,16 @@ import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.connection.PendingConnection;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.event.LoginEvent;
import net.md_5.bungee.api.event.PermissionCheckEvent;
import net.md_5.bungee.api.event.PlayerDisconnectEvent;
import net.md_5.bungee.api.event.PostLoginEvent;
import net.md_5.bungee.api.event.ServerConnectEvent;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.event.EventHandler;
import net.md_5.bungee.event.EventPriority;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
@RequiredArgsConstructor
public class BungeeListener implements Listener {
public class BungeeConnectionListener implements Listener {
private final LPBungeePlugin plugin;
@EventHandler(priority = EventPriority.LOW)
@@ -70,20 +64,30 @@ public class BungeeListener implements Listener {
final PendingConnection c = e.getConnection();
if (plugin.getConfiguration().get(ConfigKeys.DEBUG_LOGINS)) {
plugin.getLog().info("Processing pre-login for " + c.getUniqueId() + " - " + c.getName());
}
/* there was an issue connecting to the DB, performing file i/o, etc.
as this is bungeecord, we will still allow the login, as players can't really do much harm without permissions data.
the proxy will just fallback to using the config file perms. */
if (!plugin.getStorage().isAcceptingLogins()) {
// log that the user tried to login, but was denied at this stage.
plugin.getLog().warn("Permissions storage is not loaded. No permissions data will be loaded for: " + c.getUniqueId() + " - " + c.getName());
if (plugin.getConfiguration().get(ConfigKeys.CANCEL_FAILED_LOGINS)) {
// cancel the login attempt
e.setCancelReason(TextComponent.fromLegacyText(Message.LOADING_ERROR.asString(plugin.getLocaleManager())));
e.setCancelled(true);
} else {
// log that the user tried to login, but was denied at this stage.
plugin.getLog().warn("Permissions storage is not loaded. No permissions data will be loaded for: " + c.getUniqueId() + " - " + c.getName());
}
e.completeIntent(plugin);
return;
}
plugin.doAsync(() -> {
plugin.getScheduler().doAsync(() -> {
plugin.getUniqueConnections().add(c.getUniqueId());
/* Actually process the login for the connection.
@@ -96,12 +100,20 @@ public class BungeeListener implements Listener {
- creating a user instance in the UserManager for this connection.
- setting up cached data. */
try {
LoginHelper.loadUser(plugin, c.getUniqueId(), c.getName(), true);
User user = LoginHelper.loadUser(plugin, c.getUniqueId(), c.getName(), true);
plugin.getApiProvider().getEventFactory().handleUserLoginProcess(c.getUniqueId(), c.getName(), user);
} catch (Exception ex) {
ex.printStackTrace();
// there was some error loading
plugin.getLog().warn("Error loading data. No permissions data will be loaded for: " + c.getUniqueId() + " - " + c.getName());
if (plugin.getConfiguration().get(ConfigKeys.CANCEL_FAILED_LOGINS)) {
// cancel the login attempt
e.setCancelReason(TextComponent.fromLegacyText(Message.LOADING_ERROR.asString(plugin.getLocaleManager())));
e.setCancelled(true);
} else {
plugin.getLog().warn("Error loading data. No permissions data will be loaded for: " + c.getUniqueId() + " - " + c.getName());
}
}
// finally, complete our intent to modify state, so the proxy can continue handling the connection.
@@ -118,14 +130,25 @@ public class BungeeListener implements Listener {
final ProxiedPlayer player = e.getPlayer();
final User user = plugin.getUserManager().getIfLoaded(plugin.getUuidCache().getUUID(e.getPlayer().getUniqueId()));
if (user == null) {
plugin.getProxy().getScheduler().schedule(plugin, () -> {
if (!player.isConnected()) {
return;
}
if (plugin.getConfiguration().get(ConfigKeys.DEBUG_LOGINS)) {
plugin.getLog().info("Processing post-login for " + player.getUniqueId() + " - " + player.getName());
}
player.sendMessage(new TextComponent(Message.LOADING_ERROR.asString(plugin.getLocaleManager())));
}, 3, TimeUnit.SECONDS);
if (user == null) {
if (plugin.getConfiguration().get(ConfigKeys.CANCEL_FAILED_LOGINS)) {
// disconnect the user
plugin.getLog().warn("User " + player.getUniqueId() + " - " + player.getName() + " doesn't have data pre-loaded - cancelling login.");
e.getPlayer().disconnect(TextComponent.fromLegacyText(Message.LOADING_ERROR.asString(plugin.getLocaleManager())));
} else {
// just send a message
plugin.getProxy().getScheduler().schedule(plugin, () -> {
if (!player.isConnected()) {
return;
}
player.sendMessage(TextComponent.fromLegacyText(Message.LOADING_ERROR.asString(plugin.getLocaleManager())));
}, 1, TimeUnit.SECONDS);
}
}
}
@@ -136,70 +159,4 @@ public class BungeeListener implements Listener {
plugin.getUserManager().scheduleUnload(e.getPlayer().getUniqueId());
}
@EventHandler(priority = EventPriority.HIGH)
public void onPlayerPermissionCheck(PermissionCheckEvent e) {
if (!(e.getSender() instanceof ProxiedPlayer)) {
return;
}
final ProxiedPlayer player = ((ProxiedPlayer) e.getSender());
User user = plugin.getUserManager().getIfLoaded(plugin.getUuidCache().getUUID(player.getUniqueId()));
if (user == null) {
e.setHasPermission(false);
return;
}
Contexts contexts = plugin.getContextManager().getApplicableContexts(player);
Tristate result = user.getUserData().getPermissionData(contexts).getPermissionValue(e.getPermission());
if (result == Tristate.UNDEFINED && plugin.getConfiguration().get(ConfigKeys.APPLY_BUNGEE_CONFIG_PERMISSIONS)) {
return; // just use the result provided by the proxy when the event was created
}
e.setHasPermission(result.asBoolean());
}
@EventHandler(priority = EventPriority.HIGH)
public void onPlayerTristateCheck(TristateCheckEvent e) {
if (!(e.getSender() instanceof ProxiedPlayer)) {
return;
}
final ProxiedPlayer player = ((ProxiedPlayer) e.getSender());
User user = plugin.getUserManager().getIfLoaded(plugin.getUuidCache().getUUID(player.getUniqueId()));
if (user == null) {
e.setResult(Tristate.UNDEFINED);
return;
}
Contexts contexts = plugin.getContextManager().getApplicableContexts(player);
Tristate result = user.getUserData().getPermissionData(contexts).getPermissionValue(e.getPermission());
if (result == Tristate.UNDEFINED && plugin.getConfiguration().get(ConfigKeys.APPLY_BUNGEE_CONFIG_PERMISSIONS)) {
return; // just use the result provided by the proxy when the event was created
}
e.setResult(result);
}
// We don't pre-process all servers, so we have to do it here.
@EventHandler(priority = EventPriority.LOWEST)
public void onServerSwitch(ServerConnectEvent e) {
String serverName = e.getTarget().getName();
UUID uuid = e.getPlayer().getUniqueId();
plugin.doAsync(() -> {
MutableContextSet set = MutableContextSet.create();
set.add("server", plugin.getConfiguration().get(ConfigKeys.SERVER));
set.add("world", serverName);
User user = plugin.getUserManager().getIfLoaded(plugin.getUuidCache().getUUID(uuid));
if (user == null) {
return;
}
Contexts contexts = plugin.getContextManager().formContexts(e.getPlayer(), set.makeImmutable());
user.getUserData().preCalculate(contexts);
});
}
}
@@ -0,0 +1,93 @@
/*
* 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.bungee.listeners;
import lombok.RequiredArgsConstructor;
import me.lucko.luckperms.api.Contexts;
import me.lucko.luckperms.api.Tristate;
import me.lucko.luckperms.bungee.LPBungeePlugin;
import me.lucko.luckperms.bungee.event.TristateCheckEvent;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.model.User;
import me.lucko.luckperms.common.verbose.CheckOrigin;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.event.PermissionCheckEvent;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.event.EventHandler;
import net.md_5.bungee.event.EventPriority;
@RequiredArgsConstructor
public class BungeePermissionCheckListener implements Listener {
private final LPBungeePlugin plugin;
@EventHandler(priority = EventPriority.HIGH)
public void onPlayerPermissionCheck(PermissionCheckEvent e) {
if (!(e.getSender() instanceof ProxiedPlayer)) {
return;
}
final ProxiedPlayer player = ((ProxiedPlayer) e.getSender());
User user = plugin.getUserManager().getIfLoaded(plugin.getUuidCache().getUUID(player.getUniqueId()));
if (user == null) {
e.setHasPermission(false);
return;
}
Contexts contexts = plugin.getContextManager().getApplicableContexts(player);
Tristate result = user.getUserData().getPermissionData(contexts).getPermissionValue(e.getPermission(), CheckOrigin.PLATFORM_PERMISSION_CHECK);
if (result == Tristate.UNDEFINED && plugin.getConfiguration().get(ConfigKeys.APPLY_BUNGEE_CONFIG_PERMISSIONS)) {
return; // just use the result provided by the proxy when the event was created
}
e.setHasPermission(result.asBoolean());
}
@EventHandler(priority = EventPriority.HIGH)
public void onPlayerTristateCheck(TristateCheckEvent e) {
if (!(e.getSender() instanceof ProxiedPlayer)) {
return;
}
final ProxiedPlayer player = ((ProxiedPlayer) e.getSender());
User user = plugin.getUserManager().getIfLoaded(plugin.getUuidCache().getUUID(player.getUniqueId()));
if (user == null) {
e.setResult(Tristate.UNDEFINED);
return;
}
Contexts contexts = plugin.getContextManager().getApplicableContexts(player);
Tristate result = user.getUserData().getPermissionData(contexts).getPermissionValue(e.getPermission(), CheckOrigin.PLATFORM_LOOKUP_CHECK);
if (result == Tristate.UNDEFINED && plugin.getConfiguration().get(ConfigKeys.APPLY_BUNGEE_CONFIG_PERMISSIONS)) {
return; // just use the result provided by the proxy when the event was created
}
e.setResult(result);
}
}
@@ -0,0 +1,55 @@
/*
* 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.bungee.messaging;
import me.lucko.luckperms.bungee.LPBungeePlugin;
import me.lucko.luckperms.common.messaging.ExtendedMessagingService;
import me.lucko.luckperms.common.messaging.MessagingFactory;
public class BungeeMessagingFactory extends MessagingFactory<LPBungeePlugin> {
public BungeeMessagingFactory(LPBungeePlugin plugin) {
super(plugin);
}
@Override
protected ExtendedMessagingService getServiceFor(String messagingType) {
if (messagingType.equals("bungee")) {
BungeeMessagingService bungeeMessaging = new BungeeMessagingService(getPlugin());
bungeeMessaging.init();
return bungeeMessaging;
} else if (messagingType.equals("redisbungee")) {
if (getPlugin().getProxy().getPluginManager().getPlugin("RedisBungee") == null) {
getPlugin().getLog().warn("RedisBungee plugin not present.");
} else {
RedisBungeeMessagingService redisBungeeMessaging = new RedisBungeeMessagingService(getPlugin());
redisBungeeMessaging.init();
return redisBungeeMessaging;
}
}
return super.getServiceFor(messagingType);
}
}
@@ -31,6 +31,7 @@ import com.google.common.io.ByteStreams;
import me.lucko.luckperms.bungee.LPBungeePlugin;
import me.lucko.luckperms.common.messaging.AbstractMessagingService;
import me.lucko.luckperms.common.messaging.ExtendedMessagingService;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.connection.ProxiedPlayer;
@@ -39,7 +40,7 @@ import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.event.EventHandler;
/**
* An implementation of {@link me.lucko.luckperms.api.MessagingService} using the plugin messaging channels.
* An implementation of {@link ExtendedMessagingService} using the plugin messaging channels.
*/
public class BungeeMessagingService extends AbstractMessagingService implements Listener {
private final LPBungeePlugin plugin;
@@ -60,7 +61,7 @@ public class BungeeMessagingService extends AbstractMessagingService implements
}
@Override
protected void sendMessage(String channel, String message) {
protected void sendMessage(String message) {
ByteArrayDataOutput out = ByteStreams.newDataOutput();
out.writeUTF(message);
@@ -68,7 +69,7 @@ public class BungeeMessagingService extends AbstractMessagingService implements
byte[] data = out.toByteArray();
for (ServerInfo server : plugin.getProxy().getServers().values()) {
server.sendData(channel, data, true);
server.sendData(CHANNEL, data, true);
}
}
@@ -87,9 +88,9 @@ public class BungeeMessagingService extends AbstractMessagingService implements
ByteArrayDataInput in = ByteStreams.newDataInput(e.getData());
String msg = in.readUTF();
onMessage(e.getTag(), msg, u -> {
onMessage(msg, u -> {
// Forward to other servers
plugin.doAsync(() -> sendMessage(CHANNEL, "update:" + u.toString()));
plugin.getScheduler().doAsync(() -> sendMessage(u));
});
}
}
@@ -31,12 +31,13 @@ import com.imaginarycode.minecraft.redisbungee.events.PubSubMessageEvent;
import me.lucko.luckperms.bungee.LPBungeePlugin;
import me.lucko.luckperms.common.messaging.AbstractMessagingService;
import me.lucko.luckperms.common.messaging.ExtendedMessagingService;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.event.EventHandler;
/**
* An implementation of {@link me.lucko.luckperms.api.MessagingService} using Redis, via RedisBungee's API.
* An implementation of {@link ExtendedMessagingService} using Redis, via RedisBungee's API.
*/
public class RedisBungeeMessagingService extends AbstractMessagingService implements Listener {
private final LPBungeePlugin plugin;
@@ -63,12 +64,16 @@ public class RedisBungeeMessagingService extends AbstractMessagingService implem
}
@Override
protected void sendMessage(String channel, String message) {
redisBungee.sendChannelMessage(channel, message);
protected void sendMessage(String message) {
redisBungee.sendChannelMessage(CHANNEL, message);
}
@EventHandler
public void onMessage(PubSubMessageEvent e) {
onMessage(e.getChannel(), e.getMessage(), null);
if (!e.getChannel().equals(CHANNEL)) {
return;
}
onMessage(e.getMessage(), null);
}
}
+40 -7
View File
@@ -73,6 +73,28 @@ use-server-uuids: true
# try to find a uuid for a username using RedisBungee, if installed.
use-server-uuid-cache: false
# If set to true, LuckPerms will allow usernames with non alphanumeric characters.
#
# Note that due to the design of the storage implementation, usernames must still be
# 16 characters or less.
allow-invalid-usernames: false
# If LuckPerms should produce extra logging output when it handles logins.
# Useful if you're having issues with UUID forwarding or data not being loaded.
debug-logins: false
# If LuckPerms should ensure all players have permission data when they connect to the server.
#
# When set to true, LuckPerms will cancel login attempts if it is unable to load permissions data
# for a user, of if the storage provider is unavailable.
#
# When set to false, LuckPerms will allow a player to connect regardless of whether their
# permissions data could be loaded.
#
# This option does not exist on other platforms, and effectively defaults to true - however,
# the option is provided on BungeeCord, as it is less likely to be so dependant on permissions.
cancel-failed-logins: false
# If the plugin should send log notifications to users whenever permissions are modified.
log-notify: true
@@ -82,11 +104,6 @@ world-rewrite:
# world_nether: world
# world_the_end: world
# Rewrites group names. The underlying name of the group does not change, just the output in
# commands / placeholders / Vault.
group-name-rewrite:
# default: Member
# Controls how temporary permissions/parents/meta should be accumulated
#
# The default behaviour is "deny"
@@ -102,7 +119,11 @@ temporary-add-behaviour: deny
# -> parents-by-weight just use the users most highly weighted parent
# -> all-parents-by-weight same as above, but calculates based upon all parents inherited from both
# directly and indirectly
primary-group-calculation: stored
primary-group-calculation: parents-by-weight
# If set to false, the plugin will allow a Users primary group to be removed with the
# 'parent remove' command, and will set their primary group back to default.
prevent-primary-group-removal: false
# If the plugin should check for "extra" permissions with users run LP commands.
#
@@ -157,6 +178,8 @@ group-weight:
# - lowest
# - highest_own
# - lowest_own
# - highest_inherited
# - lowest_inherited
# - highest_on_track_<track>
# - lowest_on_track_<track>
# - highest_not_on_track_<track>
@@ -187,7 +210,7 @@ meta-formatting:
# Which storage method the plugin should use.
#
# See: https://github.com/lucko/LuckPerms/wiki/Choosing-a-Storage-type
# Currently supported: mysql, mariadb, postgresql, sqlite, h2, json, yaml, mongodb
# Currently supported: mysql, mariadb, postgresql, sqlite, h2, json, yaml, hocon, mongodb
#
# Fill out connection info below if you're using MySQL, MariaDB, PostgreSQL or MongoDB
# If your MySQL server supports it, the "mariadb" option is preferred over "mysql".
@@ -263,6 +286,16 @@ messaging-service: none
# If LuckPerms should automatically push updates after a change has been made with a command.
auto-push-updates: true
# If LuckPerms should push logging entries to connected servers via the messaging service.
push-log-entries: true
# If LuckPerms should broadcast received logging entries to players on this platform.
#
# If you have LuckPerms installed on your backend servers as well as a BungeeCord proxy, you should
# set this option to false on either your backends or your proxies, to avoid players being messaged
# twice about log entries.
broadcast-received-log-entries: false
# Settings for Redis.
# Port 6379 is used by default; set address to "host:port" if differs
redis:
+63 -5
View File
@@ -5,7 +5,7 @@
<parent>
<artifactId>luckperms</artifactId>
<groupId>me.lucko.luckperms</groupId>
<version>3.3-SNAPSHOT</version>
<version>4.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -39,14 +39,72 @@
<dependency>
<groupId>net.kyori</groupId>
<artifactId>text</artifactId>
<version>1.12-SNAPSHOT</version>
<version>1.11-1.2.0</version>
<scope>compile</scope>
<exclusions>
<exclusion>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
</exclusion>
<exclusion>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</exclusion>
<exclusion>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- configurate -->
<dependency>
<groupId>ninja.leaping.configurate</groupId>
<artifactId>configurate-core</artifactId>
<version>3.3</version>
<scope>compile</scope>
<optional>true</optional>
<exclusions>
<exclusion>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>ninja.leaping.configurate</groupId>
<artifactId>configurate-yaml</artifactId>
<version>3.3</version>
<scope>provided</scope>
<exclusions>
<exclusion>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>ninja.leaping.configurate</groupId>
<artifactId>configurate-gson</artifactId>
<version>3.3</version>
<scope>provided</scope>
<exclusions>
<exclusion>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>ninja.leaping.configurate</groupId>
<artifactId>configurate-hocon</artifactId>
<version>3.3</version>
<scope>provided</scope>
</dependency>
<!-- HikariCP -->
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>2.6.3</version>
<version>2.7.1</version>
<scope>provided</scope>
</dependency>
<!-- Jedis -->
@@ -60,7 +118,7 @@
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongo-java-driver</artifactId>
<version>3.4.2</version>
<version>3.5.0</version>
<scope>provided</scope>
</dependency>
<!-- SnakeYAML -->
@@ -88,7 +146,7 @@
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>2.5.2</version>
<version>2.5.6</version>
<scope>provided</scope>
</dependency>
<!-- Lombok -->
@@ -0,0 +1,393 @@
/*
* 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.common.actionlog;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.ToString;
import com.google.common.base.Preconditions;
import com.google.common.collect.Maps;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import me.lucko.luckperms.api.LogEntry;
import me.lucko.luckperms.api.context.ContextSet;
import me.lucko.luckperms.common.commands.sender.Sender;
import me.lucko.luckperms.common.model.Group;
import me.lucko.luckperms.common.model.PermissionHolder;
import me.lucko.luckperms.common.model.Track;
import me.lucko.luckperms.common.model.User;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.utils.DateUtil;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;
/**
* An implementation of {@link LogEntry} and {@link LogEntry.Builder},
* with helper methods for populating and using the entry using internal
* LuckPerms classes.
*/
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class ExtendedLogEntry implements LogEntry {
private static final String FORMAT = "&8(&e%s&8) [&a%s&8] (&b%s&8) &7--> &f%s";
/**
* Compares two LogEntries
*
* @since 3.3
*/
public static final Comparator<LogEntry> COMPARATOR = Comparator
.comparingLong(LogEntry::getTimestamp)
.thenComparing(LogEntry::getActor)
.thenComparing(LogEntry::getActorName, String.CASE_INSENSITIVE_ORDER)
.thenComparing(LogEntry::getType)
.thenComparing(e -> {
UUID u = e.getActed().orElse(null);
return u == null ? "" : u.toString();
})
.thenComparing(LogEntry::getActedName, String.CASE_INSENSITIVE_ORDER)
.thenComparing(LogEntry::getAction);
/**
* Compares two LogEntries in reverse order
*
* @since 3.3
* @see #COMPARATOR
*/
public static final Comparator<LogEntry> REVERSE_ORDER = COMPARATOR.reversed();
/**
* Creates a new log entry builder
*
* @return a new builder
*/
public static ExtendedLogEntryBuilder build() {
return new ExtendedLogEntryBuilder();
}
private final long timestamp;
private final UUID actor;
private final String actorName;
private final Type type;
private final UUID acted;
private final String actedName;
private final String action;
@Override
public long getTimestamp() {
return timestamp;
}
@Override
public UUID getActor() {
return actor;
}
@Override
public String getActorName() {
return actorName;
}
@Override
public Type getType() {
return type;
}
@Override
public Optional<UUID> getActed() {
return Optional.ofNullable(acted);
}
@Override
public String getActedName() {
return actedName;
}
@Override
public String getAction() {
return action;
}
@Override
public int compareTo(LogEntry other) {
Preconditions.checkNotNull(other, "other");
return COMPARATOR.compare(this, other);
}
public boolean matchesSearch(String query) {
query = Preconditions.checkNotNull(query, "query").toLowerCase();
return actorName.toLowerCase().contains(query) ||
actedName.toLowerCase().contains(query) ||
action.toLowerCase().contains(query);
}
public String getFormatted() {
return String.format(FORMAT,
String.valueOf(actorName).equals("null") ? actor.toString() : actorName,
Character.toString(type.getCode()),
String.valueOf(actedName).equals("null") && acted != null ? acted.toString() : actedName,
action
);
}
public void submit(LuckPermsPlugin plugin, Sender sender) {
plugin.getLogDispatcher().dispatch(this, sender);
}
@Override
public String toString() {
return "LogEntry(" +
"timestamp=" + this.getTimestamp() + ", " +
"actor=" + this.getActor() + ", " +
"actorName=" + this.getActorName() + ", " +
"type=" + this.getType() + ", " +
"acted=" + this.getActed() + ", " +
"actedName=" + this.getActedName() + ", " +
"action=" + this.getAction() + ")";
}
@Override
public boolean equals(Object o) {
if (o == this) return true;
if (!(o instanceof LogEntry)) return false;
final LogEntry other = (LogEntry) o;
return this.getTimestamp() == other.getTimestamp() &&
this.getActor().equals(other.getActor()) &&
this.getActorName().equals(other.getActorName()) &&
this.getType() == other.getType() &&
(this.getActed() == null ? other.getActed() == null : this.getActed().equals(other.getActed())) &&
this.getActedName().equals(other.getActedName()) &&
this.getAction().equals(other.getAction());
}
@Override
public int hashCode() {
final int PRIME = 59;
int result = 1;
result = result * PRIME + (int) (this.getTimestamp() >>> 32 ^ this.getTimestamp());
result = result * PRIME + this.getActor().hashCode();
result = result * PRIME + this.getActorName().hashCode();
result = result * PRIME + this.getType().hashCode();
result = result * PRIME + (this.getActed() == null ? 43 : this.getActed().hashCode());
result = result * PRIME + this.getActedName().hashCode();
result = result * PRIME + this.getAction().hashCode();
return result;
}
@ToString
public static class ExtendedLogEntryBuilder implements LogEntry.Builder {
private long timestamp = 0L;
private UUID actor = null;
private String actorName = null;
private Type type = null;
private UUID acted = null;
private String actedName = null;
private String action = null;
@Override
public ExtendedLogEntryBuilder setTimestamp(long timestamp) {
this.timestamp = timestamp;
return this;
}
@Override
public ExtendedLogEntryBuilder setActor(UUID actor) {
this.actor = Preconditions.checkNotNull(actor, "actor");
return this;
}
@Override
public ExtendedLogEntryBuilder setActorName(String actorName) {
this.actorName = Preconditions.checkNotNull(actorName, "actorName");
return this;
}
@Override
public ExtendedLogEntryBuilder setType(Type type) {
this.type = Preconditions.checkNotNull(type, "type");
return this;
}
@Override
public ExtendedLogEntryBuilder setActed(UUID acted) {
this.acted = acted; // nullable
return this;
}
@Override
public ExtendedLogEntryBuilder setActedName(String actedName) {
this.actedName = Preconditions.checkNotNull(actedName, "actedName");
return this;
}
@Override
public ExtendedLogEntryBuilder setAction(String action) {
this.action = Preconditions.checkNotNull(action, "action");
return this;
}
public ExtendedLogEntryBuilder timestamp(long timestamp) {
return setTimestamp(timestamp);
}
public ExtendedLogEntryBuilder actor(UUID actor) {
return setActor(actor);
}
public ExtendedLogEntryBuilder actorName(String actorName) {
return setActorName(actorName);
}
public ExtendedLogEntryBuilder type(Type type) {
return setType(type);
}
public ExtendedLogEntryBuilder acted(UUID acted) {
return setActed(acted);
}
public ExtendedLogEntryBuilder actedName(String actedName) {
return setActedName(actedName);
}
public ExtendedLogEntryBuilder action(String action) {
return setAction(action);
}
public ExtendedLogEntryBuilder actor(Sender actor) {
actorName(actor.getNameWithLocation());
actor(actor.getUuid());
return this;
}
public ExtendedLogEntryBuilder acted(PermissionHolder acted) {
if (acted instanceof User) {
actedName(((User) acted).getName().orElse("null"));
acted(((User) acted).getUuid());
type(Type.USER);
} else if (acted instanceof Group) {
actedName(((Group) acted).getName());
type(Type.GROUP);
}
return this;
}
public ExtendedLogEntryBuilder acted(Track track) {
actedName(track.getName());
type(Type.TRACK);
return this;
}
public ExtendedLogEntryBuilder action(Object... args) {
List<String> parts = new ArrayList<>();
for (Object o : args) {
// special formatting for ContextSets instead of just #toString
if (o instanceof ContextSet) {
ContextSet set = (ContextSet) o;
for (String value : set.getValues("server")) {
parts.add("server=" + value);
}
for (String value : set.getValues("world")) {
parts.add("world=" + value);
}
for (Map.Entry<String, String> context : set.toSet()) {
if (context.getKey().equals("server") || context.getKey().equals("world")) {
continue;
}
parts.add(context.getKey() + "=" + context.getValue());
}
} else {
parts.add(String.valueOf(o));
}
}
action(parts.stream().collect(Collectors.joining(" ")));
return this;
}
@Override
public ExtendedLogEntry build() {
if (timestamp == 0L) {
timestamp(DateUtil.unixSecondsNow());
}
Preconditions.checkNotNull(actor, "actor");
Preconditions.checkNotNull(actorName, "actorName");
Preconditions.checkNotNull(type, "type");
Preconditions.checkNotNull(actedName, "actedName");
Preconditions.checkNotNull(action, "action");
return new ExtendedLogEntry(timestamp, actor, actorName, type, acted, actedName, action);
}
}
public static JsonObject serializeWithId(UUID id, LogEntry entry) {
JsonObject data = new JsonObject();
data.add("id", new JsonPrimitive(id.toString()));
data.add("actor", new JsonPrimitive(entry.getActor().toString()));
data.add("actorName", new JsonPrimitive(entry.getActorName()));
data.add("type", new JsonPrimitive(entry.getType().name()));
if (entry.getActed() != null) {
data.add("acted", new JsonPrimitive(entry.getActed().toString()));
}
data.add("actedName", new JsonPrimitive(entry.getActedName()));
data.add("action", new JsonPrimitive(entry.getAction()));
return data;
}
public static Map.Entry<UUID, ExtendedLogEntry> deserialize(JsonObject object) {
ExtendedLogEntryBuilder builder = build();
UUID id = UUID.fromString(object.get("id").getAsString());
builder.actor(UUID.fromString(object.get("actor").getAsString()));
builder.actorName(object.get("actorName").getAsString());
builder.type(Type.valueOf(object.get("type").getAsString()));
if (object.has("acted")) {
builder.actor(UUID.fromString(object.get("acted").getAsString()));
}
builder.actedName(object.get("actedName").getAsString());
builder.action(object.get("action").getAsString());
return Maps.immutableEntry(id, builder.build());
}
}
@@ -23,7 +23,7 @@
* SOFTWARE.
*/
package me.lucko.luckperms.common.data;
package me.lucko.luckperms.common.actionlog;
import lombok.Getter;
@@ -46,7 +46,7 @@ public class Log {
return new Builder();
}
private static SortedMap<Integer, LogEntry> getPage(Set<LogEntry> set, int pageNo, int entries) {
private static SortedMap<Integer, ExtendedLogEntry> getPage(Set<ExtendedLogEntry> set, int pageNo, int entries) {
if (pageNo < 1) {
throw new IllegalArgumentException("pageNo cannot be less than 1: " + pageNo);
}
@@ -57,11 +57,11 @@ public class Log {
"Requested: " + minimumEntries + ", Log Count: " + set.size());
}
final SortedMap<Integer, LogEntry> out = new TreeMap<>();
final SortedMap<Integer, ExtendedLogEntry> out = new TreeMap<>();
final int max = minimumEntries + entries - 1;
int index = 0;
for (LogEntry e : set) {
for (ExtendedLogEntry e : set) {
index++;
if (index >= minimumEntries) {
out.put(index, e);
@@ -83,17 +83,17 @@ public class Log {
}
@Getter
private final SortedSet<LogEntry> content;
private final SortedSet<ExtendedLogEntry> content;
public Log(SortedSet<LogEntry> content) {
public Log(SortedSet<ExtendedLogEntry> content) {
this.content = ImmutableSortedSet.copyOfSorted(content);
}
public SortedSet<LogEntry> getRecent() {
public SortedSet<ExtendedLogEntry> getRecent() {
return content;
}
public SortedMap<Integer, LogEntry> getRecent(int pageNo) {
public SortedMap<Integer, ExtendedLogEntry> getRecent(int pageNo) {
return getPage(content, pageNo, PAGE_ENTRIES);
}
@@ -101,13 +101,13 @@ public class Log {
return getMaxPages(content.size(), PAGE_ENTRIES);
}
public SortedSet<LogEntry> getRecent(UUID actor) {
public SortedSet<ExtendedLogEntry> getRecent(UUID actor) {
return content.stream()
.filter(e -> e.getActor().equals(actor))
.collect(Collectors.toCollection(TreeSet::new));
}
public SortedMap<Integer, LogEntry> getRecent(int pageNo, UUID actor) {
public SortedMap<Integer, ExtendedLogEntry> getRecent(int pageNo, UUID actor) {
return getPage(getRecent(actor), pageNo, PAGE_ENTRIES);
}
@@ -117,67 +117,69 @@ public class Log {
.count(), PAGE_ENTRIES);
}
public SortedSet<LogEntry> getUserHistory(UUID uuid) {
public SortedSet<ExtendedLogEntry> getUserHistory(UUID uuid) {
return content.stream()
.filter(e -> e.getType() == 'U')
.filter(e -> e.getType() == LogEntry.Type.USER)
.filter(e -> e.getActed() != null)
.filter(e -> e.getActed().equals(uuid))
.collect(Collectors.toCollection(TreeSet::new));
}
public SortedMap<Integer, LogEntry> getUserHistory(int pageNo, UUID uuid) {
public SortedMap<Integer, ExtendedLogEntry> getUserHistory(int pageNo, UUID uuid) {
return getPage(getUserHistory(uuid), pageNo, PAGE_ENTRIES);
}
public int getUserHistoryMaxPages(UUID uuid) {
return getMaxPages(content.stream()
.filter(e -> e.getType() == 'U')
.filter(e -> e.getType() == LogEntry.Type.USER)
.filter(e -> e.getActed() != null)
.filter(e -> e.getActed().equals(uuid))
.count(), PAGE_ENTRIES);
}
public SortedSet<LogEntry> getGroupHistory(String name) {
public SortedSet<ExtendedLogEntry> getGroupHistory(String name) {
return content.stream()
.filter(e -> e.getType() == 'G')
.filter(e -> e.getType() == LogEntry.Type.GROUP)
.filter(e -> e.getActedName().equals(name))
.collect(Collectors.toCollection(TreeSet::new));
}
public SortedMap<Integer, LogEntry> getGroupHistory(int pageNo, String name) {
public SortedMap<Integer, ExtendedLogEntry> getGroupHistory(int pageNo, String name) {
return getPage(getGroupHistory(name), pageNo, PAGE_ENTRIES);
}
public int getGroupHistoryMaxPages(String name) {
return getMaxPages(content.stream()
.filter(e -> e.getType() == 'G')
.filter(e -> e.getType() == LogEntry.Type.GROUP)
.filter(e -> e.getActedName().equals(name))
.count(), PAGE_ENTRIES);
}
public SortedSet<LogEntry> getTrackHistory(String name) {
public SortedSet<ExtendedLogEntry> getTrackHistory(String name) {
return content.stream()
.filter(e -> e.getType() == 'T')
.filter(e -> e.getType() == LogEntry.Type.TRACK)
.filter(e -> e.getActedName().equals(name))
.collect(Collectors.toCollection(TreeSet::new));
}
public SortedMap<Integer, LogEntry> getTrackHistory(int pageNo, String name) {
public SortedMap<Integer, ExtendedLogEntry> getTrackHistory(int pageNo, String name) {
return getPage(getTrackHistory(name), pageNo, PAGE_ENTRIES);
}
public int getTrackHistoryMaxPages(String name) {
return getMaxPages(content.stream()
.filter(e -> e.getType() == 'T')
.filter(e -> e.getType() == LogEntry.Type.TRACK)
.filter(e -> e.getActedName().equals(name))
.count(), PAGE_ENTRIES);
}
public SortedSet<LogEntry> getSearch(String query) {
public SortedSet<ExtendedLogEntry> getSearch(String query) {
return content.stream()
.filter(e -> e.matchesSearch(query))
.collect(Collectors.toCollection(TreeSet::new));
}
public SortedMap<Integer, LogEntry> getSearch(int pageNo, String query) {
public SortedMap<Integer, ExtendedLogEntry> getSearch(int pageNo, String query) {
return getPage(getSearch(query), pageNo, PAGE_ENTRIES);
}
@@ -189,9 +191,9 @@ public class Log {
@SuppressWarnings("WeakerAccess")
public static class Builder {
private final SortedSet<LogEntry> content = new TreeSet<>();
private final SortedSet<ExtendedLogEntry> content = new TreeSet<>();
public Builder add(LogEntry e) {
public Builder add(ExtendedLogEntry e) {
content.add(e);
return this;
}
@@ -0,0 +1,86 @@
/*
* 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.common.actionlog;
import lombok.RequiredArgsConstructor;
import me.lucko.luckperms.api.event.log.LogBroadcastEvent;
import me.lucko.luckperms.common.commands.impl.log.LogNotify;
import me.lucko.luckperms.common.commands.sender.Sender;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.constants.CommandPermission;
import me.lucko.luckperms.common.locale.Message;
import me.lucko.luckperms.common.messaging.ExtendedMessagingService;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import java.util.Optional;
@RequiredArgsConstructor
public class LogDispatcher {
private final LuckPermsPlugin plugin;
public void dispatch(ExtendedLogEntry entry, Sender sender) {
// set the event to cancelled if the sender is import
if (!plugin.getApiProvider().getEventFactory().handleLogPublish(sender.isImport(), entry)) {
plugin.getStorage().logAction(entry);
}
// don't dispatch log entries sent by an import process
if (sender.isImport()) {
return;
}
Optional<ExtendedMessagingService> messagingService = plugin.getMessagingService();
if (!sender.isImport() && messagingService.isPresent()) {
messagingService.get().pushLog(entry);
}
if (!plugin.getApiProvider().getEventFactory().handleLogBroadcast(!plugin.getConfiguration().get(ConfigKeys.LOG_NOTIFY), entry, LogBroadcastEvent.Origin.LOCAL)) {
final String msg = entry.getFormatted();
plugin.getOnlineSenders()
.filter(CommandPermission.LOG_NOTIFY::isAuthorized)
.filter(s -> !LogNotify.isIgnoring(plugin, s.getUuid()))
.filter(s -> !s.getUuid().equals(sender.getUuid()))
.forEach(s -> Message.LOG.send(s, msg));
}
}
public void dispatchFromRemote(ExtendedLogEntry entry) {
if (!plugin.getConfiguration().get(ConfigKeys.BROADCAST_RECEIVED_LOG_ENTRIES)) {
return;
}
if (!plugin.getApiProvider().getEventFactory().handleLogBroadcast(!plugin.getConfiguration().get(ConfigKeys.LOG_NOTIFY), entry, LogBroadcastEvent.Origin.REMOTE)) {
final String msg = entry.getFormatted();
plugin.getOnlineSenders()
.filter(CommandPermission.LOG_NOTIFY::isAuthorized)
.filter(s -> !LogNotify.isIgnoring(plugin, s.getUuid()))
.forEach(s -> Message.LOG.send(s, msg));
}
}
}
@@ -25,80 +25,101 @@
package me.lucko.luckperms.common.api;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NonNull;
import me.lucko.luckperms.api.Contexts;
import me.lucko.luckperms.api.Group;
import me.lucko.luckperms.api.LPConfiguration;
import me.lucko.luckperms.api.Logger;
import me.lucko.luckperms.api.LogEntry;
import me.lucko.luckperms.api.LuckPermsApi;
import me.lucko.luckperms.api.MessagingService;
import me.lucko.luckperms.api.Node;
import me.lucko.luckperms.api.NodeFactory;
import me.lucko.luckperms.api.PlatformType;
import me.lucko.luckperms.api.Storage;
import me.lucko.luckperms.api.Track;
import me.lucko.luckperms.api.User;
import me.lucko.luckperms.api.UuidCache;
import me.lucko.luckperms.api.context.ContextCalculator;
import me.lucko.luckperms.api.context.ContextSet;
import me.lucko.luckperms.common.api.delegates.MetaStackFactoryDelegate;
import me.lucko.luckperms.common.api.delegates.NodeFactoryDelegate;
import me.lucko.luckperms.common.api.delegates.UserDelegate;
import me.lucko.luckperms.api.context.ContextManager;
import me.lucko.luckperms.api.event.EventBus;
import me.lucko.luckperms.api.manager.GroupManager;
import me.lucko.luckperms.api.manager.TrackManager;
import me.lucko.luckperms.api.manager.UserManager;
import me.lucko.luckperms.api.metastacking.MetaStackFactory;
import me.lucko.luckperms.api.platform.PlatformInfo;
import me.lucko.luckperms.common.actionlog.ExtendedLogEntry;
import me.lucko.luckperms.common.api.delegates.manager.ApiContextManager;
import me.lucko.luckperms.common.api.delegates.manager.ApiGroupManager;
import me.lucko.luckperms.common.api.delegates.manager.ApiTrackManager;
import me.lucko.luckperms.common.api.delegates.manager.ApiUserManager;
import me.lucko.luckperms.common.api.delegates.misc.ApiMetaStackFactory;
import me.lucko.luckperms.common.api.delegates.misc.ApiNodeFactory;
import me.lucko.luckperms.common.api.delegates.misc.ApiPlatformInfo;
import me.lucko.luckperms.common.event.EventFactory;
import me.lucko.luckperms.common.event.LuckPermsEventBus;
import me.lucko.luckperms.common.messaging.InternalMessagingService;
import me.lucko.luckperms.common.messaging.NoopMessagingService;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.references.UserIdentifier;
import java.util.Collections;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;
/**
* Implements the LuckPerms API using the plugin instance
*/
public class ApiProvider implements LuckPermsApi {
@Getter(AccessLevel.NONE)
private final LuckPermsPlugin plugin;
@Getter
private final PlatformInfo platformInfo;
private final UserManager userManager;
private final GroupManager groupManager;
private final TrackManager trackManager;
private final LuckPermsEventBus eventBus;
@Getter
private final ContextManager contextManager;
private final MetaStackFactory metaStackFactory;
private final EventFactory eventFactory;
@Getter
private final MetaStackFactoryDelegate metaStackFactory;
public ApiProvider(LuckPermsPlugin plugin) {
this.plugin = plugin;
this.platformInfo = new ApiPlatformInfo(plugin);
this.userManager = new ApiUserManager(plugin, plugin.getUserManager());
this.groupManager = new ApiGroupManager(plugin.getGroupManager());
this.trackManager = new ApiTrackManager(plugin.getTrackManager());
this.eventBus = new LuckPermsEventBus(plugin);
this.contextManager = new ApiContextManager(plugin, plugin.getContextManager());
this.metaStackFactory = new ApiMetaStackFactory(plugin);
this.eventFactory = new EventFactory(eventBus);
this.metaStackFactory = new MetaStackFactoryDelegate(plugin);
}
public EventFactory getEventFactory() {
return eventFactory;
}
@Override
public void runUpdateTask() {
plugin.getUpdateTaskBuffer().request();
public PlatformInfo getPlatformInfo() {
return platformInfo;
}
@Override
public double getApiVersion() {
return 3.3;
public UserManager getUserManager() {
return userManager;
}
@Override
public String getVersion() {
return plugin.getVersion();
public GroupManager getGroupManager() {
return groupManager;
}
@Override
public PlatformType getPlatformType() {
return plugin.getServerType();
public TrackManager getTrackManager() {
return trackManager;
}
@Override
public CompletableFuture<Void> runUpdateTask() {
return plugin.getUpdateTaskBuffer().request();
}
@Override
public EventBus getEventBus() {
return eventBus;
}
@Override
@@ -113,8 +134,7 @@ public class ApiProvider implements LuckPermsApi {
@Override
public Optional<MessagingService> getMessagingService() {
InternalMessagingService service = plugin.getMessagingService();
return service instanceof NoopMessagingService ? Optional.empty() : Optional.of(service);
return plugin.getMessagingService().map(Function.identity());
}
@Override
@@ -123,129 +143,22 @@ public class ApiProvider implements LuckPermsApi {
}
@Override
public Logger getLogger() {
return plugin.getLog();
}
@Override
public User getUser(@NonNull UUID uuid) {
final me.lucko.luckperms.common.model.User user = plugin.getUserManager().getIfLoaded(uuid);
return user == null ? null : user.getDelegate();
}
@Override
public Optional<User> getUserSafe(@NonNull UUID uuid) {
return Optional.ofNullable(getUser(uuid));
}
@Override
public User getUser(@NonNull String name) {
final me.lucko.luckperms.common.model.User user = plugin.getUserManager().getByUsername(name);
return user == null ? null : user.getDelegate();
}
@Override
public Optional<User> getUserSafe(@NonNull String name) {
return Optional.ofNullable(getUser(name));
}
@Override
public Set<User> getUsers() {
return plugin.getUserManager().getAll().values().stream().map(u -> u.getDelegate()).collect(Collectors.toSet());
}
@Override
public boolean isUserLoaded(@NonNull UUID uuid) {
return plugin.getUserManager().isLoaded(UserIdentifier.of(uuid, null));
}
@Override
public void cleanupUser(@NonNull User user) {
plugin.getUserManager().cleanup(UserDelegate.cast(user));
}
@Override
public Group getGroup(@NonNull String name) {
final me.lucko.luckperms.common.model.Group group = plugin.getGroupManager().getIfLoaded(name);
return group == null ? null : group.getDelegate();
}
@Override
public Optional<Group> getGroupSafe(@NonNull String name) {
return Optional.ofNullable(getGroup(name));
}
@Override
public Set<Group> getGroups() {
return plugin.getGroupManager().getAll().values().stream().map(g -> g.getDelegate()).collect(Collectors.toSet());
}
@Override
public boolean isGroupLoaded(@NonNull String name) {
return plugin.getGroupManager().isLoaded(name);
}
@Override
public Track getTrack(@NonNull String name) {
final me.lucko.luckperms.common.model.Track track = plugin.getTrackManager().getIfLoaded(name);
return track == null ? null : track.getDelegate();
}
@Override
public Optional<Track> getTrackSafe(@NonNull String name) {
return Optional.ofNullable(getTrack(name));
}
@Override
public Set<Track> getTracks() {
return plugin.getTrackManager().getAll().values().stream().map(t -> t.getDelegate()).collect(Collectors.toSet());
}
@Override
public boolean isTrackLoaded(@NonNull String name) {
return plugin.getTrackManager().isLoaded(name);
public ContextManager getContextManager() {
return contextManager;
}
@Override
public NodeFactory getNodeFactory() {
return NodeFactoryDelegate.INSTANCE;
return ApiNodeFactory.INSTANCE;
}
@Override
public Node.Builder buildNode(@NonNull String permission) throws IllegalArgumentException {
return me.lucko.luckperms.common.node.NodeFactory.newBuilder(permission);
}
@SuppressWarnings("unchecked")
@Override
public void registerContextCalculator(@NonNull ContextCalculator<?> contextCalculator) {
plugin.getContextManager().registerCalculator(contextCalculator);
public MetaStackFactory getMetaStackFactory() {
return metaStackFactory;
}
@Override
public Optional<Contexts> getContextForUser(@NonNull User user) {
return Optional.ofNullable(plugin.getContextForUser(UserDelegate.cast(user)));
}
@SuppressWarnings("unchecked")
@Override
public ContextSet getContextForPlayer(@NonNull Object player) {
return plugin.getContextManager().getApplicableContext(player);
}
@SuppressWarnings("unchecked")
@Override
public Contexts getContextsForPlayer(@NonNull Object player) {
return plugin.getContextManager().getApplicableContexts(player);
}
@Override
public Set<UUID> getUniqueConnections() {
return Collections.unmodifiableSet(plugin.getUniqueConnections());
}
@Override
public long getStartTime() {
return plugin.getStartTime();
public LogEntry.Builder newLogEntryBuilder() {
return ExtendedLogEntry.build();
}
}
@@ -30,19 +30,24 @@ import me.lucko.luckperms.api.LuckPermsApi;
import java.lang.reflect.Method;
public class ApiHandler {
private static Method REGISTER;
private static Method UNREGISTER;
public class ApiSingletonUtils {
private static final Method REGISTER;
private static final Method UNREGISTER;
static {
Method register = null;
Method unregister = null;
try {
REGISTER = LuckPerms.class.getDeclaredMethod("registerProvider", LuckPermsApi.class);
REGISTER.setAccessible(true);
register = LuckPerms.class.getDeclaredMethod("registerProvider", LuckPermsApi.class);
register.setAccessible(true);
UNREGISTER = LuckPerms.class.getDeclaredMethod("unregisterProvider");
UNREGISTER.setAccessible(true);
unregister = LuckPerms.class.getDeclaredMethod("unregisterProvider");
unregister.setAccessible(true);
} catch (Exception e) {
e.printStackTrace();
}
REGISTER = register;
UNREGISTER = unregister;
}
public static void registerProvider(LuckPermsApi luckPermsApi) {
Loaded 100 of 384 files, more files were not shown because too many files have changed in this diff. Show more