Compare commits

..
147 Commits
Author SHA1 Message Date
Luck abc1c164cf Expose MetaStacks in the API, general cleanup, release 3.2 2017-05-16 21:41:20 +01:00
Luck dc801464ef Refactor meta stacking to be mapped in MetaCache - towards lucko/LuckPermsPlaceholders#1 2017-05-16 19:13:08 +01:00
Luck 99c6fe20c2 Catch exceptions thrown by context calculators 2017-05-16 13:50:28 +01:00
Luck 9d780ae24a Fix misleading Vault debug message 2017-05-15 21:26:43 +01:00
Luck a44dec0402 Fix typo 2017-05-15 12:07:59 +01:00
Luck a081152baa Fix web editor url 2017-05-15 12:05:54 +01:00
Luck 9b50d48f46 fix compile 2017-05-14 22:41:25 +01:00
Luck 5121fc6b1f Cleanup 2017-05-14 22:15:25 +01:00
Luck d4ac261e85 Cleanup config keys, make editor URL configurable 2017-05-14 21:36:02 +01:00
Luck 2e9561371d Implement initial web editor support 2017-05-13 20:36:05 +01:00
Luck 942fd3c4ce Fix vault lookups with static context 2017-05-11 20:04:11 +01:00
Luck 8cf32752e9 Fix meta info only showing values in global context 2017-05-11 19:46:28 +01:00
Luck b240ca9939 Add parent cleartrack command, cleanup Messages enum 2017-05-09 16:26:55 +01:00
Luck e25d99129c Implement parent settrack command - closes #112 2017-05-09 14:06:16 +01:00
Luck 1fee47e087 Implement default contexts - closes #241 2017-05-09 13:36:43 +01:00
Luck e835b31277 Apply world rewrites on-top of the real world name, and apply recursively (#255) 2017-05-09 13:07:39 +01:00
Luck 6817945e12 Bump sponge API version, cleanup 2017-05-09 12:55:45 +01:00
Luck 030fb1262e Fix being able to negate permissions/parents/meta in specific contexts 2017-05-08 22:27:46 +01:00
Luck fae49096f3 Fix retrieving meta keys from Vault which contain delimited characters - closes #270 2017-05-08 21:43:20 +01:00
EuropeJing afd491ecea Update zh-CN locale (#281) 2017-05-08 20:17:51 +01:00
EuropeJing 09bc100ad9 Add zh-CN locale (#279) 2017-05-08 18:11:29 +01:00
Luck 037f18ac56 Fix zPerms parent group migration for users, and fix migration of temporary memberships 2017-05-08 17:45:17 +01:00
Luck a5746dfa21 Support sponge selector resolution - closes #275 2017-05-07 22:52:45 +01:00
Luck cedc5108fc Refactor Patterns to PatternCache 2017-05-07 13:56:42 +01:00
Luck 65af3d0f54 Add data constraints - closes #256 2017-05-07 13:44:29 +01:00
Luck 6c4fa65f91 Fix migrating weight with zPerms 2017-05-06 23:35:58 +01:00
Luck 4731ad8061 Fix migrating false zPerms nodes 2017-05-06 21:06:22 +01:00
Luck 7dc41e15d3 Fix users being cleaned up too early and then logging back into the server - closes #271 2017-05-06 18:24:44 +01:00
Luck 8326539d40 Refactor LP PermissionService to implement a modified API & add proxied classes 2017-05-03 20:14:31 +01:00
Luck 163e6e0f2a Always load user data caches alongside user instances 2017-05-02 16:55:08 +01:00
Luck f56bb251e9 Cleanup some stuff 2017-05-02 16:36:46 +01:00
Luck 152ca99276 Fix issue with bulk removing suffixes 2017-04-25 16:51:21 +01:00
Luck 7c398d2c67 Split up chat messages sent to console containing new lines 2017-04-23 20:51:24 +01:00
Luck a526c942ca Add config options to allow for finer control over how permissions are calculated, and set include-global to true on BungeeCord by default 2017-04-23 20:30:58 +01:00
Luck af8fd15929 improve speed of NodeTools#removeSamePermission 2017-04-23 11:51:56 +01:00
Luck 7bfd5d4bc8 implement atomic group insertion 2017-04-22 22:53:29 +01:00
Luck 4382d5c56d show uuid in log messages if username is null 2017-04-21 21:28:50 +01:00
Luck f43b9c96de cleanup login handling & add CountdownLatch to ensure the plugin has started before logins are handled 2017-04-21 19:10:25 +01:00
Luck c64f72e394 Fix issue with loading usernames on MongoDB - closes #257 2017-04-21 16:58:07 +01:00
Luck 3d54f832c5 cleanup duplicated method 2017-04-20 19:26:47 +01:00
Luck 867ba24c6c support glowstone (maybe) 2017-04-20 19:15:37 +01:00
Luck 9715c5394c Fix NPE caused by AllParentsByWeight primary group selector - closes #252 2017-04-18 17:11:07 +01:00
Luck 67b5c72520 Cleanup handling of unknown/null usernames for users 2017-04-17 19:31:52 +01:00
Articuno 43450f6cc7 Update RU locale (#250) 2017-04-17 10:12:37 +01:00
Luck a3737d4f35 fix typo 2017-04-16 11:08:54 +01:00
Luck 3314e94a9a Add not_on_track meta stack criteria 2017-04-16 11:06:05 +01:00
Luck a27436b086 Fix getting primary groups for offline users 2017-04-15 20:32:03 +01:00
Luck fe5554ffb1 fix build 2017-04-15 19:53:12 +01:00
Luck f305f8d822 Determine dynamic primary groups using current context 2017-04-15 19:08:04 +01:00
Luck 1f54bc41cc Cleanup poms, update copyright header 2017-04-15 19:01:52 +01:00
Luck c82d028429 fix yaml/json schema migration 2017-04-12 21:33:59 +01:00
Luck be2a8b7b07 Fix incorrect chat meta command usage 2017-04-12 17:59:51 +01:00
Luck 261fc82016 Remove getuuid command
why did that ever exist anyways
2017-04-12 17:25:22 +01:00
Luck 37b940f3a5 Convert promote / demote commands to use new context system, update en locale with latest changes 2017-04-12 17:11:01 +01:00
Luck 0504e904a2 Only perform flatfile schema migration if the new dir doesn't exist 2017-04-12 14:28:39 +01:00
Luck 71bfdd9bfd log the file name when exceptions are thrown during i/o 2017-04-12 14:09:51 +01:00
Articuno c7305f6b06 Update ru_RU locale (#238) 2017-04-11 10:47:43 +01:00
Luck 4fb07ff181 API changes for 3.1 2017-04-10 22:26:20 +01:00
Luck 4631dcf857 Ensure expired permissions are removed when users/groups are loaded for commands 2017-04-10 20:57:59 +01:00
Luck f6f9840eb7 Re-implement bulk updates 2017-04-10 19:33:23 +01:00
Luck 4de8165c95 fix issue with /reloading and permission registration 2017-04-10 18:17:09 +01:00
Luck 242672f49d Remove old bulk edit commands 2017-04-10 14:56:46 +01:00
creator3 c32c975a71 Fix accumulation of temporary perms with a world or server context (#235) 2017-04-08 20:49:10 +01:00
Luck 82e759d708 Make sure world names are lowercase'd 2017-04-08 17:00:36 +01:00
Luck 9de44d2605 Don't attempt to migrate empty permissions 2017-04-07 21:03:27 +01:00
Luck 132d0cf578 Fix export command switchprimarygroup output, and log player uuid if their username isn't included 2017-04-07 19:59:53 +01:00
Luck 029dc9f8d9 Make the standard MySQL driver the default again 2017-04-06 11:30:17 +01:00
Luck 5567b1dad8 Use the mariadb driver by default 2017-04-05 21:23:18 +01:00
Luck ea00ec64af Refactor NodeModel into a more useful class, remove duplicate context serialization methods 2017-04-04 19:19:31 +01:00
Luck 7259e6be0a Fixes for the verbose uploader 2017-04-04 17:17:57 +01:00
Luck 2e40557b39 Add '/lp group <group> listmembers' command - closes #203 2017-04-04 16:07:41 +01:00
Luck e68fc7c558 Improve performance of resolve methods in PermissionHolder, other cleanup 2017-04-04 15:22:25 +01:00
Luck 055dfb000d Only allow one server and one world in context sets parsed from command args 2017-04-04 13:47:50 +01:00
Luck b997ab6e3f Properly sort storage files - closes #227 2017-04-03 10:57:29 +01:00
Luck 25f31d0bb8 Cleanup sponge delegate classes 2017-04-03 10:27:31 +01:00
Luck 4cfd8a7e2c Fix broken command parameter & update default locale file 2017-04-03 09:53:26 +01:00
Luck 0ec19a8fee Implement accumulation of static contexts from a file 2017-04-03 01:42:49 +01:00
Luck 2749563f5d Replace guava caches with caffeine 2017-04-03 00:53:04 +01:00
Luck e7e2e3f7e0 Allow context pairs to be specified in commands, fix misuse of exceptions in the permission holder class 2017-04-02 18:44:05 +01:00
Luck 0a33c0aac5 Fix compatibility with older gson versions - closes #222 2017-04-01 07:26:05 +01:00
Articuno 24e09fd2d9 Add ru_RU.yml (#224) 2017-03-30 18:45:50 +01:00
Luck 4d0499a61e Avoid parsing UUID in sponge world calculator 2017-03-30 18:40:36 +01:00
Luck ae82807139 Add "use-vault-server" config option, depreciate vault primary group override feature 2017-03-27 17:35:28 +01:00
Luck 65147e1935 Remove missing permission nodes before adding new ones when saving users/groups with SQL storage 2017-03-27 16:54:59 +01:00
Luck 8e557d122b Fix some concurrency issues with login handling 2017-03-26 18:40:09 +01:00
Luck 486b19aa90 Cleanup some misc stuff 2017-03-26 16:10:49 +01:00
Luck 14cb34ac95 Force some config options to be read as lower case 2017-03-25 23:23:20 +00:00
Luck abfc23b04d Provide tab completion for rewritten arguments & add some more rules 2017-03-25 20:27:33 +00:00
Luck ebb24aefa1 Fix parent set command also adding default 2017-03-25 20:06:30 +00:00
Luck 2573f46f40 Fix removing temporary permissions/parents/meta in global context 2017-03-25 19:58:56 +00:00
Luck 5dcb8cd73a Output group/track lists in alphabetical order 2017-03-25 19:48:17 +00:00
Luck e4bfe09882 Don't bother loading test classes (#217) 2017-03-25 11:38:02 +00:00
Luck eecbfe04db Add es_ES locale 2017-03-25 11:13:45 +00:00
Luck 671e2adc32 Cleanup remaining (un)setPermission methods 2017-03-25 11:04:25 +00:00
Luck 1137e476dd Rewrite inheritance resolution implementation 2017-03-24 22:18:18 +00:00
Luck cb63b321d0 correct zPermissions migration logging tag 2017-03-23 23:18:03 +00:00
Luck c36b0d2975 Automatically push updates to other servers via the messaging service when commands are ran 2017-03-21 21:58:45 +00:00
Luck f8ad562b95 Rename "debug" classes to "verbose" 2017-03-20 17:22:18 +00:00
Luck c35ab38f36 Slight speed improvement for import processes 2017-03-19 20:52:35 +00:00
Luck 073b775566 Implement nicer json format for Sponge local data 2017-03-19 17:54:12 +00:00
Luck 85c7a7db8d Implement new file layout for YAML / JSON storage files (#211) 2017-03-19 15:37:30 +00:00
Luck 1e134df27d Clarify the file names of H2 and SQLite databases 2017-03-18 23:00:12 +00:00
Luck bdff84885a Enforce that ContextSets use the HashMultimap variant - closes #208 2017-03-18 20:18:18 +00:00
Luck f1a9c3722c Fix PM migration command not being registered 2017-03-18 17:26:44 +00:00
Luck 8b402f6423 Fix NoSuchMethodError being thrown for older Bukkit config classes 2017-03-18 15:34:29 +00:00
Luck a0aec52661 Correct the case of group names when retrieved from Node instances 2017-03-18 14:43:34 +00:00
Luck 407ab43dd1 Update PM migration to support latest version 2017-03-17 23:21:57 +00:00
Luck c2165e0196 Fix primary groups being selected in reverse order 2017-03-17 23:00:37 +00:00
Luck 2e75bc3f3b Add some alternate options for primary group calculation 2017-03-17 22:54:18 +00:00
Luck e91c2ca427 Add method to export Message enum and update language file 2017-03-17 21:17:59 +00:00
Luck 965ff04bcc Fix ClassCastException with MongoDB user search 2017-03-17 20:54:40 +00:00
Luck ac4bd418f3 Rename online-mode config option to use-server-uuids to better reflect/clarify its purpose 2017-03-17 20:51:06 +00:00
Luck e9e844c1f7 Include internal weight value in meta output if not already present 2017-03-13 20:17:54 +00:00
Luck aa2941fcdb Refactor and cleanup commands 2017-03-13 20:03:03 +00:00
Luck dbc909a317 Cleanup migration commands 2017-03-13 19:10:36 +00:00
Luck 11465a4ce9 Fix BungeePerms migration to account for group weights and negated permissions 2017-03-12 20:00:02 +00:00
Luck f5007f94dc Add server version info to the startup banner 2017-03-12 14:59:27 +00:00
Luck d18ac2e6eb Cleanup pom files, maybe improve build speed a bit 2017-03-12 14:28:35 +00:00
Luck b9fc5c39ae Listen for changes in data files and automatically update 2017-03-11 23:05:03 +00:00
Luck 9dc2278083 Cleanup a bit 2017-03-11 21:10:27 +00:00
Luck 281060a972 Make the clone commands work for existing tracks/groups too 2017-03-11 18:30:27 +00:00
Luck 91626f079d Fix mistake in removeprefix/removesuffix command descriptions 2017-03-11 18:26:29 +00:00
Luck 7305c6c54b Migrate group weightings onto the group itself as well as to the chat meta held by it 2017-03-11 18:24:25 +00:00
Luck bce7fa871d Fix getAllNodes cache not working properly, remove pre-process contexts on Bungee & cleanup some unused methods 2017-03-11 17:55:50 +00:00
Luck a35fd80763 remove placeholder expansion from main project 2017-03-11 14:02:17 +00:00
Luck b383f9dfbc Add some more placeholders to the expansion 2017-03-11 13:30:08 +00:00
Luck 680ad3c145 Update hikari version 2017-03-11 10:32:10 +00:00
Luck 7b712e7f90 Update readme 2017-03-10 23:30:08 +00:00
Luck 2462eac497 Run exporter in own thread and process user exports concurrently 2017-03-10 20:44:19 +00:00
Luck bb818c8942 Cleanup powerfulperms migration & remove support for older PP versions 2017-03-09 19:10:48 +00:00
Luck ddc881af11 Add new argument util method for reading context pairs 2017-03-09 17:56:15 +00:00
Luck 838fba9173 Switch all usages of file reader/writers to use java nio methods - closes #204 2017-03-09 17:55:33 +00:00
Luck 0b6f326c18 Fallback to uuid in the search command if the username is unknown 2017-03-07 22:17:49 +00:00
Luck 00895fc7ab Don't send json text on MC 1.7 2017-03-06 18:22:11 +00:00
Luck 07f38aa283 Fix possible issue with context conversion 2017-03-05 22:56:00 +00:00
Luck ecf1d3790c Fix issue where group nodes could be unset using the permission commands, and where users with per-server groups wouldn't be assigned to the default group globally 2017-03-05 19:05:05 +00:00
Luck 53c9e5bb4a Fix race condition with Bukkit join handling where players are denied entry but subsequently login slightly later 2017-03-05 00:59:44 +00:00
Luck b20b03113a Fix groupmanager global group migration on windows systems 2017-03-01 21:26:20 +00:00
Luck ec8273aa9f Change bungeecord leave event priority to wait longer before unloading data 2017-02-26 21:53:03 +00:00
Luck 2862f0dfae Fix creating delimiter patterns 2017-02-26 21:28:40 +00:00
Luck fd4963c74b Fix getUuid and getName API methods returning null for User, Group and Track instances 2017-02-26 20:46:37 +00:00
Luck df99ef4481 misc cleanup 2017-02-26 12:43:35 +00:00
Luck 35d1057673 Refactor sponge commands to use the proxied interfaces (closes #197) 2017-02-26 11:55:59 +00:00
Luck e6a90a18c4 Sort subjects properly 2017-02-26 01:16:54 +00:00
Luck 26afb69a14 Cleanup LuckPermsSubjectData (fixes #194) 2017-02-26 01:00:02 +00:00
Luck e8c0c43e9d misc cleanup 2017-02-24 19:31:45 +00:00
Luck 1707f333bf update javadocs link in readme 2017-02-23 21:59:43 +00:00
483 changed files with 18669 additions and 11743 deletions

No files matched your search

+76 -157
View File
@@ -1,5 +1,5 @@
# LuckPerms Language File
# Locale: en_US
# Locale: en_US (English)
# Author: Luck
prefix: "&7&l[&b&lL&3&lP&7&l] &c"
@@ -9,84 +9,65 @@ 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 contact an administrator."
loading-error: "Permissions 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."
log: "&3LOG &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-no-data: "&bUser &a{0}&b does not have any data loaded."
user-save-success: "&7(User data was saved to storage)"
user-save-error: "There was an error whilst saving the user."
user-create-fail: "There was an error whilst creating a new 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-invalid-entry: "Server names can only contain alphanumeric characters."
use-inherit-command: "Use the 'parent add' command instead of specifying the node."
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 the following filters: &f{0}"
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 the following filters: &f{0}"
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:"
tree-upload-start: "&bGenerating permission tree..."
tree-empty: "&aUnable 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."
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}"
check-result: "&aPermission check result on user &b{0}&a for permission &b{1}&a: &f{2}"
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 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."
@@ -95,9 +76,7 @@ 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."
@@ -124,7 +103,7 @@ info: >
{PREFIX}&f- &3Context Calculators: &a{14}\n
{PREFIX}&f- &3Unique permissions: &a{15}\n
{PREFIX}&f- &bConfiguration:\n
{PREFIX}&f- &3Online Mode: {16}\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
@@ -137,57 +116,49 @@ 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."
groups-list: "&aGroups: {0}"
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:\n{1}"
listparents: "&b{0}'s Parent Groups:\n{1}"
listparents-temp: "&b{0}'s Temporary Parent Groups:\n{1}"
listgroups: "&b{0}'s Groups:\n{1}"
listgroups-temp: "&b{0}'s Temporary Groups:\n{1}"
list-tracks: "&b{0}'s Tracks:\n{1}"
listnodes-temp: >
&b{0}'s Temporary Nodes:\n
{1}
listparents: >
&b{0}'s Parent Groups:\n
{1}
listparents-temp: >
&b{0}'s Temporary Parent Groups:\n
{1}
list-tracks: >
&b{0}'s Tracks:\n
{1}
list-tracks-empty: "{0} is not on any tracks."
setpermission-success: "&aSet &b{0}&a to &b{1}&a for &b{2}&a."
setpermission-server-success: "&aSet &b{0}&a to &b{1}&a for &b{2}&a on server &b{3}&a."
setpermission-server-world-success: "&aSet &b{0}&a to &b{1}&a for &b{2}&a on server &b{3}&a, world &b{4}&a."
setpermission-temp-success: "&aSet &b{0}&a to &b{1}&a for &b{2}&a for a duration of &b{3}&a."
setpermission-temp-server-success: "&aSet &b{0}&a to &b{1}&a for &b{2}&a on server &b{3}&a for a duration of &b{4}&a."
setpermission-temp-server-world-success: "&aSet &b{0}&a to &b{1}&a for &b{2}&a on server &b{3}&a, world &b{4}&a, for a duration of &b{5}&a."
unsetpermission-success: "&aUnset &b{0}&a for &b{1}&a."
unsetpermission-server-success: "&aUnset &b{0}&a for &b{1}&a on server &b{2}&a."
unsetpermission-server-world-success: "&aUnset &b{0}&a for &b{1}&a on server &b{2}&a, world &b{3}&a."
unset-temp-permission-success: "&aUnset temporary permission &b{0}&a for &b{1}&a."
unset-temp-permission-server-success: "&aUnset temporary permission &b{0}&a for &b{1}&a on server &b{2}&a."
unset-temp-permission-server-world-success: "&aUnset temporary permission &b{0}&a for &b{1}&a on server &b{2}&a, world &b{3}&a."
set-inherit-success: "&b{0}&a now inherits permissions from &b{1}&a."
set-inherit-server-success: "&b{0}&a now inherits permissions from &b{2}&a on server &b{3}&a."
set-inherit-server-world-success: "&b{0}&a now inherits permissions from &b{1}&a on server &b{2}&a, world &b{3}&a."
set-parent-success: "&b{0}&a had their existing parent groups cleared, and now only inherits &b{1}&a."
set-parent-server-success: "&b{0}&a had their existing parent groups cleared, and now only inherits &b{1}&a on server &b{2}&a."
set-parent-server-world-success: "&b{0}&a had their existing parent groups cleared, and now only inherits &b{1}&a on server &b{2}&a, world &b{3}&a."
set-temp-inherit-success: "&b{0}&a now inherits permissions from &b{1}&a for a duration of &b{2}&a."
set-temp-inherit-server-success: "&b{0}&a now inherits permissions from &b{1}&a on server &b{2}&a for a duration of &b{3}&a."
set-temp-inherit-server-world-success: "&b{0}&a now inherits permissions from &b{1}&a on server &b{2}&a, world &b{3}&a, for a duration of &b{4}&a."
unset-inherit-success: "&b{0}&a no longer inherits permissions from &b{1}&a."
unset-inherit-server-success: "&b{0}&a no longer inherits permissions from &b{1}&a on server &b{2}&a."
unset-inherit-server-world-success: "&b{0}&a no longer inherits permissions from &b{1}&a on server &b{2}&a, world &b{3}&a."
unset-temp-inherit-success: "&b{0}&a no longer temporarily inherits permissions from &b{1}&a."
unset-temp-inherit-server-success: "&b{0}&a no longer temporarily inherits permissions from &b{1}&a on server &b{2}&a."
unset-temp-inherit-server-world-success: "&b{0}&a no longer temporarily inherits permissions from &b{1}&a on server &b{2}&a, world &b{3}&a."
clear-success: "&b{0}&a's permissions were cleared. (&b{1}&a nodes were removed.)"
clear-success-singular: "&b{0}&a's permissions were cleared. (&b{1}&a node was removed.)"
parent-clear-success: "&b{0}&a's parents were cleared. (&b{1}&a nodes were removed.)"
parent-clear-success-singular: "&b{0}&a's parents were cleared. (&b{1}&a node was removed.)"
meta-clear-success: "&b{0}&a's meta was cleared. (&b{1}&a nodes were removed.)"
meta-clear-success-singular: "&b{0}&a's meta was cleared. (&b{1}&a node was removed.)"
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."
setpermission-success: "&aSet &b{0}&a to &b{1}&a for &b{2}&a in context {3}&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."
unsetpermission-success: "&aUnset &b{0}&a for &b{1}&a in context {2}&a."
unset-temp-permission-success: "&aUnset temporary permission &b{0}&a for &b{1}&a 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."
set-track-parent-success: "&b{0}&a had their existing parent groups on track &b{1}&a cleared, and now only inherits &b{2}&a in context {3}&a."
unset-inherit-success: "&b{0}&a no longer inherits permissions from &b{1}&a in context {2}&a."
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!"
chat-meta-prefix-header: "&b{0}'s Prefixes"
chat-meta-suffix-header: "&b{0}'s Suffixes"
meta-header: "&b{0}'s Meta"
@@ -198,59 +169,29 @@ meta-entry-with-context: "&b-> &a{0} &f= &f\"{1}&f\" &8(&7inherited from &a{2}&8
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-prefix: "{0} already has that prefix set."
already-has-suffix: "{0} already has that suffix set."
does-not-have-prefix: "{0} doesn't have that prefix set."
does-not-have-suffix: "{0} doesn't have that suffix set."
addprefix-success: "&b{0}&a had prefix &f\"{1}&f\"&a set at a priority of &b{2}&a."
addprefix-server-success: "&b{0}&a had prefix &f\"{1}&f\"&a set at a priority of &b{2}&a on server &b{3}&a."
addprefix-server-world-success: "&b{0}&a had prefix &f\"{1}&f\"&a set at a priority of &b{2}&a on server &b{3}&a, world &b{4}&a."
removeprefix-success: "&b{0}&a had prefix &f\"{1}&f\"&a at priority &b{2}&a removed."
removeprefix-server-success: "&b{0}&a had prefix &f\"{1}&f\"&a at priority &b{2}&a removed on server &b{3}&a."
removeprefix-server-world-success: "&b{0}&a had prefix &f\"{1}&f\"&a at priority &b{2}&a removed on server &b{3}&a, world &b{4}&a."
addsuffix-success: "&b{0}&a had suffix &f\"{1}&f\"&a set at a priority of &b{2}&a."
addsuffix-server-success: "&b{0}&a had suffix &f\"{1}&f\"&a set at a priority of &b{2}&a on server &b{3}&a."
addsuffix-server-world-success: "&b{0}&a had suffix &f\"{1}&f\"&a set at a priority of &b{2}&a on server &b{3}&a, world &b{4}&a."
removesuffix-success: "&b{0}&a had suffix &f\"{1}&f\"&a at priority &b{2}&a removed."
removesuffix-server-success: "&b{0}&a had suffix &f\"{1}&f\"&a at priority &b{2}&a removed on server &b{3}&a."
removesuffix-server-world-success: "&b{0}&a had suffix &f\"{1}&f\"&a at priority &b{2}&a removed on server &b{3}&a, world &b{4}&a."
add-temp-prefix-success: "&b{0}&a had prefix &f\"{1}&f\"&a set at a priority of &b{2}&a for a duration of &b{3}&a."
add-temp-prefix-server-success: "&b{0}&a had prefix &f\"{1}&f\"&a set at a priority of &b{2}&a on server &b{3}&a, for a duration of &b{4}&a."
add-temp-prefix-server-world-success: "&b{0}&a had prefix &f\"{1}&f\"&a set at a priority of &b{2}&a on server &b{3}&a, world &b{4}&a, for a duration of &b{5}&a."
remove-temp-prefix-success: "&b{0}&a had temporary prefix &f\"{1}&f\"&a at priority &b{2}&a removed."
remove-temp-prefix-server-success: "&b{0}&a had temporary prefix &f\"{1}&f\"&a at priority &b{2}&a removed on server &b{3}&a."
remove-temp-prefix-server-world-success: "&b{0}&a had temporary prefix &f\"{1}&f\"&a at priority &b{2}&a removed on server &b{3}&a, world &b{4}&a."
add-temp-suffix-success: "&b{0}&a had suffix &f\"{1}&f\"&a set at a priority of &b{2}&a for a duration of &b{3}&a."
add-temp-suffix-server-success: "&b{0}&a had suffix &f\"{1}&f\"&a set at a priority of &b{2}&a on server &b{3}&a, for a duration of &b{4}&a."
add-temp-suffix-server-world-success: "&b{0}&a had suffix &f\"{1}&f\"&a set at a priority of &b{2}&a on server &b{3}&a, world &b{4}&a, for a duration of &b{5}&a."
remove-temp-suffix-success: "&b{0}&a had temporary suffix &f\"{1}&f\"&a at priority &b{1}&a removed."
remove-temp-suffix-server-success: "&b{0}&a had temporary suffix &f\"{1}&f\"&a at priority &b{2}&a removed on server &b{3}&a."
remove-temp-suffix-server-world-success: "&b{0}&a had temporary suffix &f\"{1}&f\"&a at priority &b{2}&a removed on server &b{3}&a, world &b{4}&a."
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."
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."
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."
set-meta-server-success: "&aSet meta value for key &f\"{0}&f\"&a to &f\"{1}&f\"&a for &b{2}&a on server &b{3}&a."
set-meta-server-world-success: "&aSet meta value for key &f\"{0}&f\"&a to &f\"{1}&f\"&a for &b{2}&a on server &b{3}&a, world &b{4}&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."
set-meta-temp-server-success: "&aSet meta value for key &f\"{0}&f\"&a to &f\"{1}&f\"&a for &b{2}&a on server &b{3}&a for a duration of &b{4}&a."
set-meta-temp-server-world-success: "&aSet meta value for key &f\"{0}&f\"&a to &f\"{1}&f\"&a for &b{2}&a on server &b{3}&a, world &b{4}&a, for a duration of &b{5}&a."
unset-meta-success: "&aUnset meta value with key &f\"{0}&f\"&a for &b{1}&a."
unset-meta-server-success: "&aUnset meta value with key &f\"{0}&f\"&a for &b{1}&a on server &b{2}&a."
unset-meta-server-world-success: "&aUnset meta value with key &f\"{0}&f\"&a for &b{1}&a on server &b{2}&a, world &b{3}&a."
unset-meta-temp-success: "&aUnset temporary meta value with key &f\"{0}&f\"&a for &b{1}&a."
unset-meta-temp-server-success: "&aUnset temporary meta value with key &f\"{0}&f\"&a for &b{1}&a on server &b{2}&a."
unset-meta-temp-server-world-success: "&aUnset temporary meta value with key &f\"{0}&f\"&a for &b{1}&a on server &b{2}&a, world &b{3}&a."
bulk-change-type-error: "Invalid type. Was expecting 'server' or 'world'."
bulk-change-success: "&aApplied bulk change successfully. {0} records were changed."
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== != ~~ ~!"
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."
bulk-update-starting: "&aRunning bulk update."
bulk-update-success: "&bBulk update completed successfully."
bulk-update-failure: "&cBulk update failed. Check the console for errors."
user-info-general: >
{PREFIX}&b&l> &bUser Info: &f{0}\n
{PREFIX}&f- &3UUID: &f{1}\n
@@ -264,38 +205,29 @@ user-info-general: >
{PREFIX}&f- &3Meta: &a{8}
user-info-data: >
{PREFIX}&f- &aCached Data:\n
{PREFIX}&f- &3Is Loaded: {0}\n
{PREFIX}&f- &3Has contextual data: {0}\n
{PREFIX}&f- &3Current Contexts: {1}\n
{PREFIX}&f- &3Current Prefix: {2}\n
{PREFIX}&f- &3Current Suffix: {3}
info-parent-header: "&f- &aParent Groups:"
info-temp-parent-header: "&f- &aTemporary Parent Groups:"
user-getuuid: "&bThe UUID of &b{0}&b is &b{1}&b."
user-removegroup-error-primary: "You 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-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-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."
user-track-added-to-first-server: "&b{0}&a isn't in any groups on this track, so they were added to the first group, &b{1}&a, on server &b{2}&a."
user-track-added-to-first-server-world: "&b{0}&a isn't in any groups on this track, so they were added to the first group, &b{1}&a, on server &b{2}&a, world &b{3}&a."
user-promote-success: "&aPromoting user along track &b{0}&a from &b{1}&a to &b{2}&a."
user-promote-success-server: "&aPromoting user along track &b{0}&a from &b{1}&a to &b{2}&a on server &b{3}&a."
user-promote-success-server-world: "&aPromoting user along track &b{0}&a from &b{1}&a to &b{2}&a on server &b{3}&a, world &b{4}&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-error-malformed: >
{PREFIX}The next group on the track, {0}, no longer exists. Unable to promote user.
{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."
user-demote-success-server: "&aDemoting user along track &b{0}&a from &b{1}&a to &b{2}&a on server &b{3}&a."
user-demote-success-server-world: "&aDemoting user along track &b{0}&a from &b{1}&a to &b{2}&a on server &b{3}&a, world &b{4}&a."
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."
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.
user-demote-error-not-contain-group: "Demotions are done based on primary groups. The users primary group is not on the track specified."
user-showpos: "&aShowing &b{0}&a's position on track &b{1}&a.{2}"
group-info-general: >
{PREFIX}&b&l> &bGroup Info: &f{0}\n
{PREFIX}&f- &3Display Name: &f{1}\n
@@ -307,7 +239,6 @@ group-info-general: >
{PREFIX}&f- &3Suffixes: &a{6}\n
{PREFIX}&f- &3Meta: &a{7}
group-set-weight: "&aSet weight to &b{0}&a for group &b{1}&a."
track-info: >
{PREFIX}&b&l> &bShowing Track: &f{0}\n
{PREFIX}&f- &7Path: &f{1}
@@ -317,55 +248,43 @@ track-insert-success: "&aGroup &b{0}&a was successfully inserted into track &b{1
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}."
log-no-entries: "&bNo log entries to show."
log-entry: "&b#{0} -> &8(&7{1} ago&8) {2}"
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-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-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-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."
import-end-complete-err-sin: "&b(Import) &a&lCOMPLETED &7- took &b{0} &7seconds - &c{1} error."
import-end-error-header: >
{PREFIX}&b(Import) &7------------> &fShowing Error #&b{0} &7<------------\n
{PREFIX}&b(Import) &fWhilst executing: &3Command #{1}\n
{PREFIX}&b(Import) &fCommand: &7{2}\n
{PREFIX}&b(Import) &fType: &3{3}\n
{PREFIX}&b(Import) &fOutput:
import-end-error-content: "&b(Import) &b-> &c{0}"
import-end-error-footer: "&b(Import) &7<------------------------------------------>"
migration-not-console: "Migration must be performed from the Console."
+338
View File
@@ -0,0 +1,338 @@
# LuckPerms Language File
# Locale: es_ES (Spanish)
# Author: Jose Chavez (Twitter: @JoCHav3z)
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[ADVERTENCIA] {0}"
log-error: "&7&l[&bLuck&3Perms&7&l] &4[ERROR] {0}"
empty: "{0}"
player-online: "&aConectado"
player-offline: "&cDesconectado"
loading-error: "La Informacion de los permisos no pudo ser cargada. Por favor contacte al administrador."
op-disabled: "&bEl sistema OP de Vanilla esta deshabilitado en este servidor."
op-disabled-sponge: "&2El estado de operador del servidor no tiene efecto cuando un plugin de permisos esta instalado. Por favor edite la informacion de usuario directamente."
log: "&3LOG &3&l> {0}"
export-log: "&3EXPORTAR &3&l> &f{0}"
export-log-progress: "&3EXPORTADO &3&l> &7{0}"
migration-log: "&3MIGRACION &7[&3{0}&7] &3&l> &f{1}"
migration-log-progress: "&3MIGRADO &7[&3{0}&7] &3&l> &7{1}"
command-not-recognised: "Comando no reconocido."
command-no-permission: "¡No tienes permiso de usar este comando!"
already-haspermission: "{0} ¡Ya tienes este permiso!"
does-not-havepermission: "{0} No tienes este permiso establecido."
already-has-temp-permission: "{0} ¡Ya tienes este permiso establecido temporalmente!"
does-not-have-temp-permission: "{0} No tienes este permiso establecido temporalmente."
user-not-found: "&bNo se pudo encontrar al usuario."
user-not-online: "&bUsuario &a{0}&b no esta conectado."
user-no-data: "&bUsuario &a{0}&b no tiene ningún dato cargado."
user-save-success: "&7(Los datos del usuario se guardaron en el almacenamiento)"
user-save-error: "Se ha producido un error al guardar el usuario."
user-create-fail: "Se produjo un error al crear un nuevo usuario."
group-not-found: "&bEl Grupo no se pudo encontrar."
group-save-success: "&7(Los datos del grupo se guardaron en el almacenamiento)"
group-save-error: "Se ha producido un error al guardar el grupo."
track-not-found: "&bNo se pudo encontrar la pista."
track-save-success: "&7(Los datos de la pista se guardaron en el almacenamiento)"
track-save-error: "Se ha producido un error al guardar la pista."
user-invalid-entry: "&d{0}&c El username/uuid no es valido."
group-invalid-entry: "Los nombres de grupo sólo pueden contener caracteres alfanuméricos."
track-invalid-entry: "Los nombres de pista sólo pueden contener caracteres alfanuméricos."
server-invalid-entry: "Los nombres de servidor sólo pueden contener caracteres alfanuméricos."
use-inherit-command: "Usa el comando 'parent add' y 'parent remove' en vez de especificar el nodo."
verbose-invalid-filter: "&cFiltro Verbose invalido: &f{0}"
verbose-on: "&bEl chequeo Verbose de salida se ha establecido en &aTRUE &bpara todos los permisos."
verbose-on-query: "&bEl chequeo Verbose de salida que se establecio en &aTRUE &bpara los permisos se ha pareado con los siguientes filtros: &f{0}"
verbose-off: "&bEl chequeo Verbose de salida que se establecio en &cFALSE&b."
verbose-recording-on: "&bGrabacion de Verbose establecida en &aTRUE &bpara todos los permisos."
verbose-recording-on-query: "&bGrabacion de Verbose establecida en &aTRUE &bpara los permisos se ha pareado con los siguientes filtros: &f{0}"
verbose-recording-upload-start: "&bGrabacion de Verbose se ha deshabilitado. Cargando resultados..."
verbose-recording-url: "&aURL de resultados de Verbose:"
tree-upload-start: "&bGenerando arbol de permisos..."
tree-empty: "&aNo se puede generar el árbol. No se encontraron resultados."
tree-url: "&aURL de arbol de permisos:"
search-searching: "&aBúsqueda de usuarios y grupos con &b{0}&a..."
search-result: "&aEncontradas &b{0}&a entradas por parte de &b{1}&a usuarios y &b{2}&a grupos."
search-showing-users: "&bMostrando entradas del usuario:"
search-showing-groups: "&bMostrando entradas del grupo:"
search-showing-users-with-page: "&bMostrando entradas del usuario: {0}"
search-showing-groups-with-page: "&bMostrando entradas del grupo: {0}"
check-result: "&aResultado de la comprobación del permiso en el usuario &b{0}&a para permiso &b{1}&a: &f{2}"
create-success: "&b{0}&a se creó correctamente."
delete-success: "&b{0}&a fue eliminado correctamente."
rename-success: "&b{0}&a fue exitosamente renombrado a &b{1}&a."
clone-success: "&b{0}&a fue clonado con éxito en &b{1}&a."
already-inherits: "{0} ya hereda '{1}'."
does-not-inherit: "{0} no hereda '{1}'."
already-temp-inherits: "{0} ya lo heredo temporalmente a '{1}'."
does-not-temp-inherit: "{0} no lo heredo temporalmente a '{1}'."
track-already-contains: "La pista {0} ya contiene el grupo '{1}'."
track-does-not-contain: "La pista {0} no contiene el grupo '{1}'."
track-ambiguous-call: "El usuario especificado es miembro de varios grupos en esta pista. No se puede determinar su ubicación."
group-already-exists: "¡Ese grupo ya existe!"
group-does-not-exist: "¡Ese grupo no existe!"
group-load-error: "Ocurrió un error inesperado. No se pudo cargar el grupo."
groups-load-error: "Ocurrió un error inesperado. No se pueden cargar todos los grupos."
track-already-exists: "¡La pista ya existe!"
track-does-not-exist: "¡La pista no existe!"
track-load-error: "Ocurrió un error inesperado. No se pudo cargar la pista."
tracks-load-error: "Ocurrió un error inesperado. No se pueden cargar todas las pistas."
track-empty: "La pista no se puede utilizar porque está vacía o contiene sólo un grupo."
update-task-request: "&bActualizar tarea programada."
update-task-complete: "&aLa actualizacion de la tarea a finalizado."
update-task-complete-network: "&aLa actualizacion de la tarea a finalizado. Ahora intentando enviar a otros servidores."
update-task-push-success: "&aOtros servidores fueron notificados vía &b{0} Mensajería &aexitoxamente."
update-task-push-failure: "&cError al empujar cambios a otros servidores."
update-task-push-failure-not-setup: "&cError al presionar los cambios a otros servidores. No se ha configurado un servicio de mensajería."
reload-config-success: "&aSe ha vuelto a cargar el archivo de configuración. &7(Algunas opciones sólo se aplicarán después de que el servidor se haya reiniciado.)"
info: >
{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: "Se produjo un error al crear el grupo."
delete-group-error: "Se produjo un error al eliminar el grupo."
delete-group-error-default: "No puede eliminar el grupo predeterminado."
groups-list: "&aGrupos: {0}"
create-track-error: "Se produjo un error al crear la pista."
delete-track-error: "Se produjo un error al eliminar la pista."
tracks-list: "&aPistas: {0}"
listnodes: "&b{0}'s Nodos:"
listnodes-with-page: "&b{0}'s Nodos: {1}"
listnodes-temp: >
&b{0}'s Temporary Nodes:\n
{1}
listparents: >
&b{0}'s Parent Groups:\n
{1}
listparents-temp: >
&b{0}'s Temporary Parent Groups:\n
{1}
listgroups: >
&b{0}'s Groups:\n
{1}
listgroups-temp: >
&b{0}'s Temporary Groups:\n
{1}
list-tracks: >
&b{0}'s Tracks:\n
{1}
list-tracks-empty: "{0} no está en ninguna pista."
setpermission-success: "&aEstablecer &b{0}&a a &b{1}&a para &b{2}&a."
setpermission-server-success: "&aEstablecer &b{0}&a a &b{1}&a para &b{2}&a en el servidor &b{3}&a."
setpermission-server-world-success: "&aEstablecer &b{0}&a a &b{1}&a para &b{2}&a en el servidor &b{3}&a, mundo &b{4}&a."
setpermission-temp-success: "&aEstablecer &b{0}&a a &b{1}&a para &b{2}&a por una duracion de &b{3}&a."
setpermission-temp-server-success: "&aEstablecer &b{0}&a a &b{1}&a para &b{2}&a en el servidor &b{3}&a por una duracion de &b{4}&a."
setpermission-temp-server-world-success: "&aEstablecer &b{0}&a a &b{1}&a para &b{2}&a en el servidor &b{3}&a, mundo &b{4}&a, por una duracion de &b{5}&a."
unsetpermission-success: "&aDesestablecer &b{0}&a para &b{1}&a."
unsetpermission-server-success: "&aDesestablecer &b{0}&a para &b{1}&a en el servidor &b{2}&a."
unsetpermission-server-world-success: "&aDesestablecer &b{0}&a para &b{1}&a en el servidor &b{2}&a, mundo &b{3}&a."
unset-temp-permission-success: "&aDesestablecer temporalmente el permiso &b{0}&a para &b{1}&a."
unset-temp-permission-server-success: "&aDesestablecer temporalmente el permiso &b{0}&a para &b{1}&a en el servidor &b{2}&a."
unset-temp-permission-server-world-success: "&aDesestablecer temporalmente el permiso &b{0}&a para &b{1}&a en el servidor &b{2}&a, mundo &b{3}&a."
set-inherit-success: "&b{0}&a ahora hereda los permisos de &b{1}&a."
set-inherit-server-success: "&b{0}&a ahora hereda los permisos de &b{1}&a en el servidor &b{2}&a."
set-inherit-server-world-success: "&b{0}&a ahora hereda los permisos de &b{1}&a en el servidor &b{2}&a, mundo &b{3}&a."
set-parent-success: "&b{0}&a sus grupos parentales existentes han sido borrados, y ahora solo heredan &b{1}&a."
set-parent-server-success: "&b{0}&a sus grupos parentales existentes han sido borrados, y ahora solo heredan &b{1}&a en el servidor &b{2}&a."
set-parent-server-world-success: "&b{0}&a sus grupos parentales existentes han sido borrados, y ahora solo heredan &b{1}&a en el servidor &b{2}&a, mundo &b{3}&a."
set-temp-inherit-success: "&b{0}&a ahora heredan permisos de &b{1}&a por una duracion de &b{2}&a."
set-temp-inherit-server-success: "&b{0}&a ahora heredan permisos de &b{1}&a en el servidor &b{2}&a por una duracion de &b{3}&a."
set-temp-inherit-server-world-success: "&b{0}&a ahora heredan permisos de &b{1}&a en el servidor &b{2}&a, mundo &b{3}&a, por una duracion de &b{4}&a."
unset-inherit-success: "&b{0}&a ya no hereda los permisos de &b{1}&a."
unset-inherit-server-success: "&b{0}&a ya no hereda los permisos de &b{1}&a en el servidor &b{2}&a."
unset-inherit-server-world-success: "&b{0}&a ya no hereda los permisos de &b{1}&a en el servidor &b{2}&a, mundo &b{3}&a."
unset-temp-inherit-success: "&b{0}&a ya no hereda temporalmente los permisos de &b{1}&a."
unset-temp-inherit-server-success: "&b{0}&a ya no hereda temporalmente los permisos &b{1}&a en el servidor &b{2}&a."
unset-temp-inherit-server-world-success: "&b{0}&a ya no hereda temporalmente los permisos &b{1}&a en el servidor &b{2}&a, mundo &b{3}&a."
clear-success: "&b{0}&a's permisos fueron borrados. (&b{1}&a nodos fueron removidos.)"
clear-success-singular: "&b{0}&a's permisos fueron borrados. (&b{1}&a nodo fue removido.)"
parent-clear-success: "&b{0}&a's los padres fueron eliminados. (&b{1}&a nodos fueron removidos.)"
parent-clear-success-singular: "&b{0}&a's los padres fueron eliminados. (&b{1}&a nodo fue removido.)"
meta-clear-success: "&b{0}&a's meta fue eliminada. (&b{1}&a nodos fueron removidos.)"
meta-clear-success-singular: "&b{0}&a's meta fue eliminada. (&b{1}&a nodo fue removido.)"
illegal-date-error: "No se pudo analizar la fecha '{0}'."
past-date-error: "¡No puedes fijar una fecha en el pasado!"
chat-meta-prefix-header: "&b{0}'s Prefijos"
chat-meta-suffix-header: "&b{0}'s Sufijos"
meta-header: "&b{0}'s Meta"
chat-meta-entry: "&b-> {0} &f- &f\"{1}&f\" &8(&7heredado de &a{2}&8)"
chat-meta-entry-with-context: "&b-> {0} &f- &f\"{1}&f\" &8(&7heredado de &a{2}&8){3}"
meta-entry: "&b-> &a{0} &f= &f\"{1}&f\" &8(&7heredado de &a{2}&8)"
meta-entry-with-context: "&b-> &a{0} &f= &f\"{1}&f\" &8(&7heredado de &a{2}&8){3}"
chat-meta-prefix-none: "&b{0} no tiene prefijos."
chat-meta-suffix-none: "&b{0} no tiene sufijos."
meta-none: "&b{0} no tiene meta."
meta-invalid-priority: "Prioridad no válida '{0}'. Se esperaba un número."
already-has-prefix: "{0} ya tiene ese prefijo establecido."
already-has-suffix: "{0} ya tiene ese sufijo establecido."
does-not-have-prefix: "{0} no tiene ese prefijo establecido."
does-not-have-suffix: "{0} no tiene ese sufijo establecido."
addprefix-success: "&b{0}&a tiene el prefijo &f\"{1}&f\"&a establecido como prioridad de &b{2}&a."
addprefix-server-success: "&b{0}&a tiene el prefijo &f\"{1}&f\"&a establecido como prioridad de &b{2}&a en el servidor &b{3}&a."
addprefix-server-world-success: "&b{0}&a tiene el prefijo &f\"{1}&f\"&a establecido como prioridad de &b{2}&a en el servidor &b{3}&a, mundo &b{4}&a."
removeprefix-success: "&b{0}&a tiene el prefijo &f\"{1}&f\"&a en prioridad de &b{2}&a removido."
removeprefix-server-success: "&b{0}&a tiene el prefijo &f\"{1}&f\"&a en prioridad de &b{2}&a removido en el servidor &b{3}&a."
removeprefix-server-world-success: "&b{0}&a tiene el prefijo &f\"{1}&f\"&a en prioridad de &b{2}&a removido en el servidor &b{3}&a, mundo &b{4}&a."
addsuffix-success: "&b{0}&a tiene el sufijo &f\"{1}&f\"&a establecido en prioridad de &b{2}&a."
addsuffix-server-success: "&b{0}&a tiene el sufijo &f\"{1}&f\"&a establecido en prioridad de &b{2}&a en el servidor &b{3}&a."
addsuffix-server-world-success: "&b{0}&a tiene el sufijo &f\"{1}&f\"&a establecido en prioridad de &b{2}&a en el servidor &b{3}&a, mundo &b{4}&a."
removesuffix-success: "&b{0}&a tiene el sufijo &f\"{1}&f\"&a en prioridad de &b{2}&a removido."
removesuffix-server-success: "&b{0}&a tiene el sufijo &f\"{1}&f\"&a en prioridad de &b{2}&a removido en el servidor &b{3}&a."
removesuffix-server-world-success: "&b{0}&a tiene el sufijo &f\"{1}&f\"&a en prioridad de &b{2}&a removido en el servidor &b{3}&a, mundo &b{4}&a."
add-temp-prefix-success: "&b{0}&a tiene el prefijo &f\"{1}&f\"&a en prioridad de &b{2}&a por una duracion de &b{3}&a."
add-temp-prefix-server-success: "&b{0}&a tiene el prefijo &f\"{1}&f\"&a establecido en prioridad de &b{2}&a en el servidor &b{3}&a, por una duracion de &b{4}&a."
add-temp-prefix-server-world-success: "&b{0}&a tiene el prefijo &f\"{1}&f\"&a establecido en prioridad de &b{2}&a en el servidor &b{3}&a, mundo &b{4}&a, por una duracion de &b{5}&a."
remove-temp-prefix-success: "&b{0}&a tiene temporalmente el prefijo &f\"{1}&f\"&a en prioridad de &b{2}&a removido."
remove-temp-prefix-server-success: "&b{0}&a tiene temporalmente el prefijo &f\"{1}&f\"&a en prioridad de &b{2}&a removido en el servidor &b{3}&a."
remove-temp-prefix-server-world-success: "&b{0}&a tiene temporalmente el prefijo &f\"{1}&f\"&a en prioridad de &b{2}&a removido en el servidor &b{3}&a, mundo &b{4}&a."
add-temp-suffix-success: "&b{0}&a tiene el sufijo &f\"{1}&f\"&a establecido en prioridad de &b{2}&a por una duracion de &b{3}&a."
add-temp-suffix-server-success: "&b{0}&a tiene el sufijo &f\"{1}&f\"&a establecido en prioridad de &b{2}&a en el servidor &b{3}&a, por una duracion de &b{4}&a."
add-temp-suffix-server-world-success: "&b{0}&a tiene el sufijo &f\"{1}&f\"&a establecido en prioridad de &b{2}&a en el servidor &b{3}&a, mundo &b{4}&a, por una duracion de &b{5}&a."
remove-temp-suffix-success: "&b{0}&a tiene temporalmente el sufijo &f\"{1}&f\"&a en prioridad de &b{1}&a removido."
remove-temp-suffix-server-success: "&b{0}&a tiene temporalmente el sufijo &f\"{1}&f\"&a en prioridad de &b{2}&a removido en el servidor &b{3}&a."
remove-temp-suffix-server-world-success: "&b{0}&a tiene temporalmente el sufijo &f\"{1}&f\"&a en prioridad de &b{2}&a removido en el servidor &b{3}&a, mundo &b{4}&a."
already-has-meta: "{0} ya tiene ese par de valores de llave meta establecido."
set-meta-success: "&aEstablecer el valor meta para la llave &f\"{0}&f\"&a a &f\"{1}&f\"&a para &b{2}&a."
set-meta-server-success: "&aEstablecer el valor meta para la llave &f\"{0}&f\"&a a &f\"{1}&f\"&a para &b{2}&a en el servidor &b{3}&a."
set-meta-server-world-success: "&aEstablecer el valor meta para la llave &f\"{0}&f\"&a a &f\"{1}&f\"&a para &b{2}&a en el servidor &b{3}&a, mundo &b{4}&a."
set-meta-temp-success: "&aEstablecer el valor meta para la llave &f\"{0}&f\"&a a &f\"{1}&f\"&a para &b{2}&a por una duracion de &b{3}&a."
set-meta-temp-server-success: "&aEstablecer el valor meta para la llave &f\"{0}&f\"&a a &f\"{1}&f\"&a para &b{2}&a en el servidor &b{3}&a por una duracion de &b{4}&a."
set-meta-temp-server-world-success: "&aEstablecer el valor meta para la llave &f\"{0}&f\"&a a &f\"{1}&f\"&a para &b{2}&a en el servidor &b{3}&a, mundo &b{4}&a, por una duracion de &b{5}&a."
unset-meta-success: "&aDesestablecer el valor meta con la llave &f\"{0}&f\"&a para &b{1}&a."
unset-meta-server-success: "&aDesestablecer el valor meta con la llave &f\"{0}&f\"&a para &b{1}&a en el servidor &b{2}&a."
unset-meta-server-world-success: "&aDesestablecer el valor meta con la llave &f\"{0}&f\"&a for &b{1}&a en el servidor &b{2}&a, mundo &b{3}&a."
unset-meta-temp-success: "&aDesestablecer temporalmente el valor meta con la llave &f\"{0}&f\"&a para &b{1}&a."
unset-meta-temp-server-success: "&aDesestablecer temporalmente el valor meta con la llave &f\"{0}&f\"&a para &b{1}&a en el servidor &b{2}&a."
unset-meta-temp-server-world-success: "&aDesestablecer temporalmente el valor meta con la llave &f\"{0}&f\"&a para &b{1}&a en el servidor &b{2}&a, mundo &b{3}&a."
bulk-change-type-error: "Tipo no válido. Se estaba esperando 'server' o 'world'."
bulk-change-success: "&aEl cambio masivo fue aplicado correctamente. {0} los registros fueron cambiados."
user-info-general: >
{PREFIX}&b&l> &bUser Info: &f{0}\n
{PREFIX}&f- &3UUID: &f{1}\n
{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- &3Prefixes: &a{6}\n
{PREFIX}&f- &3Suffixes: &a{7}\n
{PREFIX}&f- &3Meta: &a{8}
user-info-data: >
{PREFIX}&f- &aCached Data:\n
{PREFIX}&f- &3Is Loaded: {0}\n
{PREFIX}&f- &3Current Contexts: {1}\n
{PREFIX}&f- &3Current Prefix: {2}\n
{PREFIX}&f- &3Current Suffix: {3}
info-parent-header: "&f- &aGrupos Parentales:"
info-temp-parent-header: "&f- &aGrupos Temporalmente Parentales:"
user-getuuid: "&bEl UUID de &b{0}&b es &b{1}&b."
user-removegroup-error-primary: "No puede eliminar un usuario de su grupo principal."
user-primarygroup-success: "&b{0}&a's grupo primario se estableció &b{1}&a."
user-primarygroup-warn-option: "&cAdvertencia: El método de cálculo de grupo primario utilizado por este servidor &7({0}) &cpuede no reflejar este cambio."
user-primarygroup-error-alreadyhas: "El usuario ya tiene este grupo configurado como su grupo principal."
user-primarygroup-error-notmember: "&b{0}&a ya no era miembro de &b{1}&a, agregándolos ahora."
user-track-error-not-contain-group: "El usuario especificado ya no está en ningún grupo de esta pista."
user-track-added-to-first: "&b{0}&a no está en ningún grupo en esta pista, por lo que se agregaron al primer grupo, &b{1}&a."
user-track-added-to-first-server: "&b{0}&a no está en ningún grupo en esta pista, por lo que se agregaron al primer grupo, &b{1}&a, en el servidor &b{2}&a."
user-track-added-to-first-server-world: "&b{0}&a no está en ningún grupo en esta pista, por lo que se agregaron al primer grupo, &b{1}&a, en el servidor &b{2}&a, mundo &b{3}&a."
user-promote-success: "&aPromover al usuario a lo largo de la pista &b{0}&a desde &b{1}&a a &b{2}&a."
user-promote-success-server: "&aPromover al usuario a lo largo de la pista &b{0}&a desde &b{1}&a a &b{2}&a en el servidor &b{3}&a."
user-promote-success-server-world: "&aPromover al usuario a lo largo de la pista &b{0}&a desde &b{1}&a a &b{2}&a en el servidor &b{3}&a, mundo &b{4}&a."
user-promote-error-endoftrack: "El final de la pista &4{0}&c fue alcanzado. No se puede promover al usuario."
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: "&aDegradando al usuario a lo largo de la pista &b{0}&a desde &b{1}&a a &b{2}&a."
user-demote-success-server: "&aDegradando al usuario a lo largo de la pista &b{0}&a desde &b{1}&a a &b{2}&a en el servidor &b{3}&a."
user-demote-success-server-world: "&aDegradando al usuario a lo largo de la pista &b{0}&a desde &b{1}&a a &b{2}&a en el servidor &b{3}&a, mundo &b{4}&a."
user-demote-endoftrack: "El final de la pista &4{0}&c fue alcanzado, asi que &4{1}&c fue removido de &4{2}&c."
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.
user-showpos: >
&aShowing &b{0}&a's position on track &b{1}&a.\n
{2}
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- &3Prefixes: &a{5}\n
{PREFIX}&f- &3Suffixes: &a{6}\n
{PREFIX}&f- &3Meta: &a{7}
group-set-weight: "&aEstablesca el peso a &b{0}&a para el grupo &b{1}&a."
track-info: >
{PREFIX}&b&l> &bShowing Track: &f{0}\n
{PREFIX}&f- &7Path: &f{1}
track-clear: "&b{0}&a's pista de grupos fue borrada."
track-append-success: "&aGrupo &b{0}&a fue añadido con éxito a la pista &b{1}&a."
track-insert-success: "&aGrupo &b{0}&a fue insertado con éxito en la pista &b{1}&a en la posicion &b{2}&a."
track-insert-error-number: "Numero esperado en lugar de recibido: {0}"
track-insert-error-invalid-pos: "No se puede insertar en la posición {0}. Indice fuera de los limites."
track-remove-success: "&aGrupo &b{0}&a se eliminó correctamente de la pista &b{1}&a."
log-load-error: "No se pudo cargar el registro."
log-invalid-page: "Número de página no válido."
log-invalid-page-range: "Número de página no válido. Por favor introduzca un valor entre 1 y {0}."
log-no-entries: "&bNo hay entradas de registro que mostrar."
log-entry: "&b#{0} -> &8(&7{1} hace&8) {2}"
log-notify-toggle-on: "&aHabilitada&b salida de registro."
log-notify-toggle-off: "&cDeshabilitada&b salida de registro."
log-notify-already-on: "Ya está recibiendo notificaciones."
log-notify-already-off: "Actualmente no estás recibiendo notificaciones."
log-notify-unknown: "Estado desconocido. Esperando \"on\" o \"off\"."
log-search-header: "&aMostrar acciones recientes para la consulta &b{0} &a(pagina &f{1}&a de &f{2}&a)"
log-recent-header: "&aMostrando acciones recientes (pagina &f{0}&a de &f{1}&a)"
log-recent-by-header: "&aMostrando las acciones recientes por &b{0} &a(pagina &f{1}&a de &f{2}&a)"
log-history-user-header: "&aMostrando el historial del usuario &b{0} &a(pagina &f{1}&a de &f{2}&a)"
log-history-group-header: "&aMostrando la historia del grupo &b{0} &a(pagina &f{1}&a de &f{2}&a)"
log-history-track-header: "&aMostrando la historia de la pista &b{0} &a(pagina &f{1}&a de &f{2}&a)"
log-export-already-exists: "Error: Archivo {0} ya existe."
log-export-not-writable: "Error: Archivo {0} no se puede escribir."
log-export-empty: "El registro está vacío y por lo tanto no puede ser exportado."
log-export-failure: "Se ha producido un error inesperado al escribir en el archivo."
log-export-success: "&aExitosamente exportó el registro a &b{0}&a."
import-already-running: "Ya se está ejecutando otro proceso de importación. Por favor espere a que finalice e inténtelo de nuevo."
export-already-running: "Ya se está ejecutando otro proceso de exportación. Por favor espere a que finalice e inténtelo de nuevo."
import-log-doesnt-exist: "Error: Archivo {0} no existe."
import-log-not-readable: "Error: Archivo {0} no es legible."
import-log-failure: "Se ha producido un error inesperado al leer del archivo de registro."
import-progress: "&b(Import) &b-> &f{0} &fporcentaje completado &7- &b{1}&f/&b{2} &fOperaciones completas con &c{3} &ferrores."
import-progress-sin: "&b(Import) &b-> &f{0} &fporcentaje completado &7- &b{1}&f/&b{2} &fOperaciones completas con &c{3} &ferrores."
import-start: "&b(Import) &b-> &fInicio del proceso de importación."
import-end-complete: "&b(Import) &a&lCOMPLETADO &7- tomo &b{0} &7segundos - &7No errores."
import-end-complete-err: "&b(Import) &a&lCOMPLETADO &7- tomo &b{0} &7segundos - &c{1} errores."
import-end-complete-err-sin: "&b(Import) &a&lCOMPLETADO &7- tomo &b{0} &7segundos - &c{1} errores."
import-end-error-header: >
{PREFIX}&b(Import) &7------------> &fShowing Error #&b{0} &7<------------\n
{PREFIX}&b(Import) &fWhilst executing: &3Command #{1}\n
{PREFIX}&b(Import) &fCommand: &7{2}\n
{PREFIX}&b(Import) &fType: &3{3}\n
{PREFIX}&b(Import) &fOutput:
import-end-error-content: "&b(Import) &b-> &c{0}"
import-end-error-footer: "&b(Import) &7<------------------------------------------>"
+303
View File
@@ -0,0 +1,303 @@
# LuckPerms Language File
# Locale: ru_RU
# Author: Pixelmon.PRO
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На этом сервере отключена ванильная система OP."
op-disabled-sponge: "&2Статус Оператора Сервера не влияет на установленный плагин разрешений. Пожалуйста, отредактируйте данные пользователя напрямую."
log: "&3LOG &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» вместо указания узла (node)."
verbose-invalid-filter: "&cНедопустимый подробный (verbose) фильтр: &f{0}"
verbose-on: "&Запись подробной проверки установлена на &aTRUE &bдля всех разрешений (пермов)."
verbose-on-query: "&Запись подробной проверки установлена на &aTRUE &bДля разрешений, соответствующих следующим фильтрам: &f{0}"
verbose-off: "&Запись подробной проверки установлена на &cFALSE&b."
verbose-recording-on: "&Запись подробной записи установлена ​​на &aTRUE &bдля всех разрешений."
verbose-recording-on-query: "&Запись подробной записи установлена ​​на &aTRUE &bДля разрешений, соответствующих следующим фильтрам: &f{0}"
verbose-recording-upload-start: "&bЗапись подробной информации была отключена. Загрузка результатов..."
verbose-recording-url: "&aПодробный URL результатов:"
tree-upload-start: "&bСоздание дерева разрешений..."
tree-empty: "&aНе удалось создать дерево. Ничего не найдено."
tree-url: "&aURL дерева разрешений:"
search-searching: "&aПоиск пользователей и групп с помощью &b{0}&a..."
search-searching-members: "&aПоиск пользователей и групп, унаследовавших от &b{0}&a..."
search-result: "&aНайдено &b{0}&a записи из &b{1}&a пользователей и &b{2}&a групп."
search-showing-users: "&bПросмотр записей пользователя:"
search-showing-groups: "&bПросмотр записей в группах:"
search-showing-users-with-page: "&bПросмотр записей пользователя: {0}"
search-showing-groups-with-page: "&bПросмотр записей в группах: {0}"
check-result: "&aРезультат проверки разрешения пользователя &b{0}&a на разрешение &b{1}&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} cообщения и удаленный доступ."
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
{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Применения подстановочных знаков (Wildcards): {21}\n
{PREFIX}&f- &3Применение регулярных выражений: {22}\n
{PREFIX}&f- &3Применение стенографии (Shorthand): {23}
create-group-error: "При создании группы произошла ошибка."
delete-group-error: "При удалении группы произошла ошибка."
delete-group-error-default: "Вы не можете удалить группу по умолчанию."
groups-list: "&aГруппы: {0}"
create-track-error: "При создании пути произошла ошибка."
delete-track-error: "При удалении пути произошла ошибка."
tracks-list: "&aГенеральные пути: {0}"
listnodes: "&b{0}'s Узлы:"
listnodes-with-page: "&b{0}'s Узлы: {1}"
listnodes-temp: >
&b{0}'s Временные узлы:\n
{1}
listparents: >
&b{0}'s Родительские группы:\n
{1}
listparents-temp: >
&b{0}'s Временные родительские группы:\n
{1}
listgroups: >
&b{0}'s Группы:\n
{1}
listgroups-temp: >
&b{0}'s Временные группы:\n
{1}
list-tracks: >
&b{0}'s Пути:\n
{1}
list-tracks-empty: "{0} не на каких-либо путях."
context-pair-inline: "&3{0}=&b{1}"
context-pair--global-inline: "&eГлобальный"
context-pair-end: "&a."
context-pair-sep: "&a, "
context-pair: "&8(&7{0}=&f{1}&8)"
check-permission: "&b{0}&a имеет разрешение &b{1}&a установленое в {2}&a в контексте {3}&a."
check-permission-inherited: "&b{0}&a имеет разрешение &b{1}&a установленое в {2}&a в контексте {3}&a, наследованный от &b{4}&a."
setpermission-success: "&aУстановить &b{0}&a к &b{1}&a для &b{2}&a."
setpermission-temp-success: "&aУстановить &b{0}&a к &b{1}&a для &b{2}&a На срок &b{3}&a."
unsetpermission-success: "&aСбросить &b{0}&a для &b{1}&a."
unset-temp-permission-success: "&aСбросить временное разрешение &b{0}&a для &b{1}&a."
set-inherit-success: "&b{0}&a теперь наследует разрешения от &b{1}&a."
set-parent-success: "&b{0}&a их существующие родительские группы очищены, и теперь только наследуют &b{1}&a."
set-temp-inherit-success: "&b{0}&a теперь наследует разрешения от &b{1}&a на срок &b{2}&a."
unset-inherit-success: "&b{0}&a больше не наследует разрешения от &b{1}&a."
unset-temp-inherit-success: "&b{0}&a больше не наследует временные разрешения (пермы) &b{1}&a."
clear-success: "&b{0}&a's разрешения были очищены. (&b{1}&a узлы были удалены.)"
clear-success-singular: "&b{0}&a's разрешения были очищены. (&b{1}&a узел удален.)"
parent-clear-success: "&b{0}&a's родители очищены. (&b{1}&a узлы были удалены.)"
parent-clear-success-singular: "&b{0}&a's родители очищены. (&b{1}&a узел удален.)"
meta-clear-success: "&b{0}&a's мета очищена. (&b{1}&a узлы были удалены.)"
meta-clear-success-singular: "&b{0}&a's мета очищена. (&b{1}&a узел удален.)"
illegal-date-error: "Не удалось выполнить синтаксический анализ даты '{0}'."
past-date-error: "Вы не можете установить дату в прошлом!"
chat-meta-prefix-header: "&b{0}'s Префиксы"
chat-meta-suffix-header: "&b{0}'s Суффиксы"
meta-header: "&b{0}'s Мета"
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 имеет {1} &f\"{2}&f\"&a установлен в приоритете &b{3}&a в контексте {4}&a."
add-temp-chatmeta-success: "&b{0}&a имеет {1} &f\"{2}&f\"&a установлен в приоритете &b{3}&a на срок &b{4}&a в контексте {5}&a."
remove-chatmeta-success: "&b{0}&a имеет {1} &f\"{2}&f\"&a в приоритете &b{3}&a удален из контекста {4}&a."
bulk-remove-chatmeta-success: "&b{0}&a все временные {1}es в приоритете &b{3}&a были удалены из контекста {4}&a."
remove-temp-chatmeta-success: "&b{0}&a временно установленный {1} &f\"{2}&f\"&a в приоритете &b{3}&a был удален из контекста {4}&a."
bulk-remove-temp-chatmeta-success: "&b{0}&a все временные {1}es в приоритете &b{3}&a были удалены из контекста {4}&a."
already-has-meta: "{0} уже установлено, что установлена ​​пара мета-значения клавиши."
set-meta-success: "&aУстановить метазначение для клавиши &f\"{0}&f\"&a к &f\"{1}&f\"&a для &b{2}&a."
set-meta-temp-success: "&aУстановить метазначение для клавиши &f\"{0}&f\"&a к &f\"{1}&f\"&a для &b{2}&a на срок &b{3}&a."
unset-meta-success: "&aСбросить метазначение с помощью клавиши &f\"{0}&f\"&a для &b{1}&a."
unset-meta-temp-success: "&aСбросить временное мета-значение с помощью клавиши &f\"{0}&f\"&a для &b{1}&a."
bulk-update-invalid-data-type: "Недопустимый тип. Выберите 'all', 'users' или 'groups'."
bulk-update-invalid-constraint: "Недопустимое ограничение &4{0}&c. Ограничения должны быть в формате '&f<поле> <оператор сравнения> <значение>&c'."
bulk-update-invalid-comparison: "Недопустимый оператор сравнения '&4{0}&c'. Выберите одно из следующих: &f== != ~~ ~!"
bulk-update-queued: "&aМассовое обновление было поставлено в очередь. &7(&f{0}&7)"
bulk-update-confirm: "&aВведите &b/{0} bulkupdate confirm {1} &aчтобы подтвердить выполнение массового обновления."
bulk-update-unknown-id: "&aОперация с id &b{0} &aне существует или истек срок действия."
bulk-update-starting: "&aВыполнение массового обновления."
bulk-update-success: "&bМассовое обновление успешно завершено."
bulk-update-failure: "&cНе удалось выполнить массовое обновление. Проверьте консоль на наличие ошибок."
bulk-change-type-error: "Недопустимый тип. Введите 'server' или 'world'."
bulk-change-success: "&aПрименение изменения успешно. {0} записей изменено."
user-info-general: >
{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{6}\n
{PREFIX}&f- &3Суффиксы: &a{7}\n
{PREFIX}&f- &3Мета: &a{8}
user-info-data: >
{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: "&bUUID для &b{0}&b это &b{1}&b."
user-removegroup-error-primary: "Вы не можете удалить пользователя из своей основной группы."
user-primarygroup-success: "&b{0}&a's для основной группы установил значение &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{1}&a в контексте {2}&a."
user-promote-success: "&aПовышение пользователя &b{0}&a от &b{1}&a к &b{2}&a в контексте {3}&a."
user-promote-error-endoftrack: "Конец пути &4{0}&c, был достигнут. Не удалось повысить пользователя."
user-promote-error-malformed: >
{PREFIX}Следующая группа на пути, {0}, более не существует. Не удалось продвинуть пользователя.\n
{PREFIX}Создайте группу или удалите ее с пути и повторите попытку.
user-demote-success: "&aПонижение пользователя по пути &b{0}&a от &b{1}&a к &b{2}&a в контексте {3}&a."
user-demote-endoftrack: "Конец пути &4{0}&c был достингут, поэтому &4{1}&c был удален из &4{2}&c."
user-demote-error-malformed: >
{PREFIX}Предыдущая группа на пути, {0}, более не существуют. Невозможно понизить рейтинг пользователя.\n
{PREFIX}Создайте группу или удалите ее с пути и повторите попытку.
user-showpos: >
&aпоказывает &b{0}&a's позицию на пути &b{1}&a.\n
{2}
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{4}\n
{PREFIX}&f- &3Префиксы: &a{5}\n
{PREFIX}&f- &3Суффиксы: &a{6}\n
{PREFIX}&f- &3Мета: &a{7}
group-set-weight: "&aУстановить значение &b{0}&a для группы &b{1}&a."
track-info: >
{PREFIX}&b&l> &bПоказать путь: &f{0}\n
{PREFIX}&f- &7Путь: &f{1}
track-clear: "&b{0}&a's путь групп был очищен."
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: "&aГруппа &b{0}&a Успешно удален с пути &b{1}&a."
log-load-error: "Не удалось загрузить лог."
log-invalid-page: "Недействительный номер страницы."
log-invalid-page-range: "Недопустимый номер страницы. Введите значение от 1 до {0}."
log-no-entries: "&bНет логов для отображения."
log-entry: "&b#{0} -> &8(&7{1} тому назад&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(страница &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: "Ошибка: файл {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} &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(Import) &7------------> &fОтображение ошибки #&b{0} &7<------------\n
{PREFIX}&b(Import) &fВыполняя: &3Команду #{1}\n
{PREFIX}&b(Import) &fКоманда: &7{2}\n
{PREFIX}&b(Import) &fТип: &3{3}\n
{PREFIX}&b(Import) &fВывод:
import-end-error-content: "&b(Импортировать) &b-> &c{0}"
import-end-error-footer: "&b(Импортировать) &7<------------------------------------------>"
+303
View File
@@ -0,0 +1,303 @@
# LuckPerms Language File
# Locale: zh-CN (Simplified Chinese)
# 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服务器管理员状态在安装权限插件时无效,请直接编辑用户数据."
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详细结果的网址:"
tree-upload-start: "&b生成权限树..."
tree-empty: "&a无法生成树,没有找到结果."
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-showing-users: "&b显示用户条目:"
search-showing-groups: "&b显示组条目:"
search-showing-users-with-page: "&b显示用户条目: {0}"
search-showing-groups-with-page: "&b显示组条目: {0}"
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
{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}
list-tracks: >
&b{0}的轨道:\n
{1}
list-tracks-empty: "{0}不在任何轨道上."
context-pair-inline: "&3{0}=&b{1}"
context-pair--global-inline: "&e全局"
context-pair-end: "&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: "您不能设定一个过去的日期!"
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}记录已更改."
user-info-general: >
{PREFIX}&b&l> &bUser Info: &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{6}\n
{PREFIX}&f- &3后缀: &a{7}\n
{PREFIX}&f- &3元信息: &a{8}
user-info-data: >
{PREFIX}&f- &aCached Data:\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-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中移除."
user-demote-error-malformed: >
{PREFIX}轨道上的上一组{0}不存在.无法降级用户.\n
{PREFIX}创建组,或将其从轨道中删除,然后重试.
user-showpos: >
&a显示&b{0}&a在轨道&b{1}&a的位置.\n
{2}
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{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."
track-info: >
{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没有日志条目显示."
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}错误."
import-end-error-header: >
{PREFIX}&b(导入) &7------------> &f显示错误 #&b{0} &7<------------\n
{PREFIX}&b(导入) &f执行时: &3Command #{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<------------------------------------------>"
+3 -2
View File
@@ -1,6 +1,7 @@
MIT License
The MIT License.
Copyright (c) 2016 Lucko (Luck) <luck@lucko.me>
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
+6 -6
View File
@@ -11,7 +11,7 @@ LuckPerms also includes an extensive API for developers, and support for a varie
## Useful Links
* **Development Builds** - <https://ci.lucko.me/job/LuckPerms>
* **Javadocs** - <https://jd.lucko.me/LuckPerms>
* **Javadocs** - <https://luckperms.lucko.me/javadocs/>
* **Wiki** - <https://github.com/lucko/LuckPerms/wiki>
## Building
@@ -32,13 +32,13 @@ LuckPerms is licensed under the permissive MIT license. Please see [`LICENSE.txt
## LuckPerms vs Other Plugins
### Bukkit
![alt text](https://static.lucko.me/luckperms-compare-copy.png "Feature comparison")
###### _Features checked on 23rd Aug 2016. If you find any inaccuracies, please let me know._
![alt text](https://luckperms.lucko.me/assets/bukkit-compare.png "Feature comparison")
### Sponge
![alt text](https://static.lucko.me/luckperms-compare-sponge.png "Feature comparison")
###### _Features checked on 8th Oct 2016. If you find any inaccuracies, please let me know._
![alt text](https://luckperms.lucko.me/assets/sponge-compare.png "Feature comparison")
## 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").
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").
+13 -2
View File
@@ -5,17 +5,28 @@
<parent>
<artifactId>luckperms</artifactId>
<groupId>me.lucko.luckperms</groupId>
<version>3.0-SNAPSHOT</version>
<version>3.2-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>luckperms-api</artifactId>
<build>
<finalName>LuckPerms-API-${release.version}</finalName>
<resources>
<resource>
<directory>../</directory>
<filtering>false</filtering>
<includes>
<include>LICENSE.txt</include>
</includes>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<version>${compiler.version}</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
@@ -29,8 +29,8 @@ import java.util.Optional;
/**
* Singleton for 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>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>
*/
public final class LuckPerms {
private static LuckPermsApi api = null;
@@ -49,7 +49,7 @@ public final class LuckPerms {
}
/**
* Gets an instance of {@link LuckPermsApi} safely.
* 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}.
@@ -60,10 +60,12 @@ public final class LuckPerms {
return Optional.ofNullable(api);
}
/* method used by the implementation to set the singleton instance */
static void registerProvider(LuckPermsApi luckPermsApi) {
api = luckPermsApi;
}
/* method used by the implementation to remove any previous instance */
static void unregisterProvider() {
api = null;
}
@@ -0,0 +1,102 @@
/*
* 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 java.util.Map;
/**
* Represents a type of chat meta
*
* @since 3.2
*/
public enum ChatMetaType {
/**
* Represents a prefix
*/
PREFIX {
@Override
public boolean matches(Node node) {
return node.isPrefix();
}
@Override
public boolean shouldIgnore(Node node) {
return !node.isPrefix();
}
@Override
public Map.Entry<Integer, String> getEntry(Node node) {
return node.getPrefix();
}
},
/**
* Represents a suffix
*/
SUFFIX {
@Override
public boolean matches(Node node) {
return node.isSuffix();
}
@Override
public boolean shouldIgnore(Node node) {
return !node.isSuffix();
}
@Override
public Map.Entry<Integer, String> getEntry(Node node) {
return node.getSuffix();
}
};
/**
* Returns if the passed node matches the type
*
* @param node the node to test
* @return true if the node has the same type
*/
public abstract boolean matches(Node node);
/**
* Returns if the passed node should be ignored when searching for meta of this type
*
* @param node the node to test
* @return true if the node does not share the same type
*/
public abstract boolean shouldIgnore(Node node);
/**
* Maps the corresponding entry from the given node
*
* @param node the node to retrieve the entry from
* @return the entry
* @throws IllegalStateException if the node does not share the same type
*/
public abstract Map.Entry<Integer, String> getEntry(Node node);
}
@@ -27,7 +27,7 @@ import me.lucko.luckperms.api.context.ContextSet;
/**
* Context and options for a permission lookup.
*
* <p> All values are immutable.
* <p>All values are immutable.</p>
*
* @since 2.11
*/
@@ -165,6 +165,7 @@ public class Contexts {
return this.applyGlobalWorldGroups;
}
@Override
public String toString() {
return "Contexts(" +
"context=" + this.getContexts() + ", " +
@@ -181,40 +182,26 @@ public class Contexts {
* Ugly auto-generated lombok code
*/
/**
* Check for equality
*
* @param o the other
* @return true if equal
* @since 2.12
*/
@Override
public boolean equals(Object o) {
if (o == this) return true;
if (!(o instanceof Contexts)) return false;
final Contexts other = (Contexts) o;
final Object this$context = this.getContexts();
final Object other$context = other.getContexts();
if (this$context == null ? other$context != null : !this$context.equals(other$context)) return false;
if (this.isOp() != other.isOp()) return false;
if (this.isIncludeGlobal() != other.isIncludeGlobal()) return false;
if (this.isIncludeGlobalWorld() != other.isIncludeGlobalWorld()) return false;
if (this.isApplyGroups() != other.isApplyGroups()) return false;
if (this.isApplyGlobalGroups() != other.isApplyGlobalGroups()) return false;
if (this.isApplyGlobalWorldGroups() != other.isApplyGlobalWorldGroups()) return false;
return true;
return this.getContexts().equals(other.getContexts()) &&
this.isOp() == other.isOp() &&
this.isIncludeGlobal() == other.isIncludeGlobal() &&
this.isIncludeGlobalWorld() == other.isIncludeGlobalWorld() &&
this.isApplyGroups() == other.isApplyGroups() &&
this.isApplyGlobalGroups() == other.isApplyGlobalGroups() &&
this.isApplyGlobalWorldGroups() == other.isApplyGlobalWorldGroups();
}
/**
* Gets the hashcode
*
* @return the hashcode
* @since 2.12
*/
@Override
public int hashCode() {
final int PRIME = 59;
int result = 1;
final Object $context = this.getContexts();
result = result * PRIME + ($context == null ? 43 : $context.hashCode());
final Object contexts = this.getContexts();
result = result * PRIME + (contexts == null ? 43 : contexts.hashCode());
result = result * PRIME + (this.isOp() ? 79 : 97);
result = result * PRIME + (this.isIncludeGlobal() ? 79 : 97);
result = result * PRIME + (this.isIncludeGlobalWorld() ? 79 : 97);
@@ -20,48 +20,46 @@
* SOFTWARE.
*/
package me.lucko.luckperms.common.caching.stacking.elements;
package me.lucko.luckperms.api;
import lombok.RequiredArgsConstructor;
import me.lucko.luckperms.exceptions.ObjectAlreadyHasException;
import me.lucko.luckperms.exceptions.ObjectLacksException;
import me.lucko.luckperms.api.LocalizedNode;
import me.lucko.luckperms.common.caching.stacking.MetaStackElement;
import java.util.function.Supplier;
import java.util.Map;
import java.util.Optional;
public enum DataMutateResult {
@RequiredArgsConstructor
public class HighestPriorityOwnElement implements MetaStackElement {
private final boolean prefix;
private Map.Entry<Integer, String> entry = null;
SUCCESS(true, null),
ALREADY_HAS(false, ObjectAlreadyHasException::new),
LACKS(false, ObjectLacksException::new),
FAIL(false, RuntimeException::new);
@Override
public Optional<Map.Entry<Integer, String>> getEntry() {
return Optional.ofNullable(entry);
private boolean value;
private final Supplier<? extends Exception> exceptionSupplier;
DataMutateResult(boolean value, Supplier<? extends Exception> exceptionSupplier) {
this.value = value;
this.exceptionSupplier = exceptionSupplier;
}
@Override
public boolean accumulateNode(LocalizedNode node) {
if (MetaStackElement.checkMetaType(prefix, node)) {
return false;
public void throwException() {
if (exceptionSupplier != null) {
sneakyThrow(exceptionSupplier.get());
}
if (MetaStackElement.checkOwnElement(node)) {
return false;
}
Map.Entry<Integer, String> entry = prefix ? node.getPrefix() : node.getSuffix();
if (HighestPriorityElement.compareEntries(this.entry, entry)) {
return false;
}
this.entry = entry;
return true;
}
@Override
public MetaStackElement copy() {
return new HighestPriorityOwnElement(prefix);
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);
}
private static <T extends Throwable> void sneakyThrow0(Throwable t) throws T {
throw (T) t;
}
}
@@ -22,6 +22,7 @@
package me.lucko.luckperms.api;
import me.lucko.luckperms.api.context.ContextSet;
import me.lucko.luckperms.exceptions.ObjectAlreadyHasException;
import me.lucko.luckperms.exceptions.ObjectLacksException;
@@ -41,7 +42,7 @@ public interface Group extends PermissionHolder {
String getName();
/**
* Check to see if a group inherits a group
* Check to see if a group inherits another group directly
*
* @param group The group to check membership of
* @return true if the group inherits the other group
@@ -50,6 +51,26 @@ public interface Group extends PermissionHolder {
*/
boolean inheritsGroup(Group group);
/**
* Check to see if a group inherits another group directly
*
* @param group The group to check membership of
* @param contextSet the context set to filter by
* @return true if the group inherits the other group
* @throws NullPointerException if the group is null
* @throws IllegalStateException if the group instance was not obtained from LuckPerms.
* @since 3.2
*/
boolean inheritsGroup(Group group, ContextSet contextSet);
/**
* Gets the weight of this group, if present.
*
* @return the group weight
* @since 2.17
*/
OptionalInt getWeight();
/**
* Check to see if the group inherits a group on a specific server
*
@@ -59,7 +80,9 @@ public interface Group extends PermissionHolder {
* @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(Group group, String server);
/**
@@ -72,7 +95,9 @@ public interface Group extends PermissionHolder {
* @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(Group group, String server, String world);
/**
@@ -82,7 +107,9 @@ public interface Group extends PermissionHolder {
* @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(Group group) throws ObjectAlreadyHasException;
/**
@@ -94,7 +121,9 @@ public interface Group extends PermissionHolder {
* @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(Group group, String server) throws ObjectAlreadyHasException;
/**
@@ -107,7 +136,9 @@ public interface Group extends PermissionHolder {
* @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(Group group, String server, String world) throws ObjectAlreadyHasException;
/**
@@ -119,7 +150,9 @@ public interface Group extends PermissionHolder {
* @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(Group group, long expireAt) throws ObjectAlreadyHasException;
/**
@@ -132,7 +165,9 @@ public interface Group extends PermissionHolder {
* @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(Group group, String server, long expireAt) throws ObjectAlreadyHasException;
/**
@@ -146,7 +181,9 @@ public interface Group extends PermissionHolder {
* @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(Group group, String server, String world, long expireAt) throws ObjectAlreadyHasException;
/**
@@ -156,7 +193,9 @@ public interface Group extends PermissionHolder {
* @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(Group group) throws ObjectLacksException;
/**
@@ -167,7 +206,9 @@ public interface Group extends PermissionHolder {
* @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(Group group, boolean temporary) throws ObjectLacksException;
/**
@@ -179,7 +220,9 @@ public interface Group extends PermissionHolder {
* @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(Group group, String server) throws ObjectLacksException;
/**
@@ -192,7 +235,9 @@ public interface Group extends PermissionHolder {
* @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(Group group, String server, String world) throws ObjectLacksException;
/**
@@ -205,7 +250,9 @@ public interface Group extends PermissionHolder {
* @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(Group group, String server, boolean temporary) throws ObjectLacksException;
/**
@@ -219,14 +266,18 @@ public interface Group extends PermissionHolder {
* @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(Group group, String server, 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
List<String> getGroupNames();
/**
@@ -236,7 +287,9 @@ public interface Group extends PermissionHolder {
* @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
List<String> getLocalGroups(String server);
@@ -248,15 +301,9 @@ public interface Group extends PermissionHolder {
* @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
List<String> getLocalGroups(String server, String world);
/**
* Gets the weight of this group, is present.
*
* @return the group weight
* @since 2.17
*/
OptionalInt getWeight();
}
@@ -29,8 +29,8 @@ import java.util.UUID;
/**
* 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>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>
*/
public interface Log {
@@ -27,7 +27,7 @@ import java.util.UUID;
/**
* A single entry in the log
*
* <p> Implements {@link Comparable} ordering based upon the timestamp of the entry.
* <p>Implements {@link Comparable} ordering based upon the timestamp of the entry.</p>
*/
public class LogEntry implements Comparable<LogEntry> {
private static final String FORMAT = "&8(&e%s&8) [&a%s&8] (&b%s&8) &7--> &f%s";
@@ -90,9 +90,9 @@ public class LogEntry implements Comparable<LogEntry> {
public String getFormatted() {
return String.format(FORMAT,
actorName,
String.valueOf(actorName).equals("null") ? actor.toString() : actorName,
Character.toString(type),
actedName,
String.valueOf(actedName).equals("null") && acted != null ? acted.toString() : actedName,
action
);
}
@@ -171,13 +171,13 @@ public class LogEntry implements Comparable<LogEntry> {
if (o == this) return true;
if (!(o instanceof LogEntry)) return false;
final LogEntry other = (LogEntry) o;
if (this.getTimestamp() != other.getTimestamp()) return false;
if (this.getActor() == null ? other.getActor() != null : !this.getActor().equals(other.getActor())) return false;
if (this.getActorName() == null ? other.getActorName() != null : !this.getActorName().equals(other.getActorName())) return false;
if (this.getType() != other.getType()) return false;
if (this.getActed() == null ? other.getActed() != null : !this.getActed().equals(other.getActed())) return false;
if (this.getActedName() == null ? other.getActedName() != null : !this.getActedName().equals(other.getActedName())) return false;
return this.getAction() == null ? other.getAction() == null : this.getAction().equals(other.getAction());
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
@@ -23,10 +23,10 @@
package me.lucko.luckperms.api;
/**
* A wrapper interface for platform logger instances
* Represents the logger instance being used by LuckPerms on the platform.
*
* <p> Bukkit/Bungee both use java.util.logging, and Sponge uses org.slf4j. This class wraps those classes so the
* commons module can access a logger.
* <p>Messages sent using the logger are sent prefixed with the LuckPerms tag, and on some implementations will be colored
* depending on the message type.</p>
*/
public interface Logger {
@@ -25,6 +25,7 @@ package me.lucko.luckperms.api;
import me.lucko.luckperms.api.context.ContextCalculator;
import me.lucko.luckperms.api.context.ContextSet;
import me.lucko.luckperms.api.event.EventBus;
import me.lucko.luckperms.api.metastacking.MetaStackFactory;
import java.util.Optional;
import java.util.Set;
@@ -42,6 +43,7 @@ public interface LuckPermsApi {
/**
* Gets the API version
*
* @return the version of the API running on the platform
* @since 2.6
*/
@@ -49,12 +51,14 @@ public interface LuckPermsApi {
/**
* Gets the plugin version
*
* @return the version of the plugin running on the platform
*/
String getVersion();
/**
* Gets the platform LuckPerms is running on
*
* @return the platform LuckPerms is running on
* @since 2.7
*/
@@ -62,6 +66,7 @@ public interface LuckPermsApi {
/**
* Gets the event bus, used for subscribing to events
*
* @return the event bus
* @since 3.0
*/
@@ -113,7 +118,9 @@ public interface LuckPermsApi {
User getUser(UUID uuid);
/**
* Gets a wrapped user object from the user storage. This method does not return null, unlike {@link #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
@@ -131,8 +138,9 @@ public interface LuckPermsApi {
User getUser(String name);
/**
* Gets a wrapped user object from the user storage. This method does not return null, unlike {@link
* #getUser(String)}
* 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
@@ -175,7 +183,9 @@ public interface LuckPermsApi {
Group getGroup(String name);
/**
* Gets a wrapped group object from the group storage. This method does not return null, unlike {@link #getGroup}
* 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
@@ -209,7 +219,9 @@ public interface LuckPermsApi {
Track getTrack(String name);
/**
* Gets a wrapped track object from the track storage. This method does not return null, unlike {@link #getTrack}
* 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
@@ -240,6 +252,14 @@ public interface LuckPermsApi {
*/
NodeFactory getNodeFactory();
/**
* Gets the MetaStackFactory, used for creating MetaStacks.
*
* @return the meta stack factory
* @since 3.2
*/
MetaStackFactory getMetaStackFactory();
/**
* Returns a permission builder instance
*
@@ -32,8 +32,8 @@ public interface MessagingService {
/**
* Uses the messaging service to inform other servers about changes.
*
* <p> This will push the update asynchronously, and this method will return immediately. Calling this method is
* equivalent to running "/lp networksync", except will not sync this server.
* <p>This will push the update asynchronously, and this method will return immediately. Calling this method is
* equivalent to running "/lp networksync", except will not sync this server.</p>
*/
void pushUpdate();
@@ -34,7 +34,9 @@ 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) {
@@ -92,42 +94,24 @@ public class MetaUtils {
* @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 node the meta node
* @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 node, String value) {
if (holder == null) {
throw new NullPointerException("holder");
}
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");
if (node == null) {
throw new NullPointerException("node");
}
if (value == null) {
throw new NullPointerException("value");
}
if (node.equals("")) {
throw new IllegalArgumentException("node is empty");
}
if (value.equals("")) {
throw new IllegalArgumentException("value is empty");
}
if (server == null || server.equals("")) {
server = "global";
}
node = escapeCharacters(node);
key = escapeCharacters(key);
value = escapeCharacters(value);
Set<Node> toRemove = new HashSet<>();
for (Node n : holder.getEnduringPermissions()) {
if (n.isMeta() && n.getMeta().getKey().equals(node)) {
if (n.isMeta() && n.getMeta().getKey().equals(key)) {
toRemove.add(n);
}
}
@@ -135,22 +119,20 @@ public class MetaUtils {
for (Node n : toRemove) {
try {
holder.unsetPermission(n);
} catch (ObjectLacksException ignored) {
}
} catch (ObjectLacksException ignored) {}
}
Node.Builder metaNode = LuckPerms.getApi().buildNode("meta." + node + "." + value).setValue(true);
if (!server.equalsIgnoreCase("global")) {
Node.Builder metaNode = LuckPerms.getApi().buildNode("meta." + key + "." + value).setValue(true);
if (server != null) {
metaNode.setServer(server);
}
if (world != null && !world.equals("")) {
metaNode.setServer(server).setWorld(world);
if (world != null) {
metaNode.setWorld(world);
}
try {
holder.setPermission(metaNode.build());
} catch (ObjectAlreadyHasException ignored) {
}
} catch (ObjectAlreadyHasException ignored) {}
}
/**
@@ -167,18 +149,8 @@ public class MetaUtils {
* @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 (server == null || server.equals("")) {
server = "global";
}
if (node == null) {
throw new NullPointerException("node");
}
if (holder == null) throw new NullPointerException("holder");
if (node == null) throw new NullPointerException("node");
if (node.equals("")) {
throw new IllegalArgumentException("node is empty");
}
@@ -186,23 +158,10 @@ public class MetaUtils {
node = escapeCharacters(node);
for (Node n : holder.getPermissions()) {
if (!n.getValue()) {
continue;
}
if (!n.getValue() || !n.isMeta()) continue;
if (!n.isMeta()) {
continue;
}
if (!server.equalsIgnoreCase("global")) {
if (!n.shouldApplyOnServer(server, includeGlobal, false)) {
continue;
}
}
if (!n.shouldApplyOnWorld(world, includeGlobal, false)) {
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)) {
@@ -214,27 +173,23 @@ public class MetaUtils {
}
private static void setChatMeta(boolean prefix, PermissionHolder holder, String value, int priority, String server, String world) {
if (holder == null) {
throw new NullPointerException("holder");
}
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.equalsIgnoreCase("global")) {
if (server != null) {
node.setServer(server);
}
if (world != null && !world.equals("")) {
node.setServer(server).setWorld(world);
if (world != null) {
node.setWorld(world);
}
try {
holder.setPermission(node.build());
} catch (ObjectAlreadyHasException ignored) {
}
} catch (ObjectAlreadyHasException ignored) {}
}
/**
@@ -271,26 +226,14 @@ public class MetaUtils {
if (holder == null) {
throw new NullPointerException("holder");
}
if (server == null) {
server = "global";
}
int priority = Integer.MIN_VALUE;
String meta = null;
for (Node n : holder.getAllNodes(Contexts.allowAll())) {
if (!n.getValue()) {
continue;
}
if (!n.getValue()) continue;
if (!server.equalsIgnoreCase("global")) {
if (!n.shouldApplyOnServer(server, includeGlobal, false)) {
continue;
}
}
if (!n.shouldApplyOnWorld(world, includeGlobal, false)) {
continue;
}
if (!n.shouldApplyOnServer(server, includeGlobal, false)) continue;
if (!n.shouldApplyOnWorld(world, includeGlobal, false)) continue;
if (prefix ? !n.isPrefix() : !n.isSuffix()) {
continue;
@@ -33,19 +33,21 @@ import java.util.Set;
/**
* An immutable permission node
*
* <p> Use {@link LuckPermsApi#buildNode(String)} to get an instance.
* <p>Use {@link LuckPermsApi#buildNode(String)} to get an instance.</p>
*
* @since 2.6
*/
public interface Node extends Map.Entry<String, Boolean> {
/**
* Returns the actual permission string
*
* @return the actual permission node
*/
String getPermission();
/**
* Get what value the permission is set to. A negated node would return <code>false</code>.
* Gets what value the permission is set to. A negated node would return <code>false</code>.
*
* @return the permission's value
*/
@@ -53,14 +55,22 @@ public interface Node extends Map.Entry<String, Boolean> {
Boolean getValue();
/**
* Returns the value of this node as a tristate
*
* @return the value of this node as a Tristate
*/
Tristate getTristate();
default Tristate getTristate() {
return Tristate.fromBoolean(getValue());
}
/**
* Returns if the node is negated
*
* @return true if the node is negated
*/
boolean isNegated();
default boolean isNegated() {
return !getValue();
}
/**
* If this node is set to override explicitly.
@@ -85,15 +95,49 @@ public interface Node extends Map.Entry<String, Boolean> {
Optional<String> getWorld();
/**
* Returns if this node is server specific
*
* @return true if this node is server specific
*/
boolean isServerSpecific();
/**
* Returns 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
*
* @return true if this node applies globally, and has no specific context
* @since 3.1
*/
boolean appliesGlobally();
/**
* Returns if this node has any specific context in order to apply.
*
* @return true if this node has specific context
* @since 3.1
*/
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, String server, String world, ContextSet context, boolean applyRegex);
/**
* If this node should apply on a specific server
*
@@ -167,28 +211,40 @@ public interface Node extends Map.Entry<String, Boolean> {
List<String> resolveShorthand();
/**
* Returns 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
*
* @return true if this node will not expire
*/
boolean isPermanent();
default boolean isPermanent() {
return !isTemporary();
}
/**
* Returns 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
*/
long getExpiryUnixTime();
/**
* Returns the date when this node will expire
*
* @return the {@link Date} when this node will expire
* @throws IllegalStateException if the node is not temporary
*/
Date getExpiry();
/**
* Return 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
*/
@@ -203,31 +259,49 @@ public interface Node extends Map.Entry<String, Boolean> {
boolean hasExpired();
/**
* Gets the extra contexts required for this node to apply
*
* @return the extra contexts required for this node to apply
* @since 2.13
*/
ContextSet getContexts();
/**
* The same as {@link #getContexts()}, but also includes values for "server" and "world" keys if present.
*
* @return the full contexts required for this node to apply
* @since 3.1
*/
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
String toSerializedNode();
/**
* Returns if this is a group node
*
* @return true if this is a group node
*/
boolean isGroupNode();
/**
* Returns the name of the group
*
* @return the name of the group
* @throws IllegalStateException if this is not a group node. See {@link #isGroupNode()}
*/
String getGroupName();
/**
* @return true is this node is a wildcard node
* Returns if this node is a wildcard node
*
* @return true if this node is a wildcard node
*/
boolean isWildcard();
@@ -240,6 +314,8 @@ public interface Node extends Map.Entry<String, Boolean> {
int getWildcardLevel();
/**
* Returns if this node is a meta node
*
* @return true if this node is a meta node
*/
boolean isMeta();
@@ -253,6 +329,8 @@ public interface Node extends Map.Entry<String, Boolean> {
Map.Entry<String, String> getMeta();
/**
* Returns if this node is a prefix node
*
* @return true if this node is a prefix node
*/
boolean isPrefix();
@@ -266,6 +344,8 @@ public interface Node extends Map.Entry<String, Boolean> {
Map.Entry<Integer, String> getPrefix();
/**
* Returns if this node is a suffix node
*
* @return true if this node is a suffix node
*/
boolean isSuffix();
@@ -33,10 +33,13 @@ public interface NodeFactory {
* Creates a node from a serialised node string
*
* @param serialisedPermission the serialised permission string
* @param value the value of the node
* @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
Node fromSerialisedNode(String serialisedPermission, boolean value);
@@ -60,29 +63,55 @@ public interface NodeFactory {
/**
* Creates a node builder from a serialised node string
*
* @param serialisedPermission the serialised permission string
* @param value the value of the node
* @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
Node.Builder newBuilderFromSerialisedNode(String serialisedPermission, boolean value);
/**
* Creates a node builder from a group
*
* @param group the group
* @return a node builder instance
* @throws NullPointerException if the group is null
* @throws IllegalStateException if the group instance was not obtained from LuckPerms.
* @since 3.1
*/
Node.Builder makeGroupNode(Group group);
/**
* Creates a node builder from a key value pair
*
* @param key the key
* @param key the key
* @param value the value
* @return a node builder instance
* @throws NullPointerException if the key or value is null
*/
Node.Builder makeMetaNode(String key, String value);
/**
* Creates a node builder for the given chat meta type
*
* @param priority the priority
* @param value the value for the prefix/suffix
* @return a node builder instance
* @throws NullPointerException if the type or value is null
* @since 3.2
*/
Node.Builder makeChatMetaNode(ChatMetaType type, int priority, String value);
/**
* Creates a node builder from a prefix string and priority
*
* @param priority the priority
* @param prefix the prefix string
* @param prefix the prefix string
* @return a node builder instance
* @throws NullPointerException if the prefix is null
*/
@@ -92,7 +121,7 @@ public interface NodeFactory {
* Creates a node builder from a prefix string and priority
*
* @param priority the priority
* @param suffix the suffix string
* @param suffix the suffix string
* @return a node builder instance
* @throws NullPointerException if the suffix is null
*/
@@ -22,30 +22,43 @@
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.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.function.Predicate;
/**
* An object capable of holding permissions
*
* <p> Any changes made will be lost unless the instance is saved back to the {@link Storage}.
* <p>Any changes made will be lost unless the instance is saved back to the {@link Storage}.</p>
*/
public interface PermissionHolder {
/**
* Gets the objects name
*
* <p> {@link User#getUuid()}, {@link User#getName()} or {@link Group#getName()} should normally be used instead of
* this method.
* <p>{@link User#getUuid()}, {@link User#getName()} or {@link Group#getName()} should normally be used instead of
* this method.</p>
*
* @return the identifier for this object. Either a uuid string or name.
*/
String getObjectName();
/**
* Gets a friendly name for this holder, to be displayed in command output, etc.
*
* <p>This will <strong>always</strong> return a value, eventually falling back to {@link #getObjectName()} if no
* other "friendlier" identifiers are present.</p>
*
* @return a friendly identifier for this holder
* @since 3.2
*/
String getFriendlyName();
/**
* Gets a sorted set of all held permissions.
*
@@ -57,15 +70,17 @@ public interface PermissionHolder {
/**
* 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>
*
* @return a set of nodes
* @since 2.6
*/
Set<? extends Node> getEnduringPermissions();
/**
* Gets an immutable set of all transiently held permissions.
* Similar to {@link #getPermissions()}, except without enduring permissions
*
* <p> Transient permissions only exist for the duration of the session.
* <p>Transient permissions only exist for the duration of the session.</p>
*
* @return a set of nodes
* @since 2.6
@@ -73,12 +88,30 @@ public interface PermissionHolder {
Set<? extends Node> getTransientPermissions();
/**
* Gets a mutable sorted set of the nodes that this object has and inherits, filtered by context
* Unlike {@link #getAllNodesFiltered(Contexts)}, this method will not filter individual nodes. The context is only
* used to determine which groups should apply.
* Nodes are sorted into priority order.
* Processes the nodes and returns the non-temporary ones.
*
* @param contexts the context for the lookup,
* @return a set of permanent nodes
* @since 2.6
*/
Set<Node> getPermanentPermissionNodes();
/**
* Processes the nodes and returns the temporary ones.
*
* @return a set of temporary nodes
* @since 2.6
*/
Set<Node> getTemporaryPermissionNodes();
/**
* Gets a mutable sorted set of the nodes that this object has and inherits, filtered by context
*
* <p>Unlike {@link #getAllNodesFiltered(Contexts)}, this method will not filter individual nodes. The context is only
* used to determine which groups should apply.</p>
*
* <p>Nodes are sorted into priority order.</p>
*
* @param contexts the context for the lookup
* @return a mutable sorted set of permissions
* @throws NullPointerException if the context is null
* @since 2.11
@@ -87,8 +120,9 @@ public interface PermissionHolder {
/**
* Gets a mutable set of the nodes that this object has and inherits, filtered by context.
* Unlike {@link #getAllNodes(Contexts)}, this method WILL filter individual nodes, and only return ones that fully
* meet the context provided.
*
* <p>Unlike {@link #getAllNodes(Contexts)}, this method WILL filter individual nodes, and only return ones that fully
* meet the context provided.</p>
*
* @param contexts the context for the lookup
* @return a mutable set of permissions
@@ -99,6 +133,7 @@ public interface PermissionHolder {
/**
* Converts the output of {@link #getAllNodesFiltered(Contexts)}, and expands shorthand permissions.
*
* @param contexts the context for the lookup
* @param lowerCase if the keys should be made lowercase whilst being exported
* @return a mutable map of permissions
@@ -130,6 +165,180 @@ public interface PermissionHolder {
*/
Tristate hasTransientPermission(Node node);
/**
* Checks to see if the object inherits a certain permission
*
* @param node the node to check for
* @return a Tristate for the holders inheritance status for the node
* @throws NullPointerException if the node is null
* @since 2.6
*/
Tristate inheritsPermission(Node node);
/**
* 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(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
*/
DataMutateResult setPermissionUnchecked(Node node);
/**
* 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 ObjectAlreadyHasException if the object already has the permission
* @throws NullPointerException if the node is null
* @since 2.6
*/
void setTransientPermission(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
*/
DataMutateResult setTransientPermissionUnchecked(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(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
*/
DataMutateResult unsetPermissionUnchecked(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(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
*/
DataMutateResult unsetTransientPermissionUnchecked(Node node);
/**
* Clears any nodes from the holder which pass the predicate
*
* @param test the predicate to test for nodes which should be removed
* @since 3.2
*/
void clearMatching(Predicate<Node> test);
/**
* Clears any transient nodes from the holder which pass the predicate
*
* @param test the predicate to test for nodes which should be removed
* @since 3.2
*/
void clearMatchingTransient(Predicate<Node> test);
/**
* Clears all nodes held by the object
*
* @since 2.17
*/
void clearNodes();
/**
* Clears all nodes held by the object in a specific context
*
* @param contextSet the contexts to filter by
* @since 3.2
*/
void clearNodes(ContextSet contextSet);
/**
* Clears all parent groups
*
* @since 2.17
*/
void clearParents();
/**
* Clears all parent groups in a specific context
*
* @param contextSet the contexts to filter by
* @since 3.2
*/
void clearParents(ContextSet contextSet);
/**
* Clears all meta held by the object
*
* @since 2.17
*/
void clearMeta();
/**
* Clears all meta held by the object in a specific context
*
* @param contextSet the contexts to filter by
* @since 3.2
*/
void clearMeta(ContextSet contextSet);
/**
* Clears all transient permissions the holder has.
*/
void clearTransientNodes();
/**
* Checks to see if the object has a certain permission
*
@@ -138,7 +347,9 @@ public interface PermissionHolder {
* @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(String node, boolean b);
/**
@@ -150,7 +361,9 @@ public interface PermissionHolder {
* @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(String node, boolean b, String server);
/**
@@ -163,7 +376,9 @@ public interface PermissionHolder {
* @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(String node, boolean b, String server, String world);
/**
@@ -175,7 +390,9 @@ public interface PermissionHolder {
* @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(String node, boolean b, boolean temporary);
/**
@@ -188,7 +405,9 @@ public interface PermissionHolder {
* @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(String node, boolean b, String server, boolean temporary);
/**
@@ -202,19 +421,11 @@ public interface PermissionHolder {
* @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(String node, boolean b, String server, String world, boolean temporary);
/**
* Cheks to see if the object inherits a certain permission
*
* @param node the node to check for
* @return a Tristate for the holders inheritance status for the node
* @throws NullPointerException if the node is null
* @since 2.6
*/
Tristate inheritsPermission(Node node);
/**
* Checks to see if the object inherits a certain permission
*
@@ -223,7 +434,9 @@ public interface PermissionHolder {
* @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(String node, boolean b);
/**
@@ -235,7 +448,9 @@ public interface PermissionHolder {
* @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(String node, boolean b, String server);
/**
@@ -248,7 +463,9 @@ public interface PermissionHolder {
* @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(String node, boolean b, String server, String world);
/**
@@ -260,7 +477,9 @@ public interface PermissionHolder {
* @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(String node, boolean b, boolean temporary);
/**
@@ -273,7 +492,9 @@ public interface PermissionHolder {
* @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(String node, boolean b, String server, boolean temporary);
/**
@@ -287,38 +508,11 @@ public interface PermissionHolder {
* @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(String node, boolean b, String server, String world, boolean temporary);
/**
* 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(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> 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> For unsetting a transient permission, see {@link #unsetTransientPermission(Node)}
*
* @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(Node node) throws ObjectAlreadyHasException;
/**
* Sets a permission for the object
*
@@ -406,26 +600,6 @@ public interface PermissionHolder {
@Deprecated
void setPermission(String node, boolean value, String server, String world, long expireAt) throws ObjectAlreadyHasException;
/**
* 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(Node node) throws ObjectLacksException;
/**
* 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(Node node) throws ObjectLacksException;
/**
* Unsets a permission for the object
*
@@ -507,19 +681,14 @@ public interface PermissionHolder {
@Deprecated
void unsetPermission(String node, String server, String world, boolean temporary) throws ObjectLacksException;
/**
* Clears all nodes held by the object
*
* @since 2.17
*/
void clearNodes();
/**
* 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(String server);
/**
@@ -528,22 +697,19 @@ public interface PermissionHolder {
* @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(String server, String world);
/**
* Clears all parent groups
*
* @since 2.17
*/
void clearParents();
/**
* 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(String server);
/**
@@ -552,22 +718,19 @@ public interface PermissionHolder {
* @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(String server, String world);
/**
* Clears all meta held by the object
*
* @since 2.17
*/
void clearMeta();
/**
* 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(String server);
/**
@@ -576,7 +739,9 @@ public interface PermissionHolder {
* @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(String server, String world);
/**
@@ -586,28 +751,9 @@ public interface PermissionHolder {
* @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 #removeIf(Predicate)}
*/
@Deprecated
void clearMetaKeys(String key, String server, String world, boolean temporary);
/**
* Clears all transient permissions the holder has.
*/
void clearTransientNodes();
/**
* Processes the nodes and returns the non-temporary ones.
*
* @return a set of permanent nodes
* @since 2.6
*/
Set<Node> getPermanentPermissionNodes();
/**
* Processes the nodes and returns the temporary ones.
*
* @return a set of temporary nodes
* @since 2.6
*/
Set<Node> getTemporaryPermissionNodes();
}
@@ -34,11 +34,11 @@ import java.util.function.Consumer;
*
* <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
* blocked.
* 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
* callback using {@link CompletableFuture#thenAcceptAsync(Consumer, Executor)} and {@link #getSyncExecutor()}.
* callback using {@link CompletableFuture#thenAcceptAsync(Consumer, Executor)} and {@link #getSyncExecutor()}.</p>
*
* @since 2.14
*/
@@ -93,13 +93,23 @@ public interface Storage {
* Loads a user's data from the main storage into the plugins local storage.
*
* @param uuid the uuid of the user to load
* @param username the users username. (if you want to specify <code>null</code> here, just input "null" as a
* string.)
* @param username the users username, or null if it is not known.
* @return if the operation completed successfully
* @throws NullPointerException if uuid or username is null
* @throws NullPointerException if uuid is null
*/
CompletableFuture<Boolean> loadUser(UUID uuid, String username);
/**
* Loads a user's data from the main storage into the plugins local storage.
*
* @param uuid the uuid of the user to load
* @return if the operation completed successfully
* @throws NullPointerException if uuid is null
*/
default CompletableFuture<Boolean> loadUser(UUID uuid) {
return loadUser(uuid, null);
}
/**
* Saves a user object back to storage. You should call this after you make any changes to a user.
*
@@ -41,7 +41,7 @@ public interface Track {
/**
* Gets an ordered list of the groups on this track
*
* <p> Index 0 is the first/lowest group in (or start of) the track
* <p>Index 0 is the first/lowest group in (or start of) the track</p>
*
* @return an ordered {@link List} of the groups on this track
*/
@@ -25,8 +25,8 @@ package me.lucko.luckperms.api;
/**
* Represents a permission setting.
*
* <p> Consider a value of {@link #FALSE} to be a "negated" setting, and a value of {@link #UNDEFINED} to be a
* non-existent value.
* <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>
*/
public enum Tristate {
@@ -63,7 +63,8 @@ public enum Tristate {
/**
* Returns the value of the Tristate as a boolean.
* <p> A value of {@link #UNDEFINED} converts to false.
*
* <p>A value of {@link #UNDEFINED} converts to false.</p>
*
* @return a boolean representation of the Tristate.
*/
@@ -23,6 +23,7 @@
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;
@@ -43,21 +44,24 @@ public interface User extends PermissionHolder {
UUID getUuid();
/**
* Gets the users username
* Gets the users username, or null if no username is associated with this user
*
* @return the Users Username
* @return the users username
*/
String getName();
/**
* Gets the users primary group
* Gets the users current primary group.
*
* <p>The result of this method depends on which method is configured for primary group calculation. It may not
* be the same as any value set through {@link #setPrimaryGroup(String)}.</p>
*
* @return the users primary group
*/
String getPrimaryGroup();
/**
* Sets a users primary group
* 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
@@ -69,35 +73,59 @@ public interface User extends PermissionHolder {
/**
* Refresh and re-assign the users permissions.
*
* <p> This request is not buffered, and the refresh call will be ran directly. This should ideally be called on
* an asynchronous thread.
* <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();
/**
* Gets the user's {@link UserData} cache.
*
* @return the users cached data.
* @since 3.2
*/
UserData getCachedData();
/**
* Check to see if the user is a direct member of a group
*
* @param group The group to check membership of
* @return true if the user is a member of the group
* @throws NullPointerException if the group is null
* @throws IllegalStateException if the group instance was not obtained from LuckPerms.
*/
boolean isInGroup(Group group);
/**
* Check to see if the user is a direct member of a group in a specific context
*
* @param group the group to check membership of
* @param contextSet the context set to filter by
* @return true if the user is a member of the group
* @throws IllegalStateException if the group instance was not obtained from LuckPerms.
* @since 3.2
*/
boolean isInGroup(Group group, ContextSet contextSet);
/**
* Gets the user's {@link UserData} cache, if they have one setup.
*
* @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
*/
@Deprecated
Optional<UserData> getUserDataCache();
/**
* Sets up the users data cache, if the don't have one setup already.
*
* @since 2.17
* @deprecated in version 3.2, as this cache is now always loaded.
*/
@Deprecated
void setupDataCache();
/**
* Check to see if the user is a member of a group
*
* @param group The group to check membership of
* @return true if the user is a member of the group
* @throws NullPointerException if the group is null
*/
boolean isInGroup(Group group);
/**
* Check to see if a user is a member of a group on a specific server
*
@@ -106,7 +134,9 @@ public interface User extends PermissionHolder {
* @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(Group group, String server);
/**
@@ -118,7 +148,9 @@ public interface User extends PermissionHolder {
* @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(Group group, String server, String world);
/**
@@ -128,7 +160,9 @@ public interface User extends PermissionHolder {
* @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(Group group) throws ObjectAlreadyHasException;
/**
@@ -140,7 +174,9 @@ public interface User extends PermissionHolder {
* @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(Group group, String server) throws ObjectAlreadyHasException;
/**
@@ -153,7 +189,9 @@ public interface User extends PermissionHolder {
* @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(Group group, String server, String world) throws ObjectAlreadyHasException;
/**
@@ -165,7 +203,9 @@ public interface User extends PermissionHolder {
* @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(Group group, long expireAt) throws ObjectAlreadyHasException;
/**
@@ -178,7 +218,9 @@ public interface User extends PermissionHolder {
* @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(Group group, String server, long expireAt) throws ObjectAlreadyHasException;
/**
@@ -192,7 +234,9 @@ public interface User extends PermissionHolder {
* @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(Group group, String server, String world, long expireAt) throws ObjectAlreadyHasException;
/**
@@ -202,7 +246,9 @@ public interface User extends PermissionHolder {
* @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(Group group) throws ObjectLacksException;
/**
@@ -213,7 +259,9 @@ public interface User extends PermissionHolder {
* @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(Group group, boolean temporary) throws ObjectLacksException;
/**
@@ -225,7 +273,9 @@ public interface User extends PermissionHolder {
* @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(Group group, String server) throws ObjectLacksException;
/**
@@ -238,7 +288,9 @@ public interface User extends PermissionHolder {
* @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(Group group, String server, String world) throws ObjectLacksException;
/**
@@ -251,7 +303,9 @@ public interface User extends PermissionHolder {
* @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(Group group, String server, boolean temporary) throws ObjectLacksException;
/**
@@ -265,14 +319,18 @@ public interface User extends PermissionHolder {
* @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(Group group, String server, 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
List<String> getGroupNames();
/**
@@ -283,7 +341,9 @@ public interface User extends PermissionHolder {
* @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
List<String> getLocalGroups(String server, String world);
/**
@@ -293,7 +353,9 @@ public interface User extends PermissionHolder {
* @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
List<String> getLocalGroups(String server);
}
@@ -27,15 +27,15 @@ import java.util.UUID;
/**
* 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
* <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.
* instead check the datastore cache, to get a UUID for a user that is consistent across an entire network.</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>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> THIS IS ONLY EFFECTIVE FOR ONLINE PLAYERS. USE THE DATASTORE METHODS FOR OFFLINE PLAYERS.
* <p><strong>This is only effective for online players. Use {@link Storage#getUUID(String)} for offline players.</strong></p>
*/
public interface UuidCache {
@@ -0,0 +1,110 @@
/*
* 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.caching;
import com.google.common.base.Preconditions;
import me.lucko.luckperms.api.Contexts;
import me.lucko.luckperms.api.metastacking.MetaStackDefinition;
/**
* 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.
*
* @since 3.2
*/
public final class MetaContexts {
/**
* Creates a new meta contexts instance
*
* @param contexts the standard contexts for the query
* @param prefixStackDefinition the prefix stack definition to be used
* @param suffixStackDefinition the suffix stack definition to be used
* @return the new instance
*/
public static MetaContexts of(Contexts contexts, MetaStackDefinition prefixStackDefinition, MetaStackDefinition suffixStackDefinition) {
return new MetaContexts(contexts, prefixStackDefinition, suffixStackDefinition);
}
private final Contexts contexts;
private final MetaStackDefinition prefixStackDefinition;
private final MetaStackDefinition suffixStackDefinition;
/**
* Creates a new meta contexts instance
*
* @param contexts the standard contexts for the query
* @param prefixStackDefinition the prefix stack definition to be used
* @param suffixStackDefinition the suffix stack definition to be used
*/
public MetaContexts(Contexts contexts, MetaStackDefinition prefixStackDefinition, MetaStackDefinition suffixStackDefinition) {
this.contexts = Preconditions.checkNotNull(contexts, "contexts");
this.prefixStackDefinition = Preconditions.checkNotNull(prefixStackDefinition, "prefixStackDefinition");
this.suffixStackDefinition = Preconditions.checkNotNull(suffixStackDefinition, "suffixStackDefinition");
}
public Contexts getContexts() {
return this.contexts;
}
public MetaStackDefinition getPrefixStackDefinition() {
return this.prefixStackDefinition;
}
public MetaStackDefinition getSuffixStackDefinition() {
return this.suffixStackDefinition;
}
@Override
public String toString() {
return "MetaContexts(" +
"contexts=" + this.getContexts() + ", " +
"prefixStackDefinition=" + this.getPrefixStackDefinition() + ", " +
"suffixStackDefinition=" + this.getSuffixStackDefinition() +
")";
}
@Override
public boolean equals(Object o) {
if (o == this) return true;
if (!(o instanceof MetaContexts)) return false;
final MetaContexts other = (MetaContexts) o;
return this.getContexts().equals(other.getContexts()) &&
this.getPrefixStackDefinition().equals(other.getPrefixStackDefinition()) &&
this.getSuffixStackDefinition().equals(other.getSuffixStackDefinition());
}
@Override
public int hashCode() {
final int PRIME = 59;
int result = 1;
result = result * PRIME + this.getContexts().hashCode();
result = result * PRIME + this.getPrefixStackDefinition().hashCode();
result = result * PRIME + this.getSuffixStackDefinition().hashCode();
return result;
}
}
@@ -22,6 +22,8 @@
package me.lucko.luckperms.api.caching;
import me.lucko.luckperms.api.metastacking.MetaStackDefinition;
import java.util.Map;
import java.util.SortedMap;
@@ -69,4 +71,20 @@ public interface MetaData {
*/
String getSuffix();
/**
* Gets the definition used for the prefix stack
*
* @return the definition used for the prefix stack
* @since 3.2
*/
MetaStackDefinition getPrefixStackDefinition();
/**
* Gets the definition used for the suffix stack
*
* @return the definition used for the suffix stack
* @since 3.2
*/
MetaStackDefinition getSuffixStackDefinition();
}
@@ -45,7 +45,7 @@ public interface PermissionData {
/**
* Invalidates the underlying permission calculator cache.
*
* <p> Can be called to allow for an update in defaults.
* <p>Can be called to allow for an update in defaults.</p>
*/
void invalidateCache();
@@ -30,7 +30,11 @@ import java.util.Set;
* Holds cached permission and meta lookup data for a {@link me.lucko.luckperms.api.User}.
*
* <p>Data is only likely to be available for online users. All calls will account for inheritance, as well as any
* default data provided by the platform. This calls are heavily cached and are therefore fast.
* default data provided by the platform. This calls are heavily cached and are therefore fast.</p>
*
* <p>For meta, both methods accepting {@link Contexts} and {@link MetaContexts} are provided. The only difference is that
* the latter allows you to define how the meta stack should be structured internally. Where {@link Contexts} are passed, the
* values from the configuration are used.</p>
*
* @since 2.13
*/
@@ -39,7 +43,7 @@ public interface UserData {
/**
* Gets PermissionData from the cache, given a specified context.
*
* <p> If the data is not cached, it is calculated. Therefore, this call could be costly.
* <p>If the data is not cached, it is calculated. Therefore, this call could be costly.</p>
*
* @param contexts the contexts to get the permission data in
* @return a permission data instance
@@ -50,7 +54,19 @@ public interface UserData {
/**
* Gets MetaData from the cache, given a specified context.
*
* <p> If the data is not cached, it is calculated. Therefore, this call could be costly.
* <p>If the data is not cached, it is calculated. Therefore, this call could be costly.</p>
*
* @param contexts the contexts to get the permission data in
* @return a meta data instance
* @throws NullPointerException if contexts is null
* @since 3.2
*/
MetaData getMetaData(MetaContexts contexts);
/**
* Gets MetaData from the cache, given a specified context.
*
* <p>If the data is not cached, it is calculated. Therefore, this call could be costly.</p>
*
* @param contexts the contexts to get the permission data in
* @return a meta data instance
@@ -67,6 +83,16 @@ public interface UserData {
*/
PermissionData calculatePermissions(Contexts contexts);
/**
* Calculates meta data, bypassing the cache.
*
* @param contexts the contexts to get meta data in
* @return a meta data instance
* @throws NullPointerException if contexts is null
* @since 3.2
*/
MetaData calculateMeta(MetaContexts contexts);
/**
* Calculates meta data, bypassing the cache.
*
@@ -79,8 +105,8 @@ public interface UserData {
/**
* Calculates permission data and stores it in the cache.
*
* <p> If there is already data cached for the given contexts, and if the resultant output is different,
* the cached value is updated.
* <p>If there is already data cached for the given contexts, and if the resultant output is different,
* the cached value is updated.</p>
*
* @param contexts the contexts to recalculate in.
* @throws NullPointerException if contexts is null
@@ -90,8 +116,20 @@ public interface UserData {
/**
* Calculates meta data and stores it in the cache.
*
* <p> If there is already data cached for the given contexts, and if the resultant output is different,
* the cached value is updated.
* <p>If there is already data cached for the given contexts, and if the resultant output is different,
* the cached value is updated.</p>
*
* @param contexts the contexts to recalculate in.
* @throws NullPointerException if contexts is null
* @since 3.2
*/
void recalculateMeta(MetaContexts contexts);
/**
* Calculates meta data and stores it in the cache.
*
* <p>If there is already data cached for the given contexts, and if the resultant output is different,
* the cached value is updated.</p>
*
* @param contexts the contexts to recalculate in.
* @throws NullPointerException if contexts is null
@@ -119,7 +157,7 @@ public interface UserData {
/**
* Ensures that PermissionData and MetaData is cached for a context.
*
* <p> If the cache does not contain any data for the context, it will be calculated and saved.
* <p>If the cache does not contain any data for the context, it will be calculated and saved.</p>
*
* @param contexts the contexts to pre-calculate for
* @throws NullPointerException if contexts is null
@@ -128,7 +166,8 @@ public interface UserData {
/**
* Invalidates all of the underlying Permission calculators.
* Can be called to allow for an update in defaults.
*
* <p>Can be called to allow for an update in defaults.</p>
*/
void invalidatePermissionCalculators();
@@ -22,14 +22,11 @@
package me.lucko.luckperms.api.context;
import java.util.Map;
/**
* Calculates whether contexts are applicable to {@link T}
*
* <p>Somewhat inspired by the system used on Sponge.
*
* @param <T> the subject type. Is ALWAYS the player class of the platform.
* @since 2.13
*/
public interface ContextCalculator<T> {
@@ -43,13 +40,4 @@ public interface ContextCalculator<T> {
*/
MutableContextSet giveApplicableContext(T subject, MutableContextSet accumulator);
/**
* Checks to see if a context is applicable to a subject
*
* @param subject the subject to check against
* @param context the context to check for
* @return true if met, or false if not. If this calculator does not calculate the given context, return false.
*/
boolean isContextApplicable(T subject, Map.Entry<String, String> context);
}
@@ -25,18 +25,20 @@ package me.lucko.luckperms.api.context;
import com.google.common.collect.Multimap;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
/**
* Holds contexts.
* Implementations may be either mutable or immutable.
* Holder of contexts.
*
* <p>Implementations may be either mutable or immutable.</p>
*
* @since 2.13
*/
public interface ContextSet {
/**
* Make a singleton ImmutableContextSet from a context pair
* Creates an ImmutableContextSet from a context pair
*
* @param key the key
* @param value the value
@@ -48,7 +50,22 @@ public interface ContextSet {
}
/**
* Creates a ImmutableContextSet from an existing map
* Creates an ImmutableContextSet from two context pairs
*
* @param key1 the first key
* @param value1 the first value
* @param key2 the second key
* @param value2 the second value
* @return a new ImmutableContextSet containing the two pairs
* @throws NullPointerException if any of the keys or values are null
* @since 3.1
*/
static ImmutableContextSet of(String key1, String value1, String key2, String value2) {
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
@@ -59,7 +76,7 @@ public interface ContextSet {
}
/**
* Creates a ImmutableContextSet from an existing iterable of Map Entries
* Creates an ImmutableContextSet from an existing iterable of Map Entries
*
* @param iterable the iterable to copy from
* @return a new ImmutableContextSet representing the pairs in the iterable
@@ -70,7 +87,7 @@ public interface ContextSet {
}
/**
* Creates a ImmutableContextSet from an existing multimap
* Creates an ImmutableContextSet from an existing multimap
*
* @param multimap the multimap to copy from
* @return a new ImmutableContextSet representing the pairs in the multimap
@@ -82,7 +99,7 @@ public interface ContextSet {
}
/**
* Creates a new ImmutableContextSet from an existing set.
* Creates an new ImmutableContextSet from an existing set.
* Only really useful for converting between mutable and immutable types.
*
* @param contextSet the context set to copy from
@@ -167,6 +184,17 @@ public interface ContextSet {
*/
Set<String> getValues(String key);
/**
* Returns any value from this set matching the key, if present.
*
* @param key the key to find values for
* @return an optional containing any match
* @since 3.1
*/
default Optional<String> getAnyValue(String key) {
return getValues(key).stream().findAny();
}
/**
* Check if thr set contains a given key mapped to a given value
*
@@ -187,6 +215,35 @@ public interface ContextSet {
*/
boolean hasIgnoreCase(String key, String value);
/**
* Checks to see if all entries in this context set are also included in another set.
*
* @param other the other set to check
* @return true if all entries in this set are also in the other set
* @since 3.1
*/
default boolean isSatisfiedBy(ContextSet other) {
if (this.isEmpty()) {
// this is empty, so is therefore always satisfied.
return true;
} else if (other.isEmpty()) {
// this set isn't empty, but the other one is
return false;
} else if (this.size() > other.size()) {
// this set has more unique entries than the other set, so there's no way this can be satisfied.
return false;
} 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;
}
}
return true;
}
}
/**
* Check if the set is empty
*
@@ -23,9 +23,10 @@
package me.lucko.luckperms.api.context;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultimap;
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;
@@ -37,10 +38,10 @@ import java.util.Set;
* @since 2.16
*/
public final class ImmutableContextSet implements ContextSet {
private static final ImmutableContextSet EMPTY = new ImmutableContextSet(ImmutableMultimap.of());
private static final ImmutableContextSet EMPTY = new ImmutableContextSet(ImmutableSetMultimap.of());
/**
* Make a singleton ImmutableContextSet from a context pair
* Creates an ImmutableContextSet from a context pair
*
* @param key the key
* @param value the value
@@ -55,11 +56,39 @@ public final class ImmutableContextSet implements ContextSet {
throw new NullPointerException("value");
}
return new ImmutableContextSet(ImmutableMultimap.of(key.toLowerCase(), value));
return new ImmutableContextSet(ImmutableSetMultimap.of(key.toLowerCase(), value));
}
/**
* Creates a ImmutableContextSet from an existing map
* Creates an ImmutableContextSet from two context pairs
*
* @param key1 the first key
* @param value1 the first value
* @param key2 the second key
* @param value2 the second value
* @return a new ImmutableContextSet containing the two pairs
* @throws NullPointerException if any of the keys or values are null
* @since 3.1
*/
public static ImmutableContextSet of(String key1, String value1, String key2, String value2) {
if (key1 == null) {
throw new NullPointerException("key1");
}
if (value1 == null) {
throw new NullPointerException("value1");
}
if (key2 == null) {
throw new NullPointerException("key2");
}
if (value2 == null) {
throw new NullPointerException("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
@@ -70,16 +99,16 @@ public final class ImmutableContextSet implements ContextSet {
throw new NullPointerException("map");
}
ImmutableMultimap.Builder<String, String> b = ImmutableMultimap.builder();
ImmutableSetMultimap.Builder<String, String> b = ImmutableSetMultimap.builder();
for (Map.Entry<String, String> e : map.entrySet()) {
b.put(e.getKey(), e.getValue());
b.put(e.getKey().toLowerCase(), e.getValue());
}
return new ImmutableContextSet(b.build());
}
/**
* Creates a ImmutableContextSet from an existing iterable of Map Entries
* Creates an ImmutableContextSet from an existing iterable of Map Entries
*
* @param iterable the iterable to copy from
* @return a new ImmutableContextSet representing the pairs in the iterable
@@ -94,7 +123,7 @@ public final class ImmutableContextSet implements ContextSet {
}
/**
* Creates a ImmutableContextSet from an existing multimap
* Creates an ImmutableContextSet from an existing multimap
*
* @param multimap the multimap to copy from
* @return a new ImmutableContextSet representing the pairs in the multimap
@@ -121,7 +150,7 @@ public final class ImmutableContextSet implements ContextSet {
}
/**
* Creates a new empty ContextSet.
* Creates an new empty ContextSet.
*
* @return a new ContextSet
*/
@@ -129,10 +158,10 @@ public final class ImmutableContextSet implements ContextSet {
return EMPTY;
}
private final Multimap<String, String> map;
private final SetMultimap<String, String> map;
ImmutableContextSet(Multimap<String, String> contexts) {
this.map = ImmutableMultimap.copyOf(contexts);
this.map = ImmutableSetMultimap.copyOf(contexts);
}
@Override
@@ -25,8 +25,10 @@ package me.lucko.luckperms.api.context;
import com.google.common.collect.HashMultimap;
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.Multimaps;
import com.google.common.collect.SetMultimap;
import java.util.Collection;
import java.util.Map;
@@ -60,6 +62,36 @@ public final class MutableContextSet implements ContextSet {
return set;
}
/**
* Makes a MutableContextSet from two context pairs
*
* @param key1 the first key
* @param value1 the first value
* @param key2 the second key
* @param value2 the second value
* @return a new MutableContextSet containing the two pairs
* @throws NullPointerException if any of the keys or values are null
* @since 3.1
*/
public static MutableContextSet of(String key1, String value1, String key2, String value2) {
if (key1 == null) {
throw new NullPointerException("key1");
}
if (value1 == null) {
throw new NullPointerException("value1");
}
if (key2 == null) {
throw new NullPointerException("key2");
}
if (value2 == null) {
throw new NullPointerException("value2");
}
MutableContextSet ret = singleton(key1, value1);
ret.add(key2, value2);
return ret;
}
/**
* Creates a MutableContextSet from an existing map
*
@@ -136,14 +168,14 @@ public final class MutableContextSet implements ContextSet {
return new MutableContextSet();
}
private final Multimap<String, String> map;
private final SetMultimap<String, String> map;
public MutableContextSet() {
this.map = Multimaps.synchronizedMultimap(HashMultimap.create());
this.map = Multimaps.synchronizedSetMultimap(HashMultimap.create());
}
private MutableContextSet(Multimap<String, String> contexts) {
this.map = Multimaps.synchronizedMultimap(HashMultimap.create(contexts));
this.map = Multimaps.synchronizedSetMultimap(HashMultimap.create(contexts));
}
@Override
@@ -178,7 +210,7 @@ public final class MutableContextSet implements ContextSet {
@Override
public Multimap<String, String> toMultimap() {
return map;
return ImmutableSetMultimap.copyOf(map);
}
@Override
@@ -284,7 +316,7 @@ public final class MutableContextSet implements ContextSet {
*/
public void addAll(Iterable<? extends Map.Entry<String, String>> iterable) {
if (iterable == null) {
throw new NullPointerException("contexts");
throw new NullPointerException("iterable");
}
for (Map.Entry<String, String> e : iterable) {
@@ -300,7 +332,7 @@ public final class MutableContextSet implements ContextSet {
*/
public void addAll(Map<String, String> map) {
if (map == null) {
throw new NullPointerException("contexts");
throw new NullPointerException("map");
}
addAll(map.entrySet());
}
@@ -334,7 +366,7 @@ public final class MutableContextSet implements ContextSet {
throw new NullPointerException("value");
}
map.entries().removeIf(entry -> entry.getKey().equalsIgnoreCase(key) && entry.getValue().equals(value));
map.entries().removeIf(entry -> entry.getKey().equals(key) && entry.getValue().equals(value));
}
/**
@@ -40,6 +40,6 @@ public enum CreationCause {
/**
* The creation was caused by a LuckPerms internal
*/
INTERNAL;
INTERNAL
}
@@ -40,6 +40,6 @@ public enum DeletionCause {
/**
* The deletion was caused by a LuckPerms internal
*/
INTERNAL;
INTERNAL
}
@@ -35,6 +35,6 @@ public enum TrackAction {
/**
* The user was demoted down a track
*/
DEMOTION;
DEMOTION
}
@@ -0,0 +1,71 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.api.metastacking;
import java.util.List;
/**
* Represents a meta stack model, consisting of a chain of elements, separated by spacers.
*
* <p>The resultant string is constructed as:
* [start spacer] [element] [middle spacer] [element] [middle spacer] [element] [end spacer]</p>
*
* <p>Definitions can be passed to a users UserData instance using MetaContexts, and the result of this stack can be
* retrieved from the returned MetaData instance.</p>
*
* @since 2.3
*/
public interface MetaStackDefinition {
/**
* Gets an immutable list of the elements in this stack definition
*
* @return the elements in this stack
*/
List<MetaStackElement> getElements();
/**
* Gets the spacer string added before any stack elements
*
* @return the start spacer
*/
String getStartSpacer();
/**
* Gets the spacer added between stack elements
*
* @return the middle spacer
*/
String getMiddleSpacer();
/**
* Gets the spacer added after any stack elements
*
* @return the end spacer
*/
String getEndSpacer();
}
@@ -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.api.metastacking;
import me.lucko.luckperms.api.ChatMetaType;
import me.lucko.luckperms.api.LocalizedNode;
import me.lucko.luckperms.api.Node;
import java.util.Map;
/**
* Represents an element within a {@link MetaStackDefinition}.
*
* @since 3.2
*/
public interface MetaStackElement {
/**
* Returns if the given node should be accumulated onto the stack.
*
* <p>The element being considered can be retrieved using {@link ChatMetaType#getEntry(Node)}.</p>
*
* @param node the node being considered
* @param type the type of entry being accumulated
* @param current the current value being used. If this returns true, the current value will be replaced by this entry
* @return true if the node should be accumulated into this element, replacing the current value
*/
boolean shouldAccumulate(LocalizedNode node, ChatMetaType type, Map.Entry<Integer, String> current);
}
@@ -0,0 +1,67 @@
/*
* 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.metastacking;
import java.util.List;
import java.util.Optional;
/**
* Factory to create meta stack elements and definitions.
*
* @since 3.2
*/
public interface MetaStackFactory {
/**
* Parses a standard {@link MetaStackElement} from string, using the pre-defined elements in the plugin.
*
* @param definition the definition
* @return the parsed element, if present
*/
Optional<MetaStackElement> fromString(String definition);
/**
* Parses a list of {@link MetaStackElement}s from string, using the pre-defined elements in the plugin.
*
* <p>If an element cannot be parsed, it will not be included in the resultant list.</p>
*
* @param definitions the definition strings
* @return a list of parsed elements
*/
List<MetaStackElement> fromStrings(List<String> definitions);
/**
* Creates a new {@link MetaStackDefinition} with the given properties.
*
* @param elements the elements to be included in the stack.
* @param startSpacer the spacer to be included at the start of the stacks output
* @param middleSpacer the spacer to be included between stack elements
* @param endSpacer the spacer to be included at the end of the stacks output
* @return the new stack definition instance
*/
MetaStackDefinition createDefinition(List<MetaStackElement> elements, String startSpacer, String middleSpacer, String endSpacer);
}
+15 -17
View File
@@ -5,32 +5,20 @@
<parent>
<artifactId>luckperms</artifactId>
<groupId>me.lucko.luckperms</groupId>
<version>3.0-SNAPSHOT</version>
<version>3.2-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>luckperms-bukkit-legacy</artifactId>
<build>
<defaultGoal>clean package</defaultGoal>
<finalName>LuckPerms-Bukkit-Legacy-${release.version}.${patch.version}</finalName>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
<resource>
<directory>../</directory>
<filtering>false</filtering>
<includes>
<include>LICENSE.txt</include>
</includes>
</resource>
</resources>
<finalName>LuckPerms-Bukkit-Legacy-${full.version}</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<version>${compiler.version}</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
@@ -39,7 +27,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.4.3</version>
<version>${shade.version}</version>
<executions>
<execution>
<phase>package</phase>
@@ -77,6 +65,16 @@
<artifactId>luckperms-bukkit</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
<exclusions>
<exclusion>
<groupId>me.lucko.luckperms</groupId>
<artifactId>luckperms-common</artifactId>
</exclusion>
<exclusion>
<groupId>me.lucko.luckperms</groupId>
<artifactId>luckperms-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Shade gson and guava, as they are not included in earlier Bukkit releases -->
-61
View File
@@ -1,61 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>luckperms</artifactId>
<groupId>me.lucko.luckperms</groupId>
<version>3.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>luckperms-bukkit-placeholders</artifactId>
<build>
<finalName>LuckPermsPlaceholderExpansion</finalName>
<resources>
<resource>
<directory>../</directory>
<filtering>false</filtering>
<includes>
<include>LICENSE.txt</include>
</includes>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<!-- LuckPerms API -->
<dependency>
<groupId>me.lucko.luckperms</groupId>
<artifactId>luckperms-api</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<!-- BukkitAPI -->
<dependency>
<groupId>org.bukkit</groupId>
<artifactId>bukkit</artifactId>
<version>1.8.8-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<!-- PlaceholderAPI -->
<dependency>
<groupId>me.clip</groupId>
<artifactId>placeholderapi</artifactId>
<version>2.2.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
@@ -1,220 +0,0 @@
/*
* Copyright (c) 2016 Lucko (Luck) <luck@lucko.me>
*
* 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.placeholders;
import me.clip.placeholderapi.PlaceholderAPIPlugin;
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
import me.clip.placeholderapi.util.TimeUtil;
import me.lucko.luckperms.api.Contexts;
import me.lucko.luckperms.api.LuckPermsApi;
import me.lucko.luckperms.api.Node;
import me.lucko.luckperms.api.Track;
import me.lucko.luckperms.api.User;
import me.lucko.luckperms.api.caching.UserData;
import me.lucko.luckperms.api.context.MutableContextSet;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import java.util.Optional;
import java.util.stream.Collectors;
/*
* PlaceholderAPI Expansion for LuckPerms, implemented using the LuckPerms API.
*
* Placeholders:
* - group_name
* - groups
* - has_permission_<node>
* - inherits_permission_<node>
* - in_group_<name>
* - inherits_group_<name>
* - on_track_<track>
* - expiry_time_<node>
* - group_expiry_time_<group>
* - prefix
* - suffix
* - meta_<node>
*/
public class LuckPermsPlaceholderExpansion extends PlaceholderExpansion {
private static final String IDENTIFIER = "luckperms";
private static final String PLUGIN_NAME = "LuckPerms";
private static final String AUTHOR = "Luck";
private static String formatBoolean(boolean b) {
return b ? PlaceholderAPIPlugin.booleanTrue() : PlaceholderAPIPlugin.booleanFalse();
}
private LuckPermsApi api = null;
@Override
public boolean canRegister() {
return Bukkit.getServicesManager().isProvidedFor(LuckPermsApi.class);
}
@Override
public boolean register() {
if (!canRegister()) {
return false;
}
api = Bukkit.getServicesManager().getRegistration(LuckPermsApi.class).getProvider();
return super.register();
}
@Override
public String getVersion() {
return "2.13";
}
private Contexts makeContexts(Player player) {
MutableContextSet contextSet = new MutableContextSet();
contextSet.add("server", api.getConfiguration().getVaultServer());
contextSet.add("world", player.getWorld().getName());// ignore world?
return Contexts.of(contextSet.makeImmutable(), api.getConfiguration().getVaultIncludeGlobal(), true, true, true, true, player.isOp());
}
@Override
public String onPlaceholderRequest(Player player, String identifier) {
if (player == null || api == null) {
return "";
}
Optional<User> u = api.getUserSafe(api.getUuidCache().getUUID(player.getUniqueId()));
if (!u.isPresent()) {
return "";
}
final User user = u.get();
Optional<UserData> d = user.getUserDataCache();
if (!d.isPresent()) {
return "";
}
final UserData data = d.get();
identifier = identifier.toLowerCase();
if (identifier.equalsIgnoreCase("group_name")) {
return user.getPrimaryGroup();
}
if (identifier.equalsIgnoreCase("groups")) {
return user.getGroupNames().stream().collect(Collectors.joining(", "));
}
if (identifier.startsWith("has_permission_") && identifier.length() > "has_permission_".length()) {
String node = identifier.substring("has_permission_".length());
return formatBoolean(user.hasPermission(node, true));
}
if (identifier.startsWith("inherits_permission_") && identifier.length() > "inherits_permission_".length()) {
String node = identifier.substring("inherits_permission_".length());
return formatBoolean(data.getPermissionData(makeContexts(player)).getPermissionValue(node).asBoolean());
}
if (identifier.startsWith("in_group_") && identifier.length() > "in_group_".length()) {
String groupName = identifier.substring("in_group_".length());
return formatBoolean(user.getGroupNames().contains(groupName));
}
if (identifier.startsWith("inherits_group_") && identifier.length() > "inherits_group_".length()) {
String groupName = identifier.substring("inherits_group_".length());
return formatBoolean(data.getPermissionData(makeContexts(player)).getPermissionValue("group." + groupName).asBoolean());
}
if (identifier.startsWith("on_track_") && identifier.length() > "on_track_".length()) {
String trackName = identifier.substring("on_track_".length());
Optional<Track> track = api.getTrackSafe(trackName);
if (!track.isPresent()) return "";
return formatBoolean(track.get().containsGroup(user.getPrimaryGroup()));
}
if (identifier.startsWith("expiry_time_") && identifier.length() > "expiry_time_".length()) {
String node = identifier.substring("expiry_time_".length());
long currentTime = System.currentTimeMillis() / 1000L;
for (Node n : user.getTemporaryPermissionNodes()) {
if (n.toSerializedNode().equalsIgnoreCase(node)) {
return TimeUtil.getTime((int) (n.getExpiryUnixTime() - currentTime));
}
}
return "";
}
if (identifier.startsWith("group_expiry_time_") && identifier.length() > "group_expiry_time_".length()) {
String node = "group." + identifier.substring("group_expiry_time_".length());
long currentTime = System.currentTimeMillis() / 1000L;
for (Node n : user.getTemporaryPermissionNodes()) {
if (n.getPermission().equalsIgnoreCase(node)) {
return TimeUtil.getTime((int) (n.getExpiryUnixTime() - currentTime));
}
}
return "";
}
if (identifier.equalsIgnoreCase("prefix")) {
String prefix = data.calculateMeta(makeContexts(player)).getPrefix();
if (prefix == null) {
prefix = "";
}
return prefix;
}
if (identifier.equalsIgnoreCase("suffix")) {
String suffix = data.calculateMeta(makeContexts(player)).getSuffix();
if (suffix == null) {
suffix = "";
}
return suffix;
}
if (identifier.startsWith("meta_") && identifier.length() > "meta_".length()) {
String node = identifier.substring("meta_".length());
String meta = data.getMetaData(makeContexts(player)).getMeta().get(node);
if (meta == null) {
meta = "";
}
return meta;
}
return null;
}
@Override
public String getIdentifier() {
return IDENTIFIER;
}
@Override
public String getPlugin() {
return PLUGIN_NAME;
}
@Override
public String getAuthor() {
return AUTHOR;
}
}
+46 -32
View File
@@ -5,7 +5,7 @@
<parent>
<artifactId>luckperms</artifactId>
<groupId>me.lucko.luckperms</groupId>
<version>3.0-SNAPSHOT</version>
<version>3.2-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -14,26 +14,18 @@
<build>
<defaultGoal>clean package</defaultGoal>
<finalName>LuckPerms-Bukkit-${release.version}.${patch.version}</finalName>
<sourceDirectory>${basedir}/src/main/java</sourceDirectory>
<finalName>LuckPerms-Bukkit-${full.version}</finalName>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
<resource>
<directory>../</directory>
<filtering>false</filtering>
<includes>
<include>LICENSE.txt</include>
</includes>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<version>${compiler.version}</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
@@ -42,7 +34,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.4.3</version>
<version>${shade.version}</version>
<executions>
<execution>
<phase>package</phase>
@@ -66,11 +58,11 @@
</build>
<dependencies>
<!-- BukkitAPI -->
<!-- Spigot API -->
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
<version>1.8.8-R0.1-SNAPSHOT</version>
<version>1.11.2-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<!-- Vault -->
@@ -79,6 +71,12 @@
<artifactId>VaultAPI</artifactId>
<version>1.6</version>
<scope>provided</scope>
<exclusions>
<exclusion>
<groupId>org.bukkit</groupId>
<artifactId>bukkit</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- LuckPerms Common -->
<dependency>
@@ -94,38 +92,33 @@
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
<!-- LilyPad - used as a messaging service -->
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<scope>provided</scope>
</dependency>
<!-- LilyPad -->
<dependency>
<groupId>lilypad.client.connect</groupId>
<artifactId>api</artifactId>
<version>0.0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<!-- HikariCP - needed as source -->
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>2.5.1</version>
<scope>provided</scope>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.10</version>
<scope>provided</scope>
</dependency>
<!-- **************Migration Plugins************** -->
<!-- ************** Migration Plugins ************** -->
<!-- zPermissions -->
<dependency>
<groupId>org.tyrannyofheaven.bukkit</groupId>
<artifactId>zPermissions</artifactId>
<version>1.3</version>
<scope>provided</scope>
</dependency>
<!-- PermissionsEx -->
<dependency>
<groupId>ru.tehkode</groupId>
<artifactId>PermissionsEx</artifactId>
<version>1.23</version>
<version>1.23.5</version>
<scope>provided</scope>
<exclusions>
<exclusion>
@@ -136,20 +129,41 @@
<groupId>net.gravitydevelopment.updater</groupId>
<artifactId>updater</artifactId>
</exclusion>
<exclusion>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
</exclusion>
<exclusion>
<groupId>com.mojang</groupId>
<artifactId>AccountsClient</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- PowerfulPerms -->
<dependency>
<groupId>com.github.cheesesoftware</groupId>
<artifactId>PowerfulPermsAPI</artifactId>
<version>1.0.1</version>
<version>1.4.0</version>
<scope>provided</scope>
<exclusions>
<exclusion>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</exclusion>
<exclusion>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- GroupManager -->
<dependency>
<groupId>org.anjocaido</groupId>
<artifactId>GroupManager</artifactId>
<version>1.4</version>
<scope>provided</scope>
</dependency>
<!-- bPermissions -->
<dependency>
<groupId>de.bananaco</groupId>
<artifactId>bpermissions-api</artifactId>
@@ -1,5 +1,8 @@
/*
* Copyright (c) 2016 Lucko (Luck) <luck@lucko.me>
* 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
@@ -49,23 +52,34 @@ public class BukkitCalculatorFactory extends AbstractCalculatorFactory {
@Override
public PermissionCalculator build(Contexts contexts, User user) {
UUID uuid = plugin.getUuidCache().getExternalUUID(user.getUuid());
ImmutableList.Builder<PermissionProcessor> processors = ImmutableList.builder();
processors.add(new MapProcessor());
processors.add(new ChildProcessor(plugin.getChildPermissionProvider()));
processors.add(new AttachmentProcessor(() -> {
LPPermissible permissible = Injector.getPermissible(uuid);
return permissible == null ? null : permissible.getAttachmentPermissions();
}));
if (plugin.getConfiguration().get(ConfigKeys.APPLY_BUKKIT_CHILD_PERMISSIONS)) {
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());
}
if (plugin.getConfiguration().get(ConfigKeys.APPLYING_WILDCARDS)) {
processors.add(new WildcardProcessor());
}
processors.add(new DefaultsProcessor(contexts.isOp(), plugin.getDefaultsProvider()));
return registerCalculator(new PermissionCalculator(plugin, user.getName(), processors.build()));
if (plugin.getConfiguration().get(ConfigKeys.APPLY_BUKKIT_DEFAULT_PERMISSIONS)) {
processors.add(new DefaultsProcessor(contexts.isOp(), plugin.getDefaultsProvider()));
}
return registerCalculator(new PermissionCalculator(plugin, user.getFriendlyName(), processors.build()));
}
}
@@ -1,5 +1,8 @@
/*
* Copyright (c) 2016 Lucko (Luck) <luck@lucko.me>
* 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
@@ -27,7 +30,6 @@ import com.google.common.base.Splitter;
import me.lucko.luckperms.common.commands.CommandManager;
import me.lucko.luckperms.common.commands.utils.Util;
import me.lucko.luckperms.common.constants.Patterns;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
@@ -50,7 +52,7 @@ public class BukkitCommand extends CommandManager implements CommandExecutor, Ta
onCommand(
plugin.getSenderFactory().wrap(sender),
label,
Util.stripQuotes(Splitter.on(Patterns.COMMAND_SEPARATOR).omitEmptyStrings().splitToList(Joiner.on(' ').join(args)))
Util.stripQuotes(Splitter.on(COMMAND_SEPARATOR_PATTERN).omitEmptyStrings().splitToList(Joiner.on(' ').join(args)))
);
return true;
}
@@ -1,5 +1,8 @@
/*
* Copyright (c) 2016 Lucko (Luck) <luck@lucko.me>
* 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
@@ -31,11 +34,11 @@ import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.Set;
@RequiredArgsConstructor
public class BukkitConfig extends AbstractConfiguration {
@@ -57,6 +60,11 @@ public class BukkitConfig extends AbstractConfiguration {
configuration = YamlConfiguration.loadConfiguration(configFile);
}
@Override
public boolean contains(String path) {
return configuration.contains(path);
}
@Override
public String getString(String path, String def) {
return configuration.getString(path, def);
@@ -74,7 +82,8 @@ public class BukkitConfig extends AbstractConfiguration {
@Override
public List<String> getList(String path, List<String> def) {
return Optional.ofNullable(configuration.getStringList(path)).orElse(def);
List<String> ret = configuration.getStringList(path);
return ret == null ? def : ret;
}
@Override
@@ -84,7 +93,8 @@ public class BukkitConfig extends AbstractConfiguration {
return def;
}
return Optional.ofNullable(section.getKeys(false).stream().collect(Collectors.toList())).orElse(def);
Set<String> keys = section.getKeys(false);
return keys == null ? def : new ArrayList<>(keys);
}
@Override
@@ -1,5 +1,8 @@
/*
* Copyright (c) 2016 Lucko (Luck) <luck@lucko.me>
* 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
@@ -22,12 +25,14 @@
package me.lucko.luckperms.bukkit;
import lombok.RequiredArgsConstructor;
import me.lucko.luckperms.bukkit.model.Injector;
import me.lucko.luckperms.bukkit.model.LPPermissible;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.constants.Message;
import me.lucko.luckperms.common.core.model.User;
import me.lucko.luckperms.common.utils.AbstractListener;
import me.lucko.luckperms.common.utils.LoginHelper;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
@@ -44,68 +49,123 @@ import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
class BukkitListener extends AbstractListener implements Listener {
@RequiredArgsConstructor
public class BukkitListener implements Listener {
private final LPBukkitPlugin plugin;
private final Set<UUID> deniedAsyncLogin = Collections.synchronizedSet(new HashSet<>());
private final Set<UUID> deniedLogin = new HashSet<>();
BukkitListener(LPBukkitPlugin plugin) {
super(plugin);
this.plugin = plugin;
}
private final Set<UUID> deniedAsyncLogin = Collections.synchronizedSet(new HashSet<>());
private final Set<UUID> deniedLogin = Collections.synchronizedSet(new HashSet<>());
@EventHandler(priority = EventPriority.LOW)
public void onPlayerPreLogin(AsyncPlayerPreLoginEvent e) {
/* Called when the player first attempts a connection with the server.
Listening on LOW priority to allow plugins to modify username / UUID data here. (auth plugins) */
/* wait for the plugin to enable. because these events are fired async, they can be called before
the plugin has enabled. */
try {
plugin.getEnableLatch().await(30, TimeUnit.SECONDS);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
/* the player was denied entry to the server before this priority.
log this, so we can handle appropriately later. */
if (e.getLoginResult() != AsyncPlayerPreLoginEvent.Result.ALLOWED) {
plugin.getLog().warn("Connection from " + e.getUniqueId() + " was already denied. No permissions data will be loaded.");
deniedAsyncLogin.add(e.getUniqueId());
return;
}
/* 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
this data is loaded. */
if (!plugin.isStarted() || !plugin.getStorage().isAcceptingLogins()) {
// log that the user tried to login, but was denied at this stage.
deniedAsyncLogin.add(e.getUniqueId());
// The datastore is disabled, prevent players from joining the server
e.disallow(AsyncPlayerPreLoginEvent.Result.KICK_OTHER, Message.LOADING_ERROR.toString());
// actually deny the connection.
plugin.getLog().warn("Permissions storage is not loaded yet. Denying connection from: " + e.getUniqueId() + " - " + e.getName());
e.disallow(AsyncPlayerPreLoginEvent.Result.KICK_OTHER, Message.LOADING_ERROR.asString(plugin.getLocaleManager()));
return;
}
// Process login
onAsyncLogin(e.getUniqueId(), e.getName());
/* Actually process the login for the connection.
We do this here to delay the login until the data is ready.
If the login gets cancelled later on, then this will be cleaned up.
This includes:
- loading uuid data
- loading permissions
- creating a user instance in the UserManager for this connection.
- setting up cached data. */
try {
LoginHelper.loadUser(plugin, e.getUniqueId(), e.getName(), false);
} catch (Exception ex) {
ex.printStackTrace();
// there was some error loading data. deny the connection
deniedAsyncLogin.add(e.getUniqueId());
e.disallow(AsyncPlayerPreLoginEvent.Result.KICK_OTHER, Message.LOADING_ERROR.asString(plugin.getLocaleManager()));
}
}
@EventHandler(priority = EventPriority.MONITOR)
public void onPlayerPreLoginMonitor(AsyncPlayerPreLoginEvent e) {
// If they were denied before/at LOW, then don't bother handling here.
/* Listen to see if the event was cancelled after we initially handled the connection
If the connection was cancelled here, we need to do something to clean up the data that was loaded. */
// Check to see if this connection was denied at LOW.
if (deniedAsyncLogin.remove(e.getUniqueId())) {
// This is a problem, as they were denied at low priority, but are now being allowed.
if (e.getLoginResult() == AsyncPlayerPreLoginEvent.Result.ALLOWED) {
plugin.getLog().severe("Player connection was re-allowed for " + e.getUniqueId());
e.disallow(AsyncPlayerPreLoginEvent.Result.KICK_OTHER, "");
}
return;
}
if (plugin.isStarted() && plugin.getStorage().isAcceptingLogins() && e.getLoginResult() != AsyncPlayerPreLoginEvent.Result.ALLOWED) {
// Login event was cancelled by another plugin
onLeave(e.getUniqueId());
// Login event was cancelled by another plugin
if (e.getLoginResult() != AsyncPlayerPreLoginEvent.Result.ALLOWED) {
// Schedule cleanup of this user.
plugin.getUserManager().scheduleUnload(e.getUniqueId());
}
}
@EventHandler(priority = EventPriority.LOW)
public void onPlayerLogin(PlayerLoginEvent e) {
/* Called when the player starts logging into the server.
At this point, the users data should be present and loaded.
Listening on LOW priority to allow plugins to further modify data here. (auth plugins, etc.) */
/* the player was denied entry to the server before this priority.
log this, so we can handle appropriately later. */
if (e.getResult() != PlayerLoginEvent.Result.ALLOWED) {
plugin.getLog().warn("Login from " + e.getPlayer().getUniqueId() + " was denied before an attachment could be injected.");
deniedLogin.add(e.getPlayer().getUniqueId());
return;
}
final Player player = e.getPlayer();
final User user = plugin.getUserManager().get(plugin.getUuidCache().getUUID(player.getUniqueId()));
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. */
if (user == null) {
deniedLogin.add(e.getPlayer().getUniqueId());
// User wasn't loaded for whatever reason.
e.disallow(PlayerLoginEvent.Result.KICK_OTHER, Message.LOADING_ERROR.toString());
plugin.getLog().warn("User " + player.getUniqueId() + " - " + player.getName() + " doesn't have data pre-loaded. - denying login.");
e.disallow(PlayerLoginEvent.Result.KICK_OTHER, Message.LOADING_ERROR.asString(plugin.getLocaleManager()));
return;
}
// User instance is there, now we can inject our custom Permissible into the player.
// Care should be taken at this stage to ensure that async tasks which manipulate bukkit data check that the player is still online.
try {
// Make a new permissible for the user
LPPermissible lpPermissible = new LPPermissible(player, user, plugin);
@@ -119,30 +179,42 @@ class BukkitListener extends AbstractListener implements Listener {
plugin.refreshAutoOp(player);
// We assume all users are not op, but those who are need extra calculation.
if (player.isOp()) {
// We assume all users are not op, but those who are need extra calculation.
plugin.doAsync(() -> user.getUserData().preCalculate(plugin.getPreProcessContexts(true)));
}
}
@EventHandler(priority = EventPriority.MONITOR)
public void onPlayerLoginMonitor(PlayerLoginEvent e) {
if (e.getResult() != PlayerLoginEvent.Result.ALLOWED) {
/* Listen to see if the event was cancelled after we initially handled the login
If the connection was cancelled here, we need to do something to clean up the data that was loaded. */
// If they were denied before/at LOW, then don't bother handling here.
if (deniedLogin.remove(e.getPlayer().getUniqueId())) {
return;
// Check to see if this connection was denied at LOW.
if (deniedLogin.remove(e.getPlayer().getUniqueId())) {
// This is a problem, as they were denied at low priority, but are now being allowed.
if (e.getResult() == PlayerLoginEvent.Result.ALLOWED) {
plugin.getLog().severe("Player connection was re-allowed for " + e.getPlayer().getUniqueId());
e.disallow(PlayerLoginEvent.Result.KICK_OTHER, "");
}
// The player got denied on sync login.
plugin.getServer().getScheduler().runTaskLater(plugin, () -> onLeave(e.getPlayer().getUniqueId()), 20L);
} else {
plugin.refreshAutoOp(e.getPlayer());
return;
}
// Login event was cancelled by another plugin
if (e.getResult() != PlayerLoginEvent.Result.ALLOWED) {
// Schedule cleanup of this user.
plugin.getUserManager().scheduleUnload(e.getPlayer().getUniqueId());
return;
}
// everything is going well. login was processed ok, this is just to refresh auto-op status.
plugin.refreshAutoOp(e.getPlayer());
}
@EventHandler(priority = EventPriority.MONITOR) // Allow other plugins to see data when this event gets called.
// Wait until the last priority to unload, so plugins can still perform permission checks on this event
@EventHandler(priority = EventPriority.MONITOR)
public void onPlayerQuit(PlayerQuitEvent e) {
final Player player = e.getPlayer();
@@ -154,8 +226,8 @@ class BukkitListener extends AbstractListener implements Listener {
player.setOp(false);
}
// Call internal leave handling
onLeave(player.getUniqueId());
// Request that the users data is unloaded.
plugin.getUserManager().scheduleUnload(player.getUniqueId());
}
@EventHandler
@@ -164,7 +236,7 @@ class BukkitListener extends AbstractListener implements Listener {
return;
}
String s = e.getMessage()
String s = e.getMessage().toLowerCase()
.replace("/", "")
.replace("bukkit:", "")
.replace("spigot:", "")
@@ -172,7 +244,7 @@ class BukkitListener extends AbstractListener implements Listener {
if (s.equals("op") || s.startsWith("op ") || s.equals("deop") || s.startsWith("deop ")) {
e.setCancelled(true);
e.getPlayer().sendMessage(Message.OP_DISABLED.toString());
e.getPlayer().sendMessage(Message.OP_DISABLED.asString(plugin.getLocaleManager()));
}
}
@@ -1,5 +1,8 @@
/*
* Copyright (c) 2016 Lucko (Luck) <luck@lucko.me>
* 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
@@ -1,5 +1,8 @@
/*
* Copyright (c) 2016 Lucko (Luck) <luck@lucko.me>
* 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
@@ -46,11 +49,10 @@ import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.config.LuckPermsConfiguration;
import me.lucko.luckperms.common.constants.Permission;
import me.lucko.luckperms.common.contexts.ContextManager;
import me.lucko.luckperms.common.contexts.ServerCalculator;
import me.lucko.luckperms.common.contexts.StaticCalculator;
import me.lucko.luckperms.common.core.UuidCache;
import me.lucko.luckperms.common.core.model.User;
import me.lucko.luckperms.common.data.Importer;
import me.lucko.luckperms.common.debug.DebugHandler;
import me.lucko.luckperms.common.dependencies.Dependency;
import me.lucko.luckperms.common.dependencies.DependencyManager;
import me.lucko.luckperms.common.locale.LocaleManager;
import me.lucko.luckperms.common.locale.NoopLocaleManager;
@@ -62,6 +64,7 @@ import me.lucko.luckperms.common.managers.impl.GenericGroupManager;
import me.lucko.luckperms.common.managers.impl.GenericTrackManager;
import me.lucko.luckperms.common.managers.impl.GenericUserManager;
import me.lucko.luckperms.common.messaging.InternalMessagingService;
import me.lucko.luckperms.common.messaging.NoopMessagingService;
import me.lucko.luckperms.common.messaging.RedisMessaging;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.storage.Storage;
@@ -72,7 +75,10 @@ import me.lucko.luckperms.common.tasks.ExpireTemporaryTask;
import me.lucko.luckperms.common.tasks.UpdateTask;
import me.lucko.luckperms.common.treeview.PermissionVault;
import me.lucko.luckperms.common.utils.BufferedRequest;
import me.lucko.luckperms.common.utils.LoggerImpl;
import me.lucko.luckperms.common.utils.FileWatcher;
import me.lucko.luckperms.common.utils.LoginHelper;
import me.lucko.luckperms.common.utils.SenderLogger;
import me.lucko.luckperms.common.verbose.VerboseHandler;
import org.bukkit.World;
import org.bukkit.command.PluginCommand;
@@ -86,6 +92,7 @@ import org.bukkit.plugin.java.JavaPlugin;
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;
@@ -94,6 +101,7 @@ 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;
@Getter
@@ -107,12 +115,12 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
private GroupManager groupManager;
private TrackManager trackManager;
private Storage storage;
private FileWatcher fileWatcher = null;
private InternalMessagingService messagingService = null;
private UuidCache uuidCache;
private BukkitListener listener;
private ApiProvider apiProvider;
private Logger log;
private Importer importer;
private DefaultsProvider defaultsProvider;
private ChildPermissionProvider childPermissionProvider;
private LocaleManager localeManager;
@@ -122,21 +130,39 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
private CalculatorFactory calculatorFactory;
private BufferedRequest<Void> updateTaskBuffer;
private boolean started = false;
private DebugHandler debugHandler;
private CountDownLatch enableLatch = new CountDownLatch(1);
private VerboseHandler verboseHandler;
private BukkitSenderFactory senderFactory;
private PermissionVault permissionVault;
@Override
public void onEnable() {
public void onLoad() {
// setup minimal functionality in order to load initial dependencies
scheduler = new LPBukkitScheduler(this);
localeManager = new NoopLocaleManager();
senderFactory = new BukkitSenderFactory(this);
log = new LoggerImpl(getConsoleSender());
log = new SenderLogger(getConsoleSender());
DependencyManager.loadDependencies(this, Collections.singletonList(Dependency.CAFFEINE));
}
@Override
public void onEnable() {
try {
enable();
started = true;
} finally {
// count down the latch when onEnable has been called
// we don't care about the result here
enableLatch.countDown();
}
}
private void enable() {
LuckPermsPlugin.sendStartupBanner(getConsoleSender(), this);
ignoringLogs = ConcurrentHashMap.newKeySet();
debugHandler = new DebugHandler(scheduler.getAsyncBukkitExecutor(), getVersion());
verboseHandler = new VerboseHandler(scheduler.getAsyncBukkitExecutor(), getVersion());
permissionVault = new PermissionVault(scheduler.getAsyncBukkitExecutor());
getLog().info("Loading configuration...");
@@ -152,7 +178,7 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
childPermissionProvider = new ChildPermissionProvider();
// give all plugins a chance to load their permissions, then refresh.
scheduler.doSyncLater(() -> {
scheduler.syncLater(() -> {
defaultsProvider.refresh();
childPermissionProvider.setup();
@@ -170,6 +196,11 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
listener = new BukkitListener(this);
pm.registerEvents(listener, this);
if (getConfiguration().get(ConfigKeys.WATCH_FILES)) {
fileWatcher = new FileWatcher(this);
getScheduler().asyncRepeating(fileWatcher, 30L);
}
// initialise datastore
storage = StorageFactory.getInstance(this, StorageType.H2);
@@ -212,6 +243,10 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
getLog().warn("Messaging service '" + messagingType + "' not recognised.");
}
if (messagingService == null) {
messagingService = new NoopMessagingService();
}
// setup the update task buffer
updateTaskBuffer = new BufferedRequest<Void>(1000L, this::doAsync) {
@Override
@@ -239,7 +274,8 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
PluginCommand main = getServer().getPluginCommand("luckperms");
main.setExecutor(commandManager);
main.setTabCompleter(commandManager);
main.setAliases(Arrays.asList("perms", "lp", "permissions", "perm"));
main.setDescription("Manage permissions");
main.setAliases(Arrays.asList("lp", "perm", "perms", "permission", "permissions"));
// load internal managers
getLog().info("Loading internal permission managers...");
@@ -247,14 +283,16 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
userManager = new GenericUserManager(this);
groupManager = new GenericGroupManager(this);
trackManager = new GenericTrackManager(this);
importer = new Importer(commandManager);
calculatorFactory = new BukkitCalculatorFactory(this);
cachedStateManager = new CachedStateManager(this);
contextManager = new ContextManager<>();
worldCalculator = new WorldCalculator(this);
contextManager.registerCalculator(worldCalculator);
contextManager.registerCalculator(new ServerCalculator<>(getConfiguration()));
StaticCalculator<Player> staticCalculator = new StaticCalculator<>(getConfiguration());
contextManager.registerCalculator(staticCalculator);
contextManager.registerStaticCalculator(staticCalculator);
// Provide vault support
tryVaultHook(false);
@@ -270,33 +308,36 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
int mins = getConfiguration().get(ConfigKeys.SYNC_TIME);
if (mins > 0) {
long ticks = mins * 60 * 20;
scheduler.doAsyncRepeating(() -> updateTaskBuffer.request(), ticks);
scheduler.asyncRepeating(() -> updateTaskBuffer.request(), ticks);
}
scheduler.doAsyncLater(() -> updateTaskBuffer.request(), 40L);
scheduler.asyncLater(() -> updateTaskBuffer.request(), 40L);
// run an update instantly.
updateTaskBuffer.requestDirectly();
// register tasks
scheduler.doAsyncRepeating(new ExpireTemporaryTask(this), 60L);
scheduler.doAsyncRepeating(new CacheHousekeepingTask(this), 2400L);
scheduler.asyncRepeating(new ExpireTemporaryTask(this), 60L);
scheduler.asyncRepeating(new CacheHousekeepingTask(this), 2400L);
// register permissions
registerPermissions(getConfiguration().get(ConfigKeys.COMMANDS_ALLOW_OP) ? PermissionDefault.OP : PermissionDefault.FALSE);
try {
registerPermissions(getConfiguration().get(ConfigKeys.COMMANDS_ALLOW_OP) ? PermissionDefault.OP : PermissionDefault.FALSE);
} catch (Exception e) {
// this throws an exception if the plugin is /reloaded, grr
}
if (!getConfiguration().get(ConfigKeys.OPS_ENABLED)) {
scheduler.doSync(() -> getServer().getOperators().forEach(o -> o.setOp(false)));
}
// replace the temporary executor when the Bukkit one starts
getServer().getScheduler().runTaskAsynchronously(this, () -> {
scheduler.setUseBukkitAsync(true);
});
getServer().getScheduler().runTaskAsynchronously(this, () -> scheduler.setUseBukkitAsync(true));
// Load any online users (in the case of a reload)
for (Player player : getServer().getOnlinePlayers()) {
scheduler.doAsync(() -> {
listener.onAsyncLogin(player.getUniqueId(), player.getName());
User user = getUserManager().get(getUuidCache().getUUID(player.getUniqueId()));
LoginHelper.loadUser(this, player.getUniqueId(), player.getName(), false);
User user = getUserManager().getIfLoaded(getUuidCache().getUUID(player.getUniqueId()));
if (user != null) {
scheduler.doSync(() -> {
try {
@@ -310,7 +351,6 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
});
}
started = true;
getLog().info("Successfully loaded.");
}
@@ -323,7 +363,7 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
defaultsProvider.close();
permissionVault.setShutdown(true);
debugHandler.setShutdown(true);
verboseHandler.setShutdown(true);
for (Player player : getServer().getOnlinePlayers()) {
Injector.unInject(player, false, false);
@@ -331,7 +371,7 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
player.setOp(false);
}
final User user = getUserManager().get(getUuidCache().getUUID(player.getUniqueId()));
final User user = getUserManager().getIfLoaded(getUuidCache().getUUID(player.getUniqueId()));
if (user != null) {
user.unregisterData();
getUserManager().unload(user);
@@ -341,6 +381,10 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
getLog().info("Closing datastore...");
storage.shutdown();
if (fileWatcher != null) {
fileWatcher.close();
}
if (messagingService != null) {
getLog().info("Closing messaging service...");
messagingService.close();
@@ -368,12 +412,12 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
groupManager = null;
trackManager = null;
storage = null;
fileWatcher = null;
messagingService = null;
uuidCache = null;
listener = null;
apiProvider = null;
log = null;
importer = null;
defaultsProvider = null;
childPermissionProvider = null;
localeManager = null;
@@ -382,24 +426,24 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
worldCalculator = null;
calculatorFactory = null;
updateTaskBuffer = null;
debugHandler = null;
verboseHandler = null;
senderFactory = null;
permissionVault = null;
}
public void tryVaultHook(boolean force) {
if (vaultHook != null) {
return;
return; // already hooked
}
getLog().info("Attempting to hook with Vault...");
if (force) {
getLog().info("Attempting to hook with Vault...");
}
try {
if (force || getServer().getPluginManager().isPluginEnabled("Vault")) {
vaultHook = new VaultHook();
vaultHook.hook(this);
getLog().info("Registered Vault permission & chat hook.");
} else {
getLog().info("Vault not found.");
}
} catch (Exception e) {
vaultHook = null;
@@ -420,11 +464,16 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
if (getConfiguration().get(ConfigKeys.AUTO_OP)) {
try {
LPPermissible permissible = Injector.getPermissible(player.getUniqueId());
if (permissible == null) {
if (permissible == null || !permissible.getActive().get()) {
return;
}
Map<String, Boolean> backing = permissible.getUser().getUserData().getPermissionData(permissible.calculateContexts()).getImmutableBacking();
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) {}
@@ -448,7 +497,7 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
@Override
public String getServerVersion() {
return getServer().getVersion();
return getServer().getVersion() + " - " + getServer().getBukkitVersion();
}
@Override
@@ -500,7 +549,8 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
@Override
public boolean isPlayerOnline(UUID external) {
return getServer().getPlayer(external) != null;
Player player = getServer().getPlayer(external);
return player != null && player.isOnline();
}
@Override
@@ -528,6 +578,7 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
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())
@@ -542,6 +593,7 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
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())
@@ -597,16 +649,6 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
return map;
}
@SuppressWarnings("deprecation")
@Override
public UUID getUuidFromUsername(String playerName) {
try {
return getServer().getOfflinePlayer(playerName).getUniqueId();
} catch (Exception e) {
return null;
}
}
private void registerPermissions(PermissionDefault def) {
PluginManager pm = getServer().getPluginManager();
@@ -1,5 +1,8 @@
/*
* Copyright (c) 2016 Lucko (Luck) <luck@lucko.me>
* 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
@@ -60,45 +63,45 @@ public class LPBukkitScheduler implements LuckPermsScheduler {
}
@Override
public Executor getAsyncExecutor() {
public Executor async() {
return useBukkitAsync ? asyncBukkitExecutor : asyncLpExecutor;
}
@Override
public Executor getSyncExecutor() {
public Executor sync() {
return syncExecutor;
}
@Override
public void doAsync(Runnable r) {
getAsyncExecutor().execute(r);
public void doAsync(Runnable runnable) {
async().execute(runnable);
}
@Override
public void doSync(Runnable r) {
getSyncExecutor().execute(r);
public void doSync(Runnable runnable) {
sync().execute(runnable);
}
@Override
public void doAsyncRepeating(Runnable r, long interval) {
BukkitTask task = plugin.getServer().getScheduler().runTaskTimerAsynchronously(plugin, r, interval, interval);
public void asyncRepeating(Runnable runnable, long intervalTicks) {
BukkitTask task = plugin.getServer().getScheduler().runTaskTimerAsynchronously(plugin, runnable, intervalTicks, intervalTicks);
tasks.add(task);
}
@Override
public void doSyncRepeating(Runnable r, long interval) {
BukkitTask task = plugin.getServer().getScheduler().runTaskTimer(plugin, r, interval, interval);
public void syncRepeating(Runnable runnable, long intervalTicks) {
BukkitTask task = plugin.getServer().getScheduler().runTaskTimer(plugin, runnable, intervalTicks, intervalTicks);
tasks.add(task);
}
@Override
public void doAsyncLater(Runnable r, long delay) {
plugin.getServer().getScheduler().runTaskLaterAsynchronously(plugin, r, delay);
public void asyncLater(Runnable runnable, long delayTicks) {
plugin.getServer().getScheduler().runTaskLaterAsynchronously(plugin, runnable, delayTicks);
}
@Override
public void doSyncLater(Runnable r, long delay) {
plugin.getServer().getScheduler().runTaskLater(plugin, r, delay);
public void syncLater(Runnable runnable, long delayTicks) {
plugin.getServer().getScheduler().runTaskLater(plugin, runnable, delayTicks);
}
@Override
@@ -1,5 +1,8 @@
/*
* Copyright (c) 2016 Lucko (Luck) <luck@lucko.me>
* 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
@@ -24,8 +27,6 @@ package me.lucko.luckperms.bukkit;
import lombok.RequiredArgsConstructor;
import com.google.common.collect.Maps;
import me.lucko.luckperms.api.context.ContextCalculator;
import me.lucko.luckperms.api.context.MutableContextSet;
import me.lucko.luckperms.common.config.ConfigKeys;
@@ -33,8 +34,6 @@ import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import org.bukkit.entity.Player;
import java.util.Map;
@RequiredArgsConstructor
public class WorldCalculator implements ContextCalculator<Player> {
private static final String WORLD_KEY = "world";
@@ -43,27 +42,12 @@ public class WorldCalculator implements ContextCalculator<Player> {
@Override
public MutableContextSet giveApplicableContext(Player subject, MutableContextSet accumulator) {
String world = getWorld(subject);
if (world != null) {
accumulator.add(Maps.immutableEntry(WORLD_KEY, world));
String world = subject.getWorld().getName().toLowerCase();
while (!accumulator.has(WORLD_KEY, world)) {
accumulator.add(WORLD_KEY, world);
world = plugin.getConfiguration().get(ConfigKeys.WORLD_REWRITES).getOrDefault(world, world).toLowerCase();
}
return accumulator;
}
@Override
public boolean isContextApplicable(Player subject, Map.Entry<String, String> context) {
if (!context.getKey().equals(WORLD_KEY)) {
return false;
}
String world = getWorld(subject);
return world != null && world.equals(context.getValue());
}
private String getWorld(Player player) {
String world = player.getWorld().getName();
return plugin.getConfiguration().get(ConfigKeys.WORLD_REWRITES).getOrDefault(world, world);
}
}
@@ -1,5 +1,8 @@
/*
* Copyright (c) 2016 Lucko (Luck) <luck@lucko.me>
* 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
@@ -1,5 +1,8 @@
/*
* Copyright (c) 2016 Lucko (Luck) <luck@lucko.me>
* 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
@@ -24,6 +27,8 @@ package me.lucko.luckperms.bukkit.calculators;
import lombok.RequiredArgsConstructor;
import com.google.common.collect.Maps;
import me.lucko.luckperms.api.Tristate;
import me.lucko.luckperms.bukkit.model.ChildPermissionProvider;
import me.lucko.luckperms.common.calculators.PermissionProcessor;
@@ -46,7 +51,7 @@ public class ChildProcessor implements PermissionProcessor {
public void updateBacking(Map<String, Boolean> map) {
childPermissions.clear();
for (Map.Entry<String, Boolean> e : map.entrySet()) {
Map<String, Boolean> children = provider.getPermissions().get(e);
Map<String, Boolean> children = provider.getPermissions().get(Maps.immutableEntry(e.getKey(), e.getValue()));
if (children != null) {
childPermissions.putAll(children);
}
@@ -1,5 +1,8 @@
/*
* Copyright (c) 2016 Lucko (Luck) <luck@lucko.me>
* 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
@@ -1,5 +1,8 @@
/*
* Copyright (c) 2016 Lucko (Luck) <luck@lucko.me>
* 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
@@ -22,7 +25,6 @@
package me.lucko.luckperms.bukkit.compat;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import java.lang.reflect.Constructor;
@@ -58,7 +60,7 @@ public class BukkitJsonMessageHandler {
}
}
Class<?> packetChatClass = Class.forName(getVersionedClassName("PacketPlayOutChat"));
Class<?> packetChatClass = ReflectionUtil.nmsClass("PacketPlayOutChat");
Constructor[] packetConstructors = packetChatClass.getDeclaredConstructors();
for (Constructor c : packetConstructors) {
Class<?>[] parameters = c.getParameterTypes();
@@ -68,30 +70,19 @@ public class BukkitJsonMessageHandler {
}
}
Class<?> baseComponentClass = Class.forName(getVersionedClassName("IChatBaseComponent"));
Class<?> chatSerializerClass = baseComponentClass.getClasses()[0];
Class<?> baseComponentClass = ReflectionUtil.nmsClass("IChatBaseComponent");
Class<?> chatSerializerClass;
if (baseComponentClass.getClasses().length > 0) {
chatSerializerClass = baseComponentClass.getClasses()[0];
} else {
// 1.7 class is here instead.
chatSerializerClass = ReflectionUtil.nmsClass("ChatSerializer");
}
SERIALIZE_METHOD = chatSerializerClass.getDeclaredMethod("a", String.class);
}
private static String getVersionedClassName(String className) {
Class server = Bukkit.getServer().getClass();
if (!server.getSimpleName().equals("CraftServer")) {
throw new RuntimeException("Couldn't reflect into server " + server);
}
String version;
if (server.getName().equals("org.bukkit.craftbukkit.CraftServer")) {
// Non versioned class
version = ".";
} else {
version = server.getName().substring("org.bukkit.craftbukkit".length());
version = version.substring(0, version.length() - "CraftServer".length());
}
return "net.minecraft.server" + version + className;
}
private static synchronized boolean trySetup(Object player) {
if (setup) return true;
if (triedAndFailed) return false;
@@ -1,5 +1,8 @@
/*
* Copyright (c) 2016 Lucko (Luck) <luck@lucko.me>
* 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
@@ -37,9 +40,9 @@ public class MessageHandler {
}
public void sendJsonMessage(CommandSender sender, FancyMessage message) {
if (sender instanceof Player) {
if (ReflectionUtil.isChatCompatible() && sender instanceof Player) {
Player player = (Player) sender;
String json = message.toJSONString();
String json = message.exportToJson();
// Try Bukkit.
if (bukkitHandler.sendJsonMessage(player, json)) {
@@ -0,0 +1,74 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.bukkit.compat;
import lombok.experimental.UtilityClass;
import org.bukkit.Bukkit;
@UtilityClass
public class ReflectionUtil {
private static final String SERVER_VERSION = _getServerVersion();
private static final boolean CHAT_COMPATIBLE = !SERVER_VERSION.startsWith(".v1_7_");
private static String _getServerVersion() {
Class<?> server = Bukkit.getServer().getClass();
if (!server.getSimpleName().equals("CraftServer")) {
return ".";
}
if (server.getName().equals("org.bukkit.craftbukkit.CraftServer")) {
// Non versioned class
return ".";
} else {
String version = server.getName().substring("org.bukkit.craftbukkit".length());
return version.substring(0, version.length() - "CraftServer".length());
}
}
public static String getServerVersion() {
return SERVER_VERSION;
}
public static boolean isChatCompatible() {
return CHAT_COMPATIBLE;
}
public static String nms(String className) {
return "net.minecraft.server" + getServerVersion() + className;
}
public static Class<?> nmsClass(String className) throws ClassNotFoundException {
return Class.forName(nms(className));
}
public static String obc(String className) {
return "org.bukkit.craftbukkit" + getServerVersion() + className;
}
public static Class<?> obcClass(String className) throws ClassNotFoundException {
return Class.forName(obc(className));
}
}
@@ -1,5 +1,8 @@
/*
* Copyright (c) 2016 Lucko (Luck) <luck@lucko.me>
* 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
@@ -1,5 +1,8 @@
/*
* Copyright (c) 2016 Lucko (Luck) <luck@lucko.me>
* 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
@@ -64,12 +67,11 @@ public class BungeeMessagingService extends AbstractMessagingService implements
@Override
public void run() {
Collection<? extends Player> players = plugin.getServer().getOnlinePlayers();
if (players.isEmpty()) {
Player p = Iterables.getFirst(players, null);
if (p == null) {
return;
}
Player p = Iterables.getFirst(players, null);
ByteArrayDataOutput out = ByteStreams.newDataOutput();
out.writeUTF(message);
@@ -1,5 +1,8 @@
/*
* Copyright (c) 2016 Lucko (Luck) <luck@lucko.me>
* 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
@@ -1,5 +1,8 @@
/*
* Copyright (c) 2016 Lucko (Luck) <luck@lucko.me>
* 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
@@ -29,21 +32,25 @@ import de.bananaco.bpermissions.api.Permission;
import de.bananaco.bpermissions.api.World;
import de.bananaco.bpermissions.api.WorldManager;
import me.lucko.luckperms.api.MetaUtils;
import me.lucko.luckperms.api.ChatMetaType;
import me.lucko.luckperms.api.event.cause.CreationCause;
import me.lucko.luckperms.common.commands.CommandException;
import me.lucko.luckperms.common.commands.CommandResult;
import me.lucko.luckperms.common.commands.SubCommand;
import me.lucko.luckperms.common.commands.abstraction.SubCommand;
import me.lucko.luckperms.common.commands.impl.migration.MigrationUtils;
import me.lucko.luckperms.common.commands.sender.Sender;
import me.lucko.luckperms.common.core.NodeFactory;
import me.lucko.luckperms.common.core.model.PermissionHolder;
import me.lucko.luckperms.common.core.model.User;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.utils.Predicates;
import me.lucko.luckperms.common.utils.ProgressLogger;
import org.bukkit.Bukkit;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -54,9 +61,6 @@ import static me.lucko.luckperms.common.constants.Permission.MIGRATION;
public class MigrationBPermissions extends SubCommand<Object> {
private static Field uConfigField;
private static Method getConfigurationSectionMethod = null;
private static Method getKeysMethod = null;
static {
try {
uConfigField = Class.forName("de.bananaco.bpermissions.imp.YamlWorld").getDeclaredField("uconfig");
@@ -87,7 +91,24 @@ public class MigrationBPermissions extends SubCommand<Object> {
log.log("Forcing the plugin to load all data. This could take a while.");
for (World world : worldManager.getAllWorlds()) {
log.log("Loading users in world " + world.getName());
Set<String> users = getUsers(world);
YamlConfiguration yamlWorldUsers = null;
try {
yamlWorldUsers = (YamlConfiguration) uConfigField.get(world);
} catch (Throwable t) {
t.printStackTrace();
}
if (yamlWorldUsers == null) {
continue;
}
ConfigurationSection configSection = yamlWorldUsers.getConfigurationSection("users");
if (configSection == null) {
continue;
}
Set<String> users = configSection.getKeys(false);
if (users == null) {
log.logErr("Couldn't get a list of users.");
return CommandResult.FAILURE;
@@ -109,7 +130,7 @@ public class MigrationBPermissions extends SubCommand<Object> {
log.log("Starting group migration in world " + world.getName() + ".");
AtomicInteger groupCount = new AtomicInteger(0);
for (Calculable group : world.getAll(CalculableType.GROUP)) {
String groupName = group.getName().toLowerCase();
String groupName = MigrationUtils.standardizeName(group.getName());
if (group.getName().equalsIgnoreCase(world.getDefaultGroup())) {
groupName = "default";
}
@@ -118,23 +139,31 @@ public class MigrationBPermissions extends SubCommand<Object> {
plugin.getStorage().createAndLoadGroup(groupName, CreationCause.INTERNAL).join();
me.lucko.luckperms.common.core.model.Group lpGroup = plugin.getGroupManager().getIfLoaded(groupName);
migrateHolder(log, world, group, lpGroup);
MigrationUtils.setGroupWeight(lpGroup, group.getPriority());
migrateHolder(world, group, lpGroup);
plugin.getStorage().saveGroup(lpGroup);
log.logAllProgress("Migrated {} groups so far.", groupCount.incrementAndGet());
}
log.log("Migrated " + groupCount.get() + " groups in world " + world.getName() + ".");
// Migrate all users
log.log("Starting user migration in world " + world.getName() + ".");
AtomicInteger userCount = new AtomicInteger(0);
for (Calculable user : world.getAll(CalculableType.USER)) {
// There is no mention of UUIDs in the API. I assume that name = uuid. idk?
UUID uuid;
UUID uuid = null;
try {
uuid = UUID.fromString(user.getName());
} catch (IllegalArgumentException e) {
uuid = plugin.getUuidFromUsername(user.getName());
try {
//noinspection deprecation
uuid = Bukkit.getOfflinePlayer(user.getName()).getUniqueId();
} catch (Exception ex) {
e.printStackTrace();
}
}
if (uuid == null) {
@@ -144,9 +173,9 @@ public class MigrationBPermissions extends SubCommand<Object> {
// Make a LuckPerms user for the one being migrated.
plugin.getStorage().loadUser(uuid, "null").join();
User lpUser = plugin.getUserManager().get(uuid);
User lpUser = plugin.getUserManager().getIfLoaded(uuid);
migrateHolder(log, world, user, lpUser);
migrateHolder(world, user, lpUser);
plugin.getStorage().saveUser(lpUser);
plugin.getUserManager().cleanup(lpUser);
@@ -161,78 +190,47 @@ public class MigrationBPermissions extends SubCommand<Object> {
return CommandResult.SUCCESS;
}
@SuppressWarnings("unchecked")
private static Set<String> getUsers(World world) {
try {
Object yamlWorldUsers = uConfigField.get(world);
if (getConfigurationSectionMethod == null) {
getConfigurationSectionMethod = yamlWorldUsers.getClass().getMethod("getConfigurationSection", String.class);
getConfigurationSectionMethod.setAccessible(true);
}
Object configSection = getConfigurationSectionMethod.invoke(yamlWorldUsers, "users");
if (configSection == null) {
return Collections.emptySet();
}
if (getKeysMethod == null) {
getKeysMethod = configSection.getClass().getMethod("getKeys", boolean.class);
getKeysMethod.setAccessible(true);
}
return (Set<String>) getKeysMethod.invoke(configSection, false);
} catch (Throwable t) {
t.printStackTrace();
return null;
}
}
private static void migrateHolder(ProgressLogger log, World world, Calculable c, PermissionHolder holder) {
private static void migrateHolder(World world, Calculable c, PermissionHolder holder) {
// Migrate the groups permissions in this world
for (Permission p : c.getPermissions()) {
try {
holder.setPermission(p.name(), p.isTrue(), "global", world.getName());
} catch (Exception ex) {
log.handleException(ex);
if (p.name().isEmpty()) {
continue;
}
holder.setPermission(NodeFactory.make(p.name(), p.isTrue(), "global", world.getName()));
// Include any child permissions
for (Map.Entry<String, Boolean> child : p.getChildren().entrySet()) {
try {
holder.setPermission(child.getKey(), child.getValue(), "global", world.getName());
} catch (Exception ex) {
log.handleException(ex);
if (child.getKey().isEmpty()) {
continue;
}
holder.setPermission(NodeFactory.make(child.getKey(), child.getValue(), "global", world.getName()));
}
}
// Migrate any inherited groups
for (Group parent : c.getGroups()) {
try {
holder.setPermission("group." + parent.getName(), true, "global", world.getName());
} catch (Exception ex) {
log.handleException(ex);
String parentName = MigrationUtils.standardizeName(parent.getName());
if (parent.getName().equalsIgnoreCase(world.getDefaultGroup())) {
parentName = "default";
}
holder.setPermission(NodeFactory.make("group." + parentName, true, "global", world.getName()));
}
// Migrate existing meta
for (Map.Entry<String, String> meta : c.getMeta().entrySet()) {
if (meta.getKey().equalsIgnoreCase("prefix") || meta.getKey().equalsIgnoreCase("suffix")) {
String chatMeta = MetaUtils.escapeCharacters(meta.getValue());
try {
holder.setPermission(meta.getKey().toLowerCase() + "." + c.getPriority() + "." + chatMeta, true);
} catch (Exception ex) {
log.handleException(ex);
}
if (meta.getKey().isEmpty() || meta.getValue().isEmpty()) {
continue;
}
try {
holder.setPermission("meta." + meta.getKey() + "." + meta.getValue(), true, "global", world.getName());
} catch (Exception ex) {
log.handleException(ex);
if (meta.getKey().equalsIgnoreCase("prefix") || meta.getKey().equalsIgnoreCase("suffix")) {
ChatMetaType type = ChatMetaType.valueOf(meta.getKey().toUpperCase());
holder.setPermission(NodeFactory.makeChatMetaNode(type, c.getPriority(), meta.getValue()).setWorld(world.getName()).build());
continue;
}
holder.setPermission(NodeFactory.makeMetaNode(meta.getKey(), meta.getValue()).setWorld(world.getName()).build());
}
}
}
@@ -1,5 +1,8 @@
/*
* Copyright (c) 2016 Lucko (Luck) <luck@lucko.me>
* 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
@@ -22,20 +25,20 @@
package me.lucko.luckperms.bukkit.migration;
import com.google.common.collect.Maps;
import me.lucko.luckperms.api.ChatMetaType;
import me.lucko.luckperms.api.Node;
import me.lucko.luckperms.api.event.cause.CreationCause;
import me.lucko.luckperms.common.commands.Arg;
import me.lucko.luckperms.common.commands.CommandException;
import me.lucko.luckperms.common.commands.CommandResult;
import me.lucko.luckperms.common.commands.SubCommand;
import me.lucko.luckperms.common.commands.abstraction.SubCommand;
import me.lucko.luckperms.common.commands.impl.migration.MigrationUtils;
import me.lucko.luckperms.common.commands.sender.Sender;
import me.lucko.luckperms.common.constants.Permission;
import me.lucko.luckperms.common.core.NodeFactory;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.utils.Predicates;
import me.lucko.luckperms.common.utils.ProgressLogger;
import me.lucko.luckperms.exceptions.ObjectAlreadyHasException;
import me.lucko.luckperms.exceptions.ObjectLacksException;
import org.anjocaido.groupmanager.GlobalGroups;
import org.anjocaido.groupmanager.GroupManager;
@@ -47,8 +50,10 @@ import org.bukkit.Bukkit;
import org.bukkit.World;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
@@ -73,8 +78,8 @@ public class MigrationGroupManager extends SubCommand<Object> {
log.logErr("Was expecting true/false, but got " + args.get(0) + " instead.");
return CommandResult.STATE_ERROR;
}
boolean migrateAsGlobal = Boolean.parseBoolean(args.get(0));
final Function<String, String> worldMappingFunc = s -> migrateAsGlobal ? "" : s;
final boolean migrateAsGlobal = Boolean.parseBoolean(args.get(0));
final Function<String, String> worldMappingFunc = s -> migrateAsGlobal ? null : s;
if (!Bukkit.getPluginManager().isPluginEnabled("GroupManager")) {
log.logErr("Plugin not loaded.");
@@ -82,7 +87,6 @@ public class MigrationGroupManager extends SubCommand<Object> {
}
List<String> worlds = Bukkit.getWorlds().stream().map(World::getName).map(String::toLowerCase).collect(Collectors.toList());
GroupManager gm = (GroupManager) Bukkit.getPluginManager().getPlugin("GroupManager");
// Migrate Global Groups
@@ -91,32 +95,25 @@ public class MigrationGroupManager extends SubCommand<Object> {
AtomicInteger globalGroupCount = new AtomicInteger(0);
for (Group g : gg.getGroupList()) {
plugin.getStorage().createAndLoadGroup(g.getName().toLowerCase(), CreationCause.INTERNAL).join();
me.lucko.luckperms.common.core.model.Group group = plugin.getGroupManager().getIfLoaded(g.getName().toLowerCase());
String groupName = MigrationUtils.standardizeName(g.getName());
plugin.getStorage().createAndLoadGroup(groupName, CreationCause.INTERNAL).join();
me.lucko.luckperms.common.core.model.Group group = plugin.getGroupManager().getIfLoaded(groupName);
for (String node : g.getPermissionList()) {
boolean value = true;
if (node.startsWith("!") || node.startsWith("-")) {
node = node.substring(1);
value = false;
} else if (node.startsWith("+")) {
node = node.substring(1);
value = true;
if (node.isEmpty()) {
continue;
}
try {
group.setPermission(node, value);
} catch (Exception ex) {
log.handleException(ex);
}
group.setPermission(MigrationUtils.parseNode(node, true).build());
}
for (String s : g.getInherits()) {
try {
group.setPermission("group." + s.toLowerCase(), true);
} catch (Exception ex) {
log.handleException(ex);
if (s.isEmpty()) {
continue;
}
group.setPermission(NodeFactory.make("group." + MigrationUtils.standardizeName(s)));
}
plugin.getStorage().saveGroup(group);
@@ -125,14 +122,9 @@ public class MigrationGroupManager extends SubCommand<Object> {
log.log("Migrated " + globalGroupCount.get() + " global groups");
// Collect data
// UUID --> Map<Entry<String, String>, Boolean> where k=world, v = node
Map<UUID, Map<Map.Entry<String, String>, Boolean>> users = new HashMap<>();
// UUID --> primary group name
Map<UUID, Set<Node>> users = new HashMap<>();
Map<UUID, String> primaryGroups = new HashMap<>();
// String --> Map<Entry<String, String>, Boolean> where k=world, v = node
Map<String, Map<Map.Entry<String, String>, Boolean>> groups = new HashMap<>();
Map<String, Set<Node>> groups = new HashMap<>();
WorldsHolder wh = gm.getWorldsHolder();
@@ -145,25 +137,49 @@ public class MigrationGroupManager extends SubCommand<Object> {
WorldDataHolder wdh = wh.getWorldData(world);
AtomicInteger groupWorldCount = new AtomicInteger(0);
for (Group g : wdh.getGroupList()) {
groups.putIfAbsent(g.getName().toLowerCase(), new HashMap<>());
for (Group group : wdh.getGroupList()) {
String groupName = MigrationUtils.standardizeName(group.getName());
for (String node : g.getPermissionList()) {
boolean value = true;
if (node.startsWith("!") || node.startsWith("-")) {
node = node.substring(1);
value = false;
} else if (node.startsWith("+")) {
node = node.substring(1);
value = true;
groups.putIfAbsent(groupName, new HashSet<>());
for (String node : group.getPermissionList()) {
if (node.isEmpty()) {
continue;
}
groups.get(g.getName().toLowerCase()).put(Maps.immutableEntry(worldMappingFunc.apply(world), node), value);
groups.get(groupName).add(MigrationUtils.parseNode(node, true).setWorld(worldMappingFunc.apply(world)).build());
}
for (String s : g.getInherits()) {
groups.get(g.getName().toLowerCase()).put(Maps.immutableEntry(worldMappingFunc.apply(world), "group." + s.toLowerCase()), true);
for (String s : group.getInherits()) {
if (s.isEmpty()) {
continue;
}
groups.get(groupName).add(NodeFactory.make("group." + MigrationUtils.standardizeName(s), true, null, worldMappingFunc.apply(world)));
}
String[] metaKeys = group.getVariables().getVarKeyList();
for (String key : metaKeys) {
String value = group.getVariables().getVarString(key);
key = key.toLowerCase();
if (key.isEmpty() || value.isEmpty()) {
continue;
}
if (key.equals("build")) {
continue;
}
if (key.equals("prefix") || key.equals("suffix")) {
ChatMetaType type = ChatMetaType.valueOf(key.toUpperCase());
groups.get(groupName).add(NodeFactory.makeChatMetaNode(type, 50, value).setWorld(worldMappingFunc.apply(world)).build());
} else {
groups.get(groupName).add(NodeFactory.makeMetaNode(key, value).setWorld(worldMappingFunc.apply(world)).build());
}
}
log.logAllProgress("Migrated {} groups so far in world " + world, groupWorldCount.incrementAndGet());
}
log.log("Migrated " + groupWorldCount.get() + " groups in world " + world);
@@ -178,35 +194,52 @@ public class MigrationGroupManager extends SubCommand<Object> {
continue;
}
users.putIfAbsent(uuid, new HashMap<>());
users.putIfAbsent(uuid, new HashSet<>());
for (String node : user.getPermissionList()) {
boolean value = true;
if (node.startsWith("!") || node.startsWith("-")) {
node = node.substring(1);
value = false;
} else if (node.startsWith("+")) {
node = node.substring(1);
value = true;
if (node.isEmpty()) {
continue;
}
users.get(uuid).put(Maps.immutableEntry(worldMappingFunc.apply(world), node), value);
users.get(uuid).add(MigrationUtils.parseNode(node, true).setWorld(worldMappingFunc.apply(world)).build());
}
String finalWorld = worldMappingFunc.apply(world);
// Collect sub groups
users.get(uuid).putAll(user.subGroupListStringCopy().stream()
.map(n -> "group." + n)
.map(n -> Maps.immutableEntry(finalWorld, n))
.collect(Collectors.toMap(n -> n, n -> true))
String finalWorld = worldMappingFunc.apply(world);
users.get(uuid).addAll(user.subGroupListStringCopy().stream()
.filter(n -> !n.isEmpty())
.map(n -> "group." + MigrationUtils.standardizeName(n))
.map(n -> NodeFactory.make(n, true, null, finalWorld))
.collect(Collectors.toSet())
);
primaryGroups.put(uuid, user.getGroupName());
// Get primary group
primaryGroups.put(uuid, MigrationUtils.standardizeName(user.getGroupName()));
String[] metaKeys = user.getVariables().getVarKeyList();
for (String key : metaKeys) {
String value = user.getVariables().getVarString(key);
key = key.toLowerCase();
if (key.isEmpty() || value.isEmpty()) {
continue;
}
if (key.equals("build")) {
continue;
}
if (key.equals("prefix") || key.equals("suffix")) {
ChatMetaType type = ChatMetaType.valueOf(key.toUpperCase());
users.get(uuid).add(NodeFactory.makeChatMetaNode(type, 100, value).setWorld(worldMappingFunc.apply(world)).build());
} else {
users.get(uuid).add(NodeFactory.makeMetaNode(key, value).setWorld(worldMappingFunc.apply(world)).build());
}
}
log.logProgress("Migrated {} users so far in world " + world, userWorldCount.incrementAndGet());
}
log.log("Migrated " + userWorldCount.get() + " users in world " + world);
}
log.log("All data has now been processed, now starting the import process.");
@@ -214,23 +247,12 @@ public class MigrationGroupManager extends SubCommand<Object> {
log.log("Starting group migration.");
AtomicInteger groupCount = new AtomicInteger(0);
for (Map.Entry<String, Map<Map.Entry<String, String>, Boolean>> e : groups.entrySet()) {
for (Map.Entry<String, Set<Node>> e : groups.entrySet()) {
plugin.getStorage().createAndLoadGroup(e.getKey(), CreationCause.INTERNAL).join();
me.lucko.luckperms.common.core.model.Group group = plugin.getGroupManager().getIfLoaded(e.getKey());
for (Map.Entry<Map.Entry<String, String>, Boolean> n : e.getValue().entrySet()) {
// n.key.key = world
// n.key.value = node
// n.value = true/false
try {
if (n.getKey().getKey().equals("")) {
group.setPermission(n.getKey().getValue(), n.getValue());
} else {
group.setPermission(n.getKey().getValue(), n.getValue(), "global", n.getKey().getKey());
}
} catch (Exception ex) {
log.handleException(ex);
}
for (Node node : e.getValue()) {
group.setPermission(node);
}
plugin.getStorage().saveGroup(group);
@@ -240,34 +262,19 @@ public class MigrationGroupManager extends SubCommand<Object> {
log.log("Starting user migration.");
AtomicInteger userCount = new AtomicInteger(0);
for (Map.Entry<UUID, Map<Map.Entry<String, String>, Boolean>> e : users.entrySet()) {
for (Map.Entry<UUID, Set<Node>> e : users.entrySet()) {
plugin.getStorage().loadUser(e.getKey(), "null").join();
me.lucko.luckperms.common.core.model.User user = plugin.getUserManager().get(e.getKey());
me.lucko.luckperms.common.core.model.User user = plugin.getUserManager().getIfLoaded(e.getKey());
for (Map.Entry<Map.Entry<String, String>, Boolean> n : e.getValue().entrySet()) {
// n.key.key = world
// n.key.value = node
// n.value = true/false
try {
if (n.getKey().getKey().equals("")) {
user.setPermission(n.getKey().getValue(), n.getValue());
} else {
user.setPermission(n.getKey().getValue(), n.getValue(), "global", n.getKey().getKey());
}
} catch (Exception ex) {
log.handleException(ex);
}
for (Node node : e.getValue()) {
user.setPermission(node);
}
String primaryGroup = primaryGroups.get(e.getKey());
if (primaryGroup != null) {
try {
user.setPermission("group." + primaryGroup, true);
} catch (ObjectAlreadyHasException ignored) {}
user.setPrimaryGroup(primaryGroup);
try {
user.unsetPermission("group.default");
} catch (ObjectLacksException ignored) {}
user.setPermission(NodeFactory.make("group." + primaryGroup));
user.getPrimaryGroup().setStoredValue(primaryGroup);
user.unsetPermission(NodeFactory.make("group.default"));
}
plugin.getStorage().saveUser(user);
@@ -1,5 +1,8 @@
/*
* Copyright (c) 2016 Lucko (Luck) <luck@lucko.me>
* 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
@@ -22,20 +25,19 @@
package me.lucko.luckperms.bukkit.migration;
import me.lucko.luckperms.api.MetaUtils;
import me.lucko.luckperms.api.event.cause.CreationCause;
import me.lucko.luckperms.common.commands.CommandException;
import me.lucko.luckperms.common.commands.CommandResult;
import me.lucko.luckperms.common.commands.SubCommand;
import me.lucko.luckperms.common.commands.abstraction.SubCommand;
import me.lucko.luckperms.common.commands.impl.migration.MigrationUtils;
import me.lucko.luckperms.common.commands.sender.Sender;
import me.lucko.luckperms.common.constants.Permission;
import me.lucko.luckperms.common.core.NodeFactory;
import me.lucko.luckperms.common.core.model.Group;
import me.lucko.luckperms.common.core.model.User;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.utils.Predicates;
import me.lucko.luckperms.common.utils.ProgressLogger;
import me.lucko.luckperms.exceptions.ObjectAlreadyHasException;
import me.lucko.luckperms.exceptions.ObjectLacksException;
import org.bukkit.Bukkit;
import org.bukkit.World;
@@ -99,59 +101,49 @@ public class MigrationPermissionsEx extends SubCommand<Object> {
for (PermissionGroup group : manager.getGroupList()) {
int groupWeight = maxWeight - group.getRank();
final String name = group.getName().toLowerCase();
plugin.getStorage().createAndLoadGroup(name, CreationCause.INTERNAL).join();
Group lpGroup = plugin.getGroupManager().getIfLoaded(name);
final String groupName = MigrationUtils.standardizeName(group.getName());
plugin.getStorage().createAndLoadGroup(groupName, CreationCause.INTERNAL).join();
Group lpGroup = plugin.getGroupManager().getIfLoaded(groupName);
MigrationUtils.setGroupWeight(lpGroup, groupWeight);
try {
for (String node : group.getOwnPermissions(null)) {
boolean value = true;
if (node.startsWith("-")) {
node = node.substring(1);
value = false;
if (node.isEmpty()) {
continue;
}
try {
lpGroup.setPermission(node, value);
} catch (Exception ex) {
log.handleException(ex);
}
lpGroup.setPermission(MigrationUtils.parseNode(node, true).build());
}
} catch (NullPointerException ignored) {
// No docs on if #getOwnPermissions(null) is ok. Should be fine though.
}
for (String world : worlds) {
if (world.isEmpty()) {
continue;
}
for (String node : group.getOwnPermissions(world)) {
boolean value = true;
if (node.startsWith("-")) {
node = node.substring(1);
value = false;
if (node.isEmpty()) {
continue;
}
try {
lpGroup.setPermission(node, value, "global", world.toLowerCase());
} catch (Exception ex) {
log.handleException(ex);
}
lpGroup.setPermission(MigrationUtils.parseNode(node, true).setWorld(world.toLowerCase()).build());
}
}
for (PermissionGroup g : group.getParents()) {
try {
lpGroup.setPermission("group." + g.getName().toLowerCase(), true);
} catch (Exception ex) {
log.handleException(ex);
}
lpGroup.setPermission(NodeFactory.make("group." + MigrationUtils.standardizeName(g.getName())));
}
for (String world : worlds) {
if (world.isEmpty()) {
continue;
}
for (PermissionGroup g : group.getParents(world)) {
try {
lpGroup.setPermission("group." + g.getName().toLowerCase(), true, "global", world.toLowerCase());
} catch (Exception ex) {
log.handleException(ex);
}
lpGroup.setPermission(NodeFactory.make("group." + MigrationUtils.standardizeName(g.getName()), true, "global", world.toLowerCase()));
}
}
@@ -159,21 +151,11 @@ public class MigrationPermissionsEx extends SubCommand<Object> {
String suffix = group.getOwnSuffix();
if (prefix != null && !prefix.equals("")) {
prefix = MetaUtils.escapeCharacters(prefix);
try {
lpGroup.setPermission("prefix." + groupWeight + "." + prefix, true);
} catch (Exception ex) {
log.handleException(ex);
}
lpGroup.setPermission(NodeFactory.makePrefixNode(groupWeight, prefix).build());
}
if (suffix != null && !suffix.equals("")) {
suffix = MetaUtils.escapeCharacters(suffix);
try {
lpGroup.setPermission("suffix." + groupWeight + "." + suffix, true);
} catch (Exception ex) {
log.handleException(ex);
}
lpGroup.setPermission(NodeFactory.makeSuffixNode(groupWeight, suffix).build());
}
plugin.getStorage().saveGroup(lpGroup);
@@ -195,7 +177,11 @@ public class MigrationPermissionsEx extends SubCommand<Object> {
} catch (IllegalArgumentException e) {
u = ni.nameToUUID(user.getIdentifier());
if (u == null) {
u = plugin.getUuidFromUsername(user.getIdentifier());
try {
u = Bukkit.getOfflinePlayer(user.getIdentifier()).getUniqueId();
} catch (Exception ex) {
e.printStackTrace();
}
}
}
@@ -205,57 +191,53 @@ public class MigrationPermissionsEx extends SubCommand<Object> {
}
plugin.getStorage().loadUser(u, user.getName()).join();
User lpUser = plugin.getUserManager().get(u);
User lpUser = plugin.getUserManager().getIfLoaded(u);
try {
for (String node : user.getOwnPermissions(null)) {
boolean value = true;
if (node.startsWith("-")) {
node = node.substring(1);
value = false;
if (node.isEmpty()) {
continue;
}
try {
lpUser.setPermission(node, value);
} catch (Exception ex) {
log.handleException(ex);
}
lpUser.setPermission(MigrationUtils.parseNode(node, true).build());
}
} catch (NullPointerException ignored) {
// No docs on if #getOwnPermissions(null) is ok. Should be fine though.
}
for (String world : worlds) {
if (world.isEmpty()) {
continue;
}
for (String node : user.getOwnPermissions(world)) {
boolean value = true;
if (node.startsWith("-")) {
node = node.substring(1);
value = false;
if (node.isEmpty()) {
continue;
}
try {
lpUser.setPermission(node, value, "global", world.toLowerCase());
} catch (Exception ex) {
log.handleException(ex);
}
lpUser.setPermission(MigrationUtils.parseNode(node, true).setWorld(world.toLowerCase()).build());
}
}
for (String s : user.getGroupNames()) {
try {
lpUser.setPermission("group." + s.toLowerCase(), true);
} catch (Exception ex) {
log.handleException(ex);
for (String g : user.getGroupNames()) {
if (g.isEmpty()) {
continue;
}
lpUser.setPermission(NodeFactory.make("group." + MigrationUtils.standardizeName(g)));
}
for (String world : worlds) {
for (String s : user.getGroupNames(world)) {
try {
lpUser.setPermission("group." + s.toLowerCase(), true, "global", world.toLowerCase());
} catch (Exception ex) {
log.handleException(ex);
if (world.isEmpty()) {
continue;
}
for (String g : user.getGroupNames(world)) {
if (g.isEmpty()) {
continue;
}
lpUser.setPermission(NodeFactory.make("group." + MigrationUtils.standardizeName(g), true, "global", world.toLowerCase()));
}
}
@@ -263,21 +245,11 @@ public class MigrationPermissionsEx extends SubCommand<Object> {
String suffix = user.getOwnSuffix();
if (prefix != null && !prefix.equals("")) {
prefix = MetaUtils.escapeCharacters(prefix);
try {
lpUser.setPermission("prefix." + maxWeight + "." + prefix, true);
} catch (Exception ex) {
log.handleException(ex);
}
lpUser.setPermission(NodeFactory.makePrefixNode(maxWeight, prefix).build());
}
if (suffix != null && !suffix.equals("")) {
suffix = MetaUtils.escapeCharacters(suffix);
try {
lpUser.setPermission("suffix." + maxWeight + "." + suffix, true);
} catch (Exception ex) {
log.handleException(ex);
}
lpUser.setPermission(NodeFactory.makeSuffixNode(maxWeight, suffix).build());
}
// Lowest rank is the highest group #logic
@@ -291,13 +263,9 @@ public class MigrationPermissionsEx extends SubCommand<Object> {
}
if (primary != null && !primary.equalsIgnoreCase("default")) {
try {
lpUser.setPermission("group." + primary.toLowerCase(), true);
} catch (ObjectAlreadyHasException ignored) {}
lpUser.setPrimaryGroup(primary);
try {
lpUser.unsetPermission("group.default");
} catch (ObjectLacksException ignored) {}
lpUser.setPermission(NodeFactory.make("group." + primary.toLowerCase()));
lpUser.getPrimaryGroup().setStoredValue(primary);
lpUser.unsetPermission(NodeFactory.make("group.default"));
}
plugin.getUserManager().cleanup(lpUser);
@@ -1,5 +1,8 @@
/*
* Copyright (c) 2016 Lucko (Luck) <luck@lucko.me>
* 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
@@ -22,106 +25,52 @@
package me.lucko.luckperms.bukkit.migration;
import lombok.SneakyThrows;
import com.github.cheesesoftware.PowerfulPermsAPI.CachedGroup;
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.github.cheesesoftware.PowerfulPermsAPI.ResultRunnable;
import com.google.common.util.concurrent.ListenableFuture;
import com.zaxxer.hikari.HikariDataSource;
import me.lucko.luckperms.api.Node;
import me.lucko.luckperms.api.event.cause.CreationCause;
import me.lucko.luckperms.bukkit.migration.utils.LPResultRunnable;
import me.lucko.luckperms.common.commands.Arg;
import me.lucko.luckperms.common.commands.CommandException;
import me.lucko.luckperms.common.commands.CommandResult;
import me.lucko.luckperms.common.commands.SubCommand;
import me.lucko.luckperms.common.commands.abstraction.SubCommand;
import me.lucko.luckperms.common.commands.impl.migration.MigrationUtils;
import me.lucko.luckperms.common.commands.sender.Sender;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.core.NodeFactory;
import me.lucko.luckperms.common.core.model.PermissionHolder;
import me.lucko.luckperms.common.core.model.User;
import me.lucko.luckperms.common.dependencies.DependencyManager;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.storage.StorageType;
import me.lucko.luckperms.common.utils.HikariSupplier;
import me.lucko.luckperms.common.utils.Predicates;
import me.lucko.luckperms.common.utils.ProgressLogger;
import org.bukkit.Bukkit;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.HashMap;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import static me.lucko.luckperms.common.constants.Permission.MIGRATION;
// Only supports the latest versions of the PP API. (it seems to change randomly almost every release)
public class MigrationPowerfulPerms extends SubCommand<Object> {
private static Method getPlayerPermissionsMethod = null;
private static Method getPlayerGroupsMethod = null;
private static Method getGroupMethod = null;
// lol, nice "api"
private static boolean superLegacy = false;
private static boolean legacy = false;
static {
try {
Class.forName("com.github.cheesesoftware.PowerfulPermsAPI.ResponseRunnable");
legacy = true;
} catch (ClassNotFoundException ignored) {}
if (legacy) {
try {
getPlayerPermissionsMethod = PermissionManager.class.getMethod("getPlayerOwnPermissions", UUID.class, ResultRunnable.class);
getPlayerPermissionsMethod.setAccessible(true);
} catch (NoSuchMethodException ignored) {}
} else {
try {
getPlayerPermissionsMethod = PermissionManager.class.getMethod("getPlayerOwnPermissions", UUID.class);
getPlayerPermissionsMethod.setAccessible(true);
} catch (NoSuchMethodException ignored) {}
}
try {
getGroupMethod = CachedGroup.class.getMethod("getGroup");
getGroupMethod.setAccessible(true);
superLegacy = true;
} catch (NoSuchMethodException ignored) {}
if (!legacy) {
try {
getPlayerGroupsMethod = PermissionManager.class.getMethod("getPlayerOwnGroups", UUID.class);
getPlayerGroupsMethod.setAccessible(true);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
} else {
try {
getPlayerGroupsMethod = PermissionManager.class.getMethod("getPlayerOwnGroups", UUID.class, ResultRunnable.class);
getPlayerGroupsMethod.setAccessible(true);
} catch (NoSuchMethodException e) {
try {
getPlayerGroupsMethod = PermissionManager.class.getMethod("getPlayerGroups", UUID.class, ResultRunnable.class);
getPlayerGroupsMethod.setAccessible(true);
} catch (NoSuchMethodException e1) {
e1.printStackTrace();
}
}
}
}
public MigrationPowerfulPerms() {
super("powerfulperms", "Migration from PowerfulPerms", MIGRATION, Predicates.not(5),
Arg.list(
@@ -136,44 +85,37 @@ public class MigrationPowerfulPerms extends SubCommand<Object> {
@Override
public CommandResult execute(LuckPermsPlugin plugin, Sender sender, Object o, List<String> args, String label) throws CommandException {
try {
return run(plugin, sender, args);
} catch (Throwable t) {
t.printStackTrace();
return CommandResult.FAILURE;
}
}
private CommandResult run(LuckPermsPlugin plugin, Sender sender, List<String> args) {
ProgressLogger log = new ProgressLogger("PowerfulPerms");
log.addListener(plugin.getConsoleSender());
log.addListener(sender);
log.log("Starting.");
if (!Bukkit.getPluginManager().isPluginEnabled("PowerfulPerms")) {
log.logErr("PowerfulPerms is not loaded.");
log.logErr("Plugin not loaded.");
return CommandResult.STATE_ERROR;
}
final String address = args.get(0);
final String database = args.get(1);
final String username = args.get(2);
final String password = args.get(3);
final String dbTable = args.get(4);
String method = plugin.getConfiguration().get(ConfigKeys.STORAGE_METHOD);
StorageType type = StorageType.parse(method);
if (type == null || type != StorageType.MYSQL) {
// We need to load the Hikari/MySQL stuff.
DependencyManager.loadDependencies(plugin, DependencyManager.STORAGE_DEPENDENCIES.get(StorageType.MYSQL));
}
String address = args.get(0);
String database = args.get(1);
String username = args.get(2);
String password = args.get(3);
String dbTable = args.get(4);
// Find a list of UUIDs
log.log("Getting a list of UUIDs to migrate.");
Set<UUID> uuids = new HashSet<>();
try (HikariDataSource hikari = new HikariDataSource()) {
hikari.setMaximumPoolSize(2);
hikari.setDataSourceClassName("com.mysql.jdbc.jdbc2.optional.MysqlDataSource");
hikari.addDataSourceProperty("serverName", address.split(":")[0]);
hikari.addDataSourceProperty("port", address.split(":")[1]);
hikari.addDataSourceProperty("databaseName", database);
hikari.addDataSourceProperty("user", username);
hikari.addDataSourceProperty("password", password);
try (HikariSupplier hikari = new HikariSupplier(address, database, username, password)) {
hikari.setup("powerfulperms-migrator-pool");
try (Connection c = hikari.getConnection()) {
DatabaseMetaData meta = c.getMetaData();
@@ -220,23 +162,54 @@ public class MigrationPowerfulPerms extends SubCommand<Object> {
PowerfulPermsPlugin ppPlugin = (PowerfulPermsPlugin) Bukkit.getPluginManager().getPlugin("PowerfulPerms");
PermissionManager pm = ppPlugin.getPermissionManager();
Collection<Group> groups = pm.getGroups().values();
int maxWeight = 0;
// Groups first.
log.log("Starting group migration.");
AtomicInteger groupCount = new AtomicInteger(0);
Map<Integer, Group> groups = pm.getGroups(); // All versions
for (Group g : groups.values()) {
plugin.getStorage().createAndLoadGroup(g.getName().toLowerCase(), CreationCause.INTERNAL).join();
final me.lucko.luckperms.common.core.model.Group group = plugin.getGroupManager().getIfLoaded(g.getName().toLowerCase());
for (Group g : groups) {
maxWeight = Math.max(maxWeight, g.getRank());
for (Permission p : g.getOwnPermissions()) { // All versions
applyPerm(group, p, log);
final String groupName = MigrationUtils.standardizeName(g.getName());
plugin.getStorage().createAndLoadGroup(groupName, CreationCause.INTERNAL).join();
final me.lucko.luckperms.common.core.model.Group group = plugin.getGroupManager().getIfLoaded(groupName);
MigrationUtils.setGroupWeight(group, g.getRank());
for (Permission p : g.getOwnPermissions()) {
applyPerm(group, p);
}
for (Group parent : g.getParents()) { // All versions
try {
group.setPermission("group." + parent.getName().toLowerCase(), true);
} catch (Exception ex) {
log.handleException(ex);
for (Group parent : g.getParents()) {
group.setPermission(NodeFactory.make("group." + parent.getName().toLowerCase(), true));
}
// server --> prefix afaik
for (Map.Entry<String, String> prefix : g.getPrefixes().entrySet()) {
String server = prefix.getKey().toLowerCase();
if (prefix.getKey().equals("*") || prefix.getKey().equals("all")) {
server = null;
}
if (server != null) {
group.setPermission(NodeFactory.makePrefixNode(g.getRank(), prefix.getValue()).setServer(server).build());
} else {
group.setPermission(NodeFactory.makePrefixNode(g.getRank(), prefix.getValue()).build());
}
}
for (Map.Entry<String, String> suffix : g.getSuffixes().entrySet()) {
String server = suffix.getKey().toLowerCase();
if (suffix.getKey().equals("*") || suffix.getKey().equals("all")) {
server = null;
}
if (server != null) {
group.setPermission(NodeFactory.makeSuffixNode(g.getRank(), suffix.getValue()).setServer(server).build());
} else {
group.setPermission(NodeFactory.makeSuffixNode(g.getRank(), suffix.getValue()).build());
}
}
@@ -245,291 +218,134 @@ public class MigrationPowerfulPerms extends SubCommand<Object> {
}
log.log("Migrated " + groupCount.get() + " groups");
// Now users.
// Migrate all users
log.log("Starting user migration.");
final Map<UUID, CountDownLatch> progress = new HashMap<>();
AtomicInteger userCount = new AtomicInteger(0);
// Increment the max weight from the group migrations. All user meta should override.
maxWeight += 5;
// Migrate all users and their groups
for (UUID uuid : uuids) {
progress.put(uuid, new CountDownLatch(2));
// Create a LuckPerms user for the UUID
plugin.getStorage().loadUser(uuid, "null").join();
User user = plugin.getUserManager().get(uuid);
User user = plugin.getUserManager().getIfLoaded(uuid);
// Get a list of Permissions held by the user from the PP API.
getPlayerPermissions(pm, uuid, perms -> { // Changes each version
perms.forEach(p -> applyPerm(user, p, log));
List<Permission> permissions = joinFuture(pm.getPlayerOwnPermissions(uuid));
// Update the progress so the user can be saved and unloaded.
synchronized (progress) {
progress.get(uuid).countDown();
if (progress.get(uuid).getCount() == 0) {
plugin.getStorage().saveUser(user);
plugin.getUserManager().cleanup(user);
}
}
});
for (Permission p : permissions) {
applyPerm(user, p);
}
// Migrate the user's groups to LuckPerms from PP.
Consumer<Map<String, List<CachedGroup>>> callback = groups1 -> {
for (Map.Entry<String, List<CachedGroup>> e : groups1.entrySet()) {
final String server;
if (e.getKey() != null && (e.getKey().equals("") || e.getKey().equalsIgnoreCase("all"))) {
server = null;
} else {
server = e.getKey();
}
if (superLegacy) {
e.getValue().stream()
.filter(cg -> !cg.isNegated())
.map(cg -> {
try {
return (Group) getGroupMethod.invoke(cg);
} catch (IllegalAccessException | InvocationTargetException e1) {
e1.printStackTrace();
return null;
}
})
.forEach(g -> {
if (g != null) {
if (server == null) {
try {
user.setPermission("group." + g.getName().toLowerCase(), true);
} catch (Exception ex) {
log.handleException(ex);
}
} else {
try {
user.setPermission("group." + g.getName().toLowerCase(), true, server);
} catch (Exception ex) {
log.handleException(ex);
}
}
}
});
} else {
e.getValue().stream()
.filter(g -> !g.hasExpired() && !g.isNegated())
.forEach(g -> {
final Group group = pm.getGroup(g.getGroupId());
if (g.willExpire()) {
if (server == null) {
try {
user.setPermission("group." + group.getName().toLowerCase(), true, g.getExpirationDate().getTime() / 1000L);
} catch (Exception ex) {
log.handleException(ex);
}
} else {
try {
user.setPermission("group." + group.getName().toLowerCase(), true, server, g.getExpirationDate().getTime() / 1000L);
} catch (Exception ex) {
log.handleException(ex);
}
}
} else {
if (server == null) {
try {
user.setPermission("group." + group.getName().toLowerCase(), true);
} catch (Exception ex) {
log.handleException(ex);
}
} else {
try {
user.setPermission("group." + group.getName().toLowerCase(), true, server);
} catch (Exception ex) {
log.handleException(ex);
}
}
}
});
}
// server --> list of groups
Map<String, List<CachedGroup>> parents = joinFuture(pm.getPlayerOwnGroups(uuid));
for (Map.Entry<String, List<CachedGroup>> parent : parents.entrySet()) {
String server = parent.getKey().toLowerCase();
if (parent.getKey().equals("*") || parent.getKey().equals("all")) {
server = null;
}
// Update the progress so the user can be saved and unloaded.
synchronized (progress) {
progress.get(uuid).countDown();
if (progress.get(uuid).getCount() == 0) {
plugin.getStorage().saveUser(user);
plugin.getUserManager().cleanup(user);
}
}
};
if (!legacy) {
try {
ListenableFuture<LinkedHashMap<String, List<CachedGroup>>> future = (ListenableFuture<LinkedHashMap<String, List<CachedGroup>>>) getPlayerGroupsMethod.invoke(pm, uuid);
try {
if (future.isDone()) {
callback.accept(future.get());
} else {
future.addListener(() -> {
try {
callback.accept(future.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}, Runnable::run);
}
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
} catch (IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
} else {
try {
getPlayerGroupsMethod.invoke(pm, uuid, new LPResultRunnable<LinkedHashMap<String, List<CachedGroup>>>() {
@Override
public void run() {
callback.accept(getResult());
}
});
} catch (IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
for (CachedGroup group : parent.getValue()) {
applyGroup(pm, user, group, server);
}
}
String prefix = joinFuture(pm.getPlayerOwnPrefix(uuid));
String suffix = joinFuture(pm.getPlayerOwnSuffix(uuid));
if (prefix != null && !prefix.equals("")) {
user.setPermission(NodeFactory.makePrefixNode(maxWeight, prefix).build());
}
if (suffix != null && !suffix.equals("")) {
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);
}
plugin.getUserManager().cleanup(user);
plugin.getStorage().saveUser(user);
log.logProgress("Migrated {} users so far.", userCount.incrementAndGet());
}
// All groups are migrated, but there may still be some users being migrated.
// This block will wait for all users to be completed.
log.log("Waiting for user migration to complete. This may take some time");
boolean sleep = true;
while (sleep) {
sleep = false;
for (Map.Entry<UUID, CountDownLatch> e : progress.entrySet()) {
if (e.getValue().getCount() != 0) {
sleep = true;
break;
}
}
if (sleep) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
// We done.
log.log("Migrated " + userCount.get() + " users.");
log.log("Success! Migration complete.");
return CommandResult.SUCCESS;
}
private void applyPerm(PermissionHolder holder, Permission p, ProgressLogger log) {
private void applyPerm(PermissionHolder holder, Permission p) {
String node = p.getPermissionString();
boolean value = true;
if (node.startsWith("!")) {
if (node.startsWith("!") || node.startsWith("-")) {
node = node.substring(1);
value = false;
}
String server = p.getServer();
if (server != null && server.equalsIgnoreCase("all")) {
if (server != null && (server.equalsIgnoreCase("all") || server.equalsIgnoreCase("*"))) {
server = null;
}
String world = p.getWorld();
if (world != null && world.equalsIgnoreCase("all")) {
if (world != null && (world.equalsIgnoreCase("all") || world.equalsIgnoreCase("*"))) {
world = null;
}
long expireAt = 0L;
if (!superLegacy) {
if (p.willExpire()) {
expireAt = p.getExpirationDate().getTime() / 1000L;
}
if (p.willExpire()) {
expireAt = p.getExpirationDate().getTime() / 1000L;
}
if (world != null && server == null) {
server = "global";
}
if (world != null) {
if (expireAt == 0L) {
try {
holder.setPermission(node, value, server, world);
} catch (Exception ex) {
log.handleException(ex);
}
} else {
try {
holder.setPermission(node, value, server, world, expireAt);
} catch (Exception ex) {
log.handleException(ex);
}
}
Node.Builder nb = NodeFactory.newBuilder(node).setValue(value);
} else if (server != null) {
if (expireAt == 0L) {
try {
holder.setPermission(node, value, server);
} catch (Exception ex) {
log.handleException(ex);
}
} else {
try {
holder.setPermission(node, value, server, expireAt);
} catch (Exception ex) {
log.handleException(ex);
}
}
} else {
if (expireAt == 0L) {
try {
holder.setPermission(node, value);
} catch (Exception ex) {
log.handleException(ex);
}
} else {
try {
holder.setPermission(node, value, expireAt);
} catch (Exception ex) {
log.handleException(ex);
}
}
if (expireAt != 0) {
nb.setExpiry(expireAt);
}
if (server != null) {
nb.setServer(server);
}
if (world != null) {
nb.setWorld(world);
}
holder.setPermission(nb.build());
}
private static void getPlayerPermissions(PermissionManager manager, UUID uuid, Consumer<List<Permission>> callback) {
if (legacy) {
try {
getPlayerPermissionsMethod.invoke(manager, uuid, new LPResultRunnable<List<Permission>>() {
@Override
public void run() {
callback.accept(getResult());
}
});
} catch (IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
} else {
try {
ListenableFuture<List<Permission>> lf = (ListenableFuture<List<Permission>>) getPlayerPermissionsMethod.invoke(manager, uuid);
try {
if (lf.isDone()) {
callback.accept(lf.get());
} else {
lf.addListener(() -> {
try {
callback.accept(lf.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}, Runnable::run);
}
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
} catch (IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
private void applyGroup(PermissionManager pm, PermissionHolder holder, CachedGroup g, String server) {
Group group = pm.getGroup(g.getGroupId());
String node = "group." + MigrationUtils.standardizeName(group.getName());
long expireAt = 0L;
if (g.willExpire()) {
expireAt = g.getExpirationDate().getTime() / 1000L;
}
Node.Builder nb = NodeFactory.newBuilder(node);
if (expireAt != 0) {
nb.setExpiry(expireAt);
}
if (server != null) {
nb.setServer(server);
}
holder.setPermission(nb.build());
}
@SneakyThrows
private static <T> T joinFuture(Future<T> future) {
return future.get();
}
}
@@ -1,5 +1,8 @@
/*
* Copyright (c) 2016 Lucko (Luck) <luck@lucko.me>
* 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
@@ -22,11 +25,15 @@
package me.lucko.luckperms.bukkit.migration;
import me.lucko.luckperms.api.ChatMetaType;
import me.lucko.luckperms.api.Node;
import me.lucko.luckperms.api.event.cause.CreationCause;
import me.lucko.luckperms.common.commands.CommandException;
import me.lucko.luckperms.common.commands.CommandResult;
import me.lucko.luckperms.common.commands.SubCommand;
import me.lucko.luckperms.common.commands.abstraction.SubCommand;
import me.lucko.luckperms.common.commands.impl.migration.MigrationUtils;
import me.lucko.luckperms.common.commands.sender.Sender;
import me.lucko.luckperms.common.commands.utils.Util;
import me.lucko.luckperms.common.constants.Permission;
import me.lucko.luckperms.common.core.NodeFactory;
import me.lucko.luckperms.common.core.model.Group;
@@ -36,19 +43,21 @@ import me.lucko.luckperms.common.core.model.User;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.utils.Predicates;
import me.lucko.luckperms.common.utils.ProgressLogger;
import me.lucko.luckperms.exceptions.ObjectAlreadyHasException;
import org.bukkit.Bukkit;
import org.tyrannyofheaven.bukkit.zPermissions.ZPermissionsService;
import org.tyrannyofheaven.bukkit.zPermissions.dao.PermissionService;
import org.tyrannyofheaven.bukkit.zPermissions.model.EntityMetadata;
import org.tyrannyofheaven.bukkit.zPermissions.model.Entry;
import org.tyrannyofheaven.bukkit.zPermissions.model.Inheritance;
import org.tyrannyofheaven.bukkit.zPermissions.model.Membership;
import org.tyrannyofheaven.bukkit.zPermissions.model.PermissionEntity;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
@@ -59,19 +68,19 @@ public class MigrationZPermissions extends SubCommand<Object> {
@Override
public CommandResult execute(LuckPermsPlugin plugin, Sender sender, Object o, List<String> args, String label) throws CommandException {
ProgressLogger log = new ProgressLogger("PermissionManager");
ProgressLogger log = new ProgressLogger("zPermissions");
log.addListener(plugin.getConsoleSender());
log.addListener(sender);
log.log("Starting.");
if (!Bukkit.getPluginManager().isPluginEnabled("zPermissions")) {
log.logErr("zPermissions is not loaded.");
log.logErr("Plugin not loaded.");
return CommandResult.STATE_ERROR;
}
if (!Bukkit.getServicesManager().isProvidedFor(ZPermissionsService.class)) {
log.logErr("zPermissions is not loaded.");
log.logErr("Plugin not loaded.");
return CommandResult.STATE_ERROR;
}
@@ -89,13 +98,37 @@ public class MigrationZPermissions extends SubCommand<Object> {
// Migrate all groups
log.log("Starting group migration.");
AtomicInteger groupCount = new AtomicInteger(0);
for (String g : service.getAllGroups()) {
plugin.getStorage().createAndLoadGroup(g.toLowerCase(), CreationCause.INTERNAL).join();
Group group = plugin.getGroupManager().getIfLoaded(g.toLowerCase());
PermissionEntity entity = internalService.getEntity(g, null, true);
migrateEntity(group, entity, null);
Map<UUID, Set<Node>> userParents = new HashMap<>();
AtomicInteger groupCount = new AtomicInteger(0);
int maxWeight = 0;
for (PermissionEntity entity : internalService.getEntities(true)) {
String groupName = MigrationUtils.standardizeName(entity.getDisplayName());
plugin.getStorage().createAndLoadGroup(groupName, CreationCause.INTERNAL).join();
Group group = plugin.getGroupManager().getIfLoaded(groupName);
int weight = entity.getPriority();
maxWeight = Math.max(maxWeight, weight);
migrateEntity(group, entity, weight);
MigrationUtils.setGroupWeight(group, weight);
// store user data for later
Set<Membership> members = entity.getMemberships();
for (Membership membership : members) {
UUID uuid = Util.parseUuid(membership.getMember());
if (uuid == null) {
continue;
}
Set<Node> nodes = userParents.computeIfAbsent(uuid, u -> new HashSet<>());
if (membership.getExpiration() == null) {
nodes.add(NodeFactory.make("group." + groupName));
} else {
long expiry = membership.getExpiration().toInstant().getEpochSecond();
nodes.add(NodeFactory.newBuilder("group." + groupName).setExpiry(expiry).build());
}
}
plugin.getStorage().saveGroup(group);
log.logAllProgress("Migrated {} groups so far.", groupCount.incrementAndGet());
@@ -106,30 +139,49 @@ public class MigrationZPermissions extends SubCommand<Object> {
log.log("Starting track migration.");
AtomicInteger trackCount = new AtomicInteger(0);
for (String t : service.getAllTracks()) {
plugin.getStorage().createAndLoadTrack(t.toLowerCase(), CreationCause.INTERNAL).join();
Track track = plugin.getTrackManager().getIfLoaded(t.toLowerCase());
String trackName = MigrationUtils.standardizeName(t);
plugin.getStorage().createAndLoadTrack(trackName, CreationCause.INTERNAL).join();
Track track = plugin.getTrackManager().getIfLoaded(trackName);
track.setGroups(service.getTrackGroups(t));
plugin.getStorage().saveTrack(track);
log.logAllProgress("Migrated {} tracks so far.", trackCount.incrementAndGet());
}
log.log("Migrated " + trackCount.get() + " tracks");
// Migrate all users.
log.log("Starting user migration.");
maxWeight += 10;
AtomicInteger userCount = new AtomicInteger(0);
for (UUID u : service.getAllPlayersUUID()) {
plugin.getStorage().loadUser(u, "null").join();
User user = plugin.getUserManager().get(u);
Set<UUID> usersToMigrate = new HashSet<>(userParents.keySet());
usersToMigrate.addAll(service.getAllPlayersUUID());
for (UUID u : usersToMigrate) {
PermissionEntity entity = internalService.getEntity(null, u, false);
migrateEntity(user, entity, internalService.getGroups(u));
user.setPrimaryGroup(service.getPlayerPrimaryGroup(u));
if (!entity.isGroup()) {
user.setName(entity.getDisplayName());
String username = null;
if (entity != null) {
username = entity.getDisplayName();
}
plugin.getStorage().loadUser(u, username).join();
User user = plugin.getUserManager().getIfLoaded(u);
// migrate permissions & meta
if (entity != null) {
migrateEntity(user, entity, maxWeight);
}
// migrate groups
Set<Node> parents = userParents.get(u);
if (parents != null) {
parents.forEach(user::setPermission);
}
user.getPrimaryGroup().setStoredValue(MigrationUtils.standardizeName(service.getPlayerPrimaryGroup(u)));
plugin.getUserManager().cleanup(user);
plugin.getStorage().saveUser(user);
log.logProgress("Migrated {} users so far.", userCount.incrementAndGet());
@@ -140,41 +192,32 @@ public class MigrationZPermissions extends SubCommand<Object> {
return CommandResult.SUCCESS;
}
private void migrateEntity(PermissionHolder group, PermissionEntity entity, List<Membership> memberships) {
private void migrateEntity(PermissionHolder holder, PermissionEntity entity, int weight) {
for (Entry e : entity.getPermissions()) {
if (e.getWorld() != null) {
try {
group.setPermission(e.getPermission(), true, "global", e.getWorld().getName());
} catch (ObjectAlreadyHasException ignored) {}
if (e.getWorld() != null && !e.getWorld().getName().equals("")) {
holder.setPermission(NodeFactory.newBuilder(e.getPermission()).setValue(e.isValue()).setWorld(e.getWorld().getName()).build());
} else {
try {
group.setPermission(e.getPermission(), true); // TODO handle negated.
} catch (ObjectAlreadyHasException ignored) {}
holder.setPermission(NodeFactory.newBuilder(e.getPermission()).setValue(e.isValue()).build());
}
}
// only migrate inheritances for groups
if (entity.isGroup()) {
for (Inheritance inheritance : entity.getInheritancesAsChild()) {
try {
if (!inheritance.getParent().getName().equals(group.getObjectName())) {
group.setPermission("group." + inheritance.getParent().getName(), true);
}
} catch (ObjectAlreadyHasException ignored) {}
}
} else {
// entity.getMemberships() doesn't work (always returns 0 records)
for (Membership membership : memberships) {
try {
group.setPermission("group." + membership.getGroup().getDisplayName(), true);
} catch (ObjectAlreadyHasException ignored) {}
for (PermissionEntity inheritance : entity.getParents()) {
if (!inheritance.getDisplayName().equals(holder.getObjectName())) {
holder.setPermission(NodeFactory.make("group." + MigrationUtils.standardizeName(inheritance.getDisplayName())));
}
}
}
for (EntityMetadata metadata : entity.getMetadata()) {
try {
group.setPermission(NodeFactory.makeMetaNode(metadata.getName(), metadata.getStringValue()).build());
} catch (Exception e) {
e.printStackTrace();
String key = metadata.getName().toLowerCase();
if (key.equals("prefix") || key.equals("suffix")) {
ChatMetaType type = ChatMetaType.valueOf(key.toUpperCase());
holder.setPermission(NodeFactory.makeChatMetaNode(type, weight, metadata.getStringValue()).build());
} else {
holder.setPermission(NodeFactory.makeMetaNode(key, metadata.getStringValue()).build());
}
}
}
@@ -1,5 +1,8 @@
/*
* Copyright (c) 2016 Lucko (Luck) <luck@lucko.me>
* 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
@@ -1,5 +1,8 @@
/*
* Copyright (c) 2016 Lucko (Luck) <luck@lucko.me>
* 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
@@ -1,5 +1,8 @@
/*
* Copyright (c) 2016 Lucko (Luck) <luck@lucko.me>
* 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
@@ -1,5 +1,8 @@
/*
* Copyright (c) 2016 Lucko (Luck) <luck@lucko.me>
* 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
@@ -1,5 +1,8 @@
/*
* Copyright (c) 2016 Lucko (Luck) <luck@lucko.me>
* 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
@@ -24,7 +27,8 @@ package me.lucko.luckperms.bukkit.model;
import lombok.experimental.UtilityClass;
import org.bukkit.Bukkit;
import me.lucko.luckperms.bukkit.compat.ReflectionUtil;
import org.bukkit.entity.Player;
import org.bukkit.permissions.PermissibleBase;
import org.bukkit.permissions.PermissionAttachment;
@@ -38,46 +42,62 @@ import java.util.concurrent.ConcurrentHashMap;
/**
* Injects a {@link LPPermissible} into a {@link Player}
*/
@SuppressWarnings("unchecked")
@UtilityClass
public class Injector {
private static final Map<UUID, LPPermissible> INJECTED_PERMISSIBLES = new ConcurrentHashMap<>();
private static Field HUMAN_ENTITY_FIELD;
private static Field PERMISSIBLEBASE_ATTACHMENTS;
private static Field humanEntityField;
private static Field permissibleAttachmentsField;
private static Throwable cachedThrowable = null;
static {
try {
HUMAN_ENTITY_FIELD = Class.forName(getVersionedClassName("entity.CraftHumanEntity")).getDeclaredField("perm");
HUMAN_ENTITY_FIELD.setAccessible(true);
try {
// craftbukkit
humanEntityField = ReflectionUtil.obcClass("entity.CraftHumanEntity").getDeclaredField("perm");
humanEntityField.setAccessible(true);
} catch (Exception e) {
// glowstone
humanEntityField = Class.forName("net.glowstone.entity.GlowHumanEntity").getDeclaredField("permissions");
humanEntityField.setAccessible(true);
}
PERMISSIBLEBASE_ATTACHMENTS = PermissibleBase.class.getDeclaredField("attachments");
PERMISSIBLEBASE_ATTACHMENTS.setAccessible(true);
permissibleAttachmentsField = PermissibleBase.class.getDeclaredField("attachments");
permissibleAttachmentsField.setAccessible(true);
} catch (Exception e) {
e.printStackTrace();
} catch (Throwable t) {
cachedThrowable = t;
t.printStackTrace();
}
}
public static boolean inject(Player player, LPPermissible lpPermissible) {
if (cachedThrowable != null) {
cachedThrowable.printStackTrace();
return false;
}
try {
PermissibleBase existing = (PermissibleBase) HUMAN_ENTITY_FIELD.get(player);
PermissibleBase existing = (PermissibleBase) humanEntityField.get(player);
if (existing instanceof LPPermissible) {
// uh oh
throw new IllegalStateException();
throw new IllegalStateException("LPPermissible already injected into player " + player.toString());
}
// Move attachments over from the old permissible.
List<PermissionAttachment> attachments = (List<PermissionAttachment>) PERMISSIBLEBASE_ATTACHMENTS.get(existing);
List<PermissionAttachment> attachments = (List<PermissionAttachment>) permissibleAttachmentsField.get(existing);
lpPermissible.addAttachments(attachments);
attachments.clear();
existing.clearPermissions();
lpPermissible.recalculatePermissions();
lpPermissible.getActive().set(true);
lpPermissible.recalculatePermissions(false);
lpPermissible.setOldPermissible(existing);
lpPermissible.updateSubscriptionsAsync();
HUMAN_ENTITY_FIELD.set(player, lpPermissible);
humanEntityField.set(player, lpPermissible);
INJECTED_PERMISSIBLES.put(player.getUniqueId(), lpPermissible);
return true;
} catch (Exception e) {
@@ -87,8 +107,13 @@ public class Injector {
}
public static boolean unInject(Player player, boolean dummy, boolean unsubscribe) {
if (cachedThrowable != null) {
cachedThrowable.printStackTrace();
return false;
}
try {
PermissibleBase permissible = (PermissibleBase) HUMAN_ENTITY_FIELD.get(player);
PermissibleBase permissible = (PermissibleBase) humanEntityField.get(player);
if (permissible instanceof LPPermissible) {
permissible.clearPermissions();
@@ -97,8 +122,10 @@ public class Injector {
((LPPermissible) permissible).unsubscribeFromAllAsync();
}
((LPPermissible) permissible).getActive().set(false);
if (dummy) {
HUMAN_ENTITY_FIELD.set(player, new DummyPermissibleBase());
humanEntityField.set(player, new DummyPermissibleBase());
} else {
LPPermissible lpp = ((LPPermissible) permissible);
List<PermissionAttachment> attachments = lpp.getAttachments();
@@ -108,11 +135,11 @@ public class Injector {
newPb = new PermissibleBase(player);
}
List<PermissionAttachment> newAttachments = (List<PermissionAttachment>) PERMISSIBLEBASE_ATTACHMENTS.get(newPb);
List<PermissionAttachment> newAttachments = (List<PermissionAttachment>) permissibleAttachmentsField.get(newPb);
newAttachments.addAll(attachments);
attachments.clear();
HUMAN_ENTITY_FIELD.set(player, newPb);
humanEntityField.set(player, newPb);
}
}
INJECTED_PERMISSIBLES.remove(player.getUniqueId());
@@ -127,22 +154,4 @@ public class Injector {
return INJECTED_PERMISSIBLES.get(uuid);
}
private static String getVersionedClassName(String className) {
Class server = Bukkit.getServer().getClass();
if (!server.getSimpleName().equals("CraftServer")) {
throw new RuntimeException("Couldn't inject into server " + server);
}
String version;
if (server.getName().equals("org.bukkit.craftbukkit.CraftServer")) {
// Non versioned class
version = ".";
} else {
version = server.getName().substring("org.bukkit.craftbukkit".length());
version = version.substring(0, version.length() - "CraftServer".length());
}
return "org.bukkit.craftbukkit" + version + className;
}
}
@@ -1,5 +1,8 @@
/*
* Copyright (c) 2016 Lucko (Luck) <luck@lucko.me>
* 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
@@ -49,6 +52,7 @@ 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;
@@ -66,11 +70,13 @@ public class LPPermissible extends PermissibleBase {
@Setter
private PermissibleBase oldPermissible = null;
private final AtomicBoolean active = new AtomicBoolean(false);
// Attachment stuff.
private final Map<String, PermissionAttachmentInfo> attachmentPermissions = new ConcurrentHashMap<>();
private final List<PermissionAttachment> attachments = Collections.synchronizedList(new LinkedList<>());
public LPPermissible(@NonNull Player parent, User user, LPBukkitPlugin plugin) {
public LPPermissible(@NonNull Player parent, @NonNull User user, @NonNull LPBukkitPlugin plugin) {
super(parent);
this.user = user;
this.parent = parent;
@@ -81,10 +87,18 @@ public class LPPermissible extends PermissibleBase {
}
public void updateSubscriptionsAsync() {
if (!active.get()) {
return;
}
plugin.doAsync(this::updateSubscriptions);
}
public void updateSubscriptions() {
if (!active.get()) {
return;
}
UserCache cache = user.getUserData();
if (cache == null) {
return;
@@ -110,9 +124,7 @@ public class LPPermissible extends PermissibleBase {
}
public void addAttachments(List<PermissionAttachment> attachments) {
for (PermissionAttachment attachment : attachments) {
this.attachments.add(attachment);
}
this.attachments.addAll(attachments);
}
public Contexts calculateContexts() {
@@ -127,10 +139,6 @@ public class LPPermissible extends PermissibleBase {
);
}
private boolean hasData() {
return user.getUserData() != null;
}
@Override
public void setOp(boolean value) {
parent.setOp(value);
@@ -138,7 +146,7 @@ public class LPPermissible extends PermissibleBase {
@Override
public boolean isPermissionSet(@NonNull String name) {
return hasData() && user.getUserData().getPermissionData(calculateContexts()).getPermissionValue(name) != Tristate.UNDEFINED;
return user.getUserData().getPermissionData(calculateContexts()).getPermissionValue(name) != Tristate.UNDEFINED;
}
@Override
@@ -148,11 +156,9 @@ public class LPPermissible extends PermissibleBase {
@Override
public boolean hasPermission(@NonNull String name) {
if (hasData()) {
Tristate ts = user.getUserData().getPermissionData(calculateContexts()).getPermissionValue(name);
if (ts != Tristate.UNDEFINED) {
return ts.asBoolean();
}
Tristate ts = user.getUserData().getPermissionData(calculateContexts()).getPermissionValue(name);
if (ts != Tristate.UNDEFINED) {
return ts.asBoolean();
}
return Permission.DEFAULT_PERMISSION.getValue(isOp());
@@ -160,11 +166,9 @@ public class LPPermissible extends PermissibleBase {
@Override
public boolean hasPermission(@NonNull Permission perm) {
if (hasData()) {
Tristate ts = user.getUserData().getPermissionData(calculateContexts()).getPermissionValue(perm.getName());
if (ts != Tristate.UNDEFINED) {
return ts.asBoolean();
}
Tristate ts = user.getUserData().getPermissionData(calculateContexts()).getPermissionValue(perm.getName());
if (ts != Tristate.UNDEFINED) {
return ts.asBoolean();
}
return perm.getDefault().getValue(isOp());
@@ -175,13 +179,11 @@ public class LPPermissible extends PermissibleBase {
Set<PermissionAttachmentInfo> perms = new HashSet<>();
perms.addAll(attachmentPermissions.values());
if (hasData()) {
perms.addAll(
user.getUserData().getPermissionData(calculateContexts()).getImmutableBacking().entrySet().stream()
.map(e -> new PermissionAttachmentInfo(parent, e.getKey(), null, e.getValue()))
.collect(Collectors.toList())
);
}
perms.addAll(
user.getUserData().getPermissionData(calculateContexts()).getImmutableBacking().entrySet().stream()
.map(e -> new PermissionAttachmentInfo(parent, e.getKey(), null, e.getValue()))
.collect(Collectors.toList())
);
return perms;
}
@@ -262,6 +264,10 @@ public class LPPermissible extends PermissibleBase {
@Override
public void recalculatePermissions() {
recalculatePermissions(true);
}
public void recalculatePermissions(boolean invalidate) {
if (attachmentPermissions == null) {
return;
}
@@ -272,7 +278,7 @@ public class LPPermissible extends PermissibleBase {
calculateChildPermissions(attachment.getPermissions(), false, attachment);
}
if (hasData()) {
if (invalidate) {
user.getUserData().invalidatePermissionCalculators();
}
}
@@ -1,5 +1,8 @@
/*
* Copyright (c) 2016 Lucko (Luck) <luck@lucko.me>
* 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
@@ -1,5 +1,8 @@
/*
* Copyright (c) 2016 Lucko (Luck) <luck@lucko.me>
* 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
@@ -24,28 +27,20 @@ package me.lucko.luckperms.bukkit.vault;
import lombok.NonNull;
import me.lucko.luckperms.api.ChatMetaType;
import me.lucko.luckperms.api.Contexts;
import me.lucko.luckperms.api.Node;
import me.lucko.luckperms.api.caching.MetaData;
import me.lucko.luckperms.api.context.ContextSet;
import me.lucko.luckperms.common.caching.MetaHolder;
import me.lucko.luckperms.common.caching.MetaAccumulator;
import me.lucko.luckperms.common.core.NodeFactory;
import me.lucko.luckperms.common.core.model.Group;
import me.lucko.luckperms.common.core.model.PermissionHolder;
import me.lucko.luckperms.common.core.model.User;
import me.lucko.luckperms.common.utils.ExtractedContexts;
import me.lucko.luckperms.exceptions.ObjectAlreadyHasException;
import me.lucko.luckperms.exceptions.ObjectLacksException;
import net.milkbowl.vault.chat.Chat;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import static me.lucko.luckperms.api.MetaUtils.escapeCharacters;
import static me.lucko.luckperms.api.MetaUtils.unescapeCharacters;
/**
* LuckPerms Vault Chat implementation
@@ -69,21 +64,12 @@ public class VaultChatHook extends Chat {
private void setMeta(PermissionHolder holder, String world, String node, String value) {
String finalWorld = perms.isIgnoreWorld() ? null : world;
if (holder == null) return;
if (node.equals("")) return;
if (holder == null || node.equals("")) return;
perms.log("Setting meta: '" + node + "' for " + holder.getObjectName() + " on world " + world + ", server " + perms.getServer());
perms.getScheduler().execute(() -> {
List<Node> toRemove = holder.getNodes().stream()
.filter(n -> n.isMeta() && n.getMeta().getKey().equals(node))
.collect(Collectors.toList());
toRemove.forEach(n -> {
try {
holder.unsetPermission(n);
} catch (ObjectLacksException ignored) {}
});
holder.removeIf(n -> n.isMeta() && n.getMeta().getKey().equals(node));
Node.Builder metaNode = NodeFactory.makeMetaNode(node, value).setValue(true);
if (!perms.getServer().equalsIgnoreCase("global")) {
@@ -93,40 +79,28 @@ public class VaultChatHook extends Chat {
metaNode.setWorld(finalWorld);
}
try {
holder.setPermission(metaNode.build());
} catch (ObjectAlreadyHasException ignored) {}
holder.setPermission(metaNode.build());
perms.save(holder);
});
}
private void setChatMeta(boolean prefix, PermissionHolder holder, String value, String world) {
private void setChatMeta(ChatMetaType type, PermissionHolder holder, String value, String world) {
String finalWorld = perms.isIgnoreWorld() ? null : world;
if (holder == null) return;
if (value.equals("")) return;
perms.log("Setting " + (prefix ? "prefix" : "suffix") + " for " + holder.getObjectName() + " on world " + world + ", server " + perms.getServer());
perms.log("Setting " + type.name().toLowerCase() + " for " + holder.getObjectName() + " on world " + world + ", server " + perms.getServer());
perms.getScheduler().execute(() -> {
// remove all prefixes/suffixes directly set on the user/group
List<Node> toRemove = holder.getNodes().stream()
.filter(n -> prefix ? n.isPrefix() : n.isSuffix())
.collect(Collectors.toList());
toRemove.forEach(n -> {
try {
holder.unsetPermission(n);
} catch (ObjectLacksException ignored) {}
});
holder.removeIf(type::matches);
// find the max inherited priority & add 10
MetaHolder metaHolder = holder.accumulateMeta(null, null, ExtractedContexts.generate(perms.createContextForWorld(finalWorld)));
int priority = (prefix ? metaHolder.getPrefixes() : metaHolder.getSuffixes()).keySet().stream()
MetaAccumulator metaAccumulator = holder.accumulateMeta(null, null, ExtractedContexts.generate(perms.createContextForWorldSet(finalWorld)));
int priority = (type == ChatMetaType.PREFIX ? metaAccumulator.getPrefixes() : metaAccumulator.getSuffixes()).keySet().stream()
.mapToInt(e -> e).max().orElse(0) + 10;
Node.Builder chatMetaNode = NodeFactory.makeChatMetaNode(prefix, priority, value);
Node.Builder chatMetaNode = NodeFactory.makeChatMetaNode(type, priority, value);
if (!perms.getServer().equalsIgnoreCase("global")) {
chatMetaNode.setServer(perms.getServer());
}
@@ -134,10 +108,7 @@ public class VaultChatHook extends Chat {
chatMetaNode.setWorld(finalWorld);
}
try {
holder.setPermission(chatMetaNode.build());
} catch (ObjectAlreadyHasException ignored) {}
holder.setPermission(chatMetaNode.build());
perms.save(holder);
});
}
@@ -145,138 +116,115 @@ public class VaultChatHook extends Chat {
private String getUserMeta(User user, String world, String node, String defaultValue) {
if (user == null) return defaultValue;
world = perms.isIgnoreWorld() ? null : world;
node = escapeCharacters(node);
perms.log("Getting meta: '" + node + "' for user " + user.getName() + " on world " + world + ", server " + perms.getServer());
perms.log("Getting meta: '" + node + "' for user " + user.getFriendlyName() + " on world " + world + ", server " + perms.getServer());
if (user.getUserData() == null) {
return defaultValue;
}
String ret = user.getUserData().getMetaData(perms.createContextForWorld(world)).getMeta().get(node);
if (ret == null) {
return defaultValue;
} else {
return unescapeCharacters(ret);
}
String ret = user.getUserData().getMetaData(perms.createContextForWorldLookup(perms.getPlugin().getPlayer(user), world)).getMeta().get(node);
return ret != null ? ret : defaultValue;
}
private String getUserChatMeta(boolean prefix, User user, String world) {
private String getUserChatMeta(ChatMetaType type, User user, String world) {
if (user == null) return "";
world = perms.isIgnoreWorld() ? null : world;
perms.log("Getting " + (prefix ? "prefix" : "suffix") + " for user " + user.getName() + " on world " + world + ", server " + perms.getServer());
perms.log("Getting " + type.name().toLowerCase() + " for user " + user.getFriendlyName() + " on world " + world + ", server " + perms.getServer());
if (user.getUserData() == null) {
return "";
}
MetaData data = user.getUserData().getMetaData(perms.createContextForWorld(world));
String v = prefix ? data.getPrefix() : data.getSuffix();
return v == null ? "" : unescapeCharacters(v);
MetaData data = user.getUserData().getMetaData(perms.createContextForWorldLookup(perms.getPlugin().getPlayer(user), world));
String ret = type == ChatMetaType.PREFIX ? data.getPrefix() : data.getSuffix();
return ret != null ? ret : "";
}
private String getGroupMeta(Group group, String world, String node, String defaultValue) {
world = perms.isIgnoreWorld() ? null : world;
if (group == null) return defaultValue;
if (node.equals("")) return defaultValue;
node = escapeCharacters(node);
if (group == null || node.equals("")) return defaultValue;
perms.log("Getting meta: '" + node + "' for group " + group.getName() + " on world " + world + ", server " + perms.getServer());
for (Node n : group.getPermissions(true)) {
for (Node n : group.mergePermissionsToList()) {
if (!n.getValue()) continue;
if (!n.isMeta()) continue;
if (!n.shouldApplyOnServer(perms.getServer(), perms.isIncludeGlobal(), false)) continue;
if (!n.shouldApplyOnWorld(world, perms.isIncludeGlobal(), false)) continue;
if (!n.shouldApplyWithContext(perms.createContextForWorldLookup(world).getContexts())) continue;
Map.Entry<String, String> meta = n.getMeta();
if (meta.getKey().equalsIgnoreCase(node)) {
return unescapeCharacters(meta.getValue());
return meta.getValue();
}
}
return defaultValue;
}
private String getGroupChatMeta(boolean prefix, Group group, String world) {
private String getGroupChatMeta(ChatMetaType type, Group group, String world) {
world = perms.isIgnoreWorld() ? null : world;
if (group == null) return "";
perms.log("Getting " + (prefix ? "prefix" : "suffix") + " for group " + group + " on world " + world + ", server " + perms.getServer());
perms.log("Getting " + type.name().toLowerCase() + " for group " + group + " on world " + world + ", server " + perms.getServer());
int priority = Integer.MIN_VALUE;
String meta = null;
Map<String, String> context = new HashMap<>();
context.put("server", perms.getServer());
if (world != null) {
context.put("world", world);
}
ExtractedContexts ec = ExtractedContexts.generate(new Contexts(ContextSet.fromMap(context), perms.isIncludeGlobal(), true, true, true, true, false));
for (Node n : group.getAllNodes(null, ec)) {
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;
if (prefix ? !n.isPrefix() : !n.isSuffix()) continue;
if (!n.shouldApplyOnServer(perms.getServer(), perms.isIncludeGlobal(), false)) continue;
if (!n.shouldApplyOnWorld(world, perms.isIncludeGlobal(), false)) continue;
if (type.shouldIgnore(n)) continue;
if (!n.shouldApplyWithContext(perms.createContextForWorldLookup(world).getContexts())) continue;
Map.Entry<Integer, String> value = prefix ? n.getPrefix() : n.getSuffix();
Map.Entry<Integer, String> value = type.getEntry(n);
if (value.getKey() > priority) {
meta = value.getValue();
priority = value.getKey();
}
}
return meta == null ? "" : unescapeCharacters(meta);
return meta != null ? meta : "";
}
@Override
public String getPlayerPrefix(String world, @NonNull String player) {
final User user = perms.getPlugin().getUserManager().getByUsername(player);
return getUserChatMeta(true, user, world);
return getUserChatMeta(ChatMetaType.PREFIX, user, world);
}
@Override
public void setPlayerPrefix(String world, @NonNull String player, @NonNull String prefix) {
final User user = perms.getPlugin().getUserManager().getByUsername(player);
setChatMeta(true, user, prefix, world);
setChatMeta(ChatMetaType.PREFIX, user, prefix, world);
}
@Override
public String getPlayerSuffix(String world, @NonNull String player) {
final User user = perms.getPlugin().getUserManager().getByUsername(player);
return getUserChatMeta(false, user, world);
return getUserChatMeta(ChatMetaType.SUFFIX, user, world);
}
@Override
public void setPlayerSuffix(String world, @NonNull String player, @NonNull String suffix) {
final User user = perms.getPlugin().getUserManager().getByUsername(player);
setChatMeta(false, user, suffix, world);
setChatMeta(ChatMetaType.SUFFIX, user, suffix, world);
}
@Override
public String getGroupPrefix(String world, @NonNull String group) {
final Group g = perms.getPlugin().getGroupManager().getIfLoaded(group);
return getGroupChatMeta(true, g, world);
return getGroupChatMeta(ChatMetaType.PREFIX, g, world);
}
@Override
public void setGroupPrefix(String world, @NonNull String group, @NonNull String prefix) {
final Group g = perms.getPlugin().getGroupManager().getIfLoaded(group);
setChatMeta(true, g, prefix, world);
setChatMeta(ChatMetaType.PREFIX, g, prefix, world);
}
@Override
public String getGroupSuffix(String world, @NonNull String group) {
final Group g = perms.getPlugin().getGroupManager().getIfLoaded(group);
return getGroupChatMeta(false, g, world);
return getGroupChatMeta(ChatMetaType.SUFFIX, g, world);
}
@Override
public void setGroupSuffix(String world, @NonNull String group, @NonNull String suffix) {
final Group g = perms.getPlugin().getGroupManager().getIfLoaded(group);
setChatMeta(false, g, suffix, world);
setChatMeta(ChatMetaType.SUFFIX, g, suffix, world);
}
@Override
@@ -1,5 +1,8 @@
/*
* Copyright (c) 2016 Lucko (Luck) <luck@lucko.me>
* 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
@@ -1,5 +1,8 @@
/*
* Copyright (c) 2016 Lucko (Luck) <luck@lucko.me>
* 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
@@ -26,25 +29,29 @@ import lombok.Getter;
import lombok.NonNull;
import me.lucko.luckperms.api.Contexts;
import me.lucko.luckperms.api.LocalizedNode;
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.ContextSet;
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.core.NodeFactory;
import me.lucko.luckperms.common.core.model.Group;
import me.lucko.luckperms.common.core.model.PermissionHolder;
import me.lucko.luckperms.common.core.model.User;
import me.lucko.luckperms.exceptions.ObjectAlreadyHasException;
import me.lucko.luckperms.exceptions.ObjectLacksException;
import me.lucko.luckperms.common.utils.ExtractedContexts;
import net.milkbowl.vault.permission.Permission;
import java.util.HashMap;
import org.bukkit.entity.Player;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* LuckPerms Vault Permission implementation
@@ -90,15 +97,16 @@ public class VaultPermissionHook extends Permission {
*/
private CompletableFuture<Void> add(String world, PermissionHolder holder, String permission) {
return CompletableFuture.runAsync(() -> {
try {
if (world != null && !world.equals("") && !world.equalsIgnoreCase("global")) {
holder.setPermission(permission, true, getServer(), world);
} else {
holder.setPermission(permission, true, getServer());
}
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()) {
save(holder);
} catch (ObjectAlreadyHasException ignored) {}
}
}, scheduler);
}
@@ -111,15 +119,16 @@ public class VaultPermissionHook extends Permission {
*/
private CompletableFuture<Void> remove(String world, PermissionHolder holder, String permission) {
return CompletableFuture.runAsync(() -> {
try {
if (world != null && !world.equals("") && !world.equalsIgnoreCase("global")) {
holder.unsetPermission(permission, getServer(), world);
} else {
holder.unsetPermission(permission, getServer());
}
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()) {
save(holder);
} catch (ObjectLacksException ignored) {}
}
}, scheduler);
}
@@ -131,21 +140,56 @@ public class VaultPermissionHook extends Permission {
public void save(PermissionHolder holder) {
if (holder instanceof User) {
User u = (User) holder;
plugin.getStorage().saveUser(u).thenRunAsync(() -> u.getRefreshBuffer().request(), plugin.getScheduler().getAsyncExecutor());
plugin.getStorage().saveUser(u).thenRunAsync(() -> u.getRefreshBuffer().request(), plugin.getScheduler().async());
}
if (holder instanceof Group) {
Group g = (Group) holder;
plugin.getStorage().saveGroup(g).thenRunAsync(() -> plugin.getUpdateTaskBuffer().request(), plugin.getScheduler().getAsyncExecutor());
plugin.getStorage().saveGroup(g).thenRunAsync(() -> plugin.getUpdateTaskBuffer().request(), plugin.getScheduler().async());
}
}
public Contexts createContextForWorld(String world) {
Map<String, String> context = new HashMap<>();
public Contexts createContextForWorldSet(String world) {
MutableContextSet context = MutableContextSet.create();
if (world != null && !world.equals("") && !world.equalsIgnoreCase("global")) {
context.put("world", world);
context.add("world", world);
}
context.put("server", getServer());
return new Contexts(ContextSet.fromMap(context), isIncludeGlobal(), true, true, true, true, false);
context.add("server", getServer());
return new Contexts(context, isIncludeGlobal(), true, true, true, true, false);
}
public Contexts createContextForWorldLookup(String world) {
MutableContextSet context = MutableContextSet.create();
if (world != null && !world.equals("") && !world.equalsIgnoreCase("global")) {
context.add("world", world);
}
context.add("server", getServer());
context.addAll(plugin.getConfiguration().getContextsFile().getStaticContexts());
return new Contexts(context, isIncludeGlobal(), true, true, true, true, false);
}
public Contexts createContextForWorldLookup(Player player, String world) {
MutableContextSet context = MutableContextSet.create();
// use player context
if (player != null) {
ImmutableContextSet applicableContext = plugin.getContextManager().getApplicableContext(player);
context.addAll(applicableContext);
} else {
// at least given them the static context defined for this instance
context.addAll(plugin.getConfiguration().getContextsFile().getStaticContexts());
}
// worlds & servers get set depending on the config setting
context.removeAll("world");
context.removeAll("server");
// add the vault settings
if (world != null && !world.equals("") && !world.equalsIgnoreCase("global")) {
context.add("world", world);
}
context.add("server", getServer());
return new Contexts(context, isIncludeGlobal(), true, true, true, true, false);
}
@Override
@@ -156,12 +200,8 @@ public class VaultPermissionHook extends Permission {
User user = plugin.getUserManager().getByUsername(player);
if (user == null) return false;
if (user.getUserData() == null) {
return false;
}
// Effectively fallback to the standard Bukkit #hasPermission check.
return user.getUserData().getPermissionData(createContextForWorld(world)).getPermissionValue(permission).asBoolean();
return user.getUserData().getPermissionData(createContextForWorldLookup(plugin.getPlayer(user), world)).getPermissionValue(permission).asBoolean();
}
@Override
@@ -197,7 +237,7 @@ public class VaultPermissionHook extends Permission {
if (group == null) return false;
// This is a nasty call. Groups aren't cached. :(
Map<String, Boolean> permissions = group.exportNodes(createContextForWorld(world), true);
Map<String, Boolean> permissions = group.exportNodes(ExtractedContexts.generate(createContextForWorldLookup(world)), true);
return permissions.containsKey(permission.toLowerCase()) && permissions.get(permission.toLowerCase());
}
@@ -234,10 +274,9 @@ public class VaultPermissionHook extends Permission {
if (user == null) return false;
String w = world; // screw effectively final
return user.getNodes().stream()
return user.getNodes().values().stream()
.filter(Node::isGroupNode)
.filter(n -> n.shouldApplyOnServer(getServer(), isIncludeGlobal(), false))
.filter(n -> n.shouldApplyOnWorld(w, true, false))
.filter(n -> n.shouldApplyWithContext(createContextForWorldLookup(plugin.getPlayer(user), w).getContexts()))
.map(Node::getGroupName)
.anyMatch(s -> s.equalsIgnoreCase(group));
}
@@ -255,15 +294,16 @@ public class VaultPermissionHook extends Permission {
String w = world;
scheduler.execute(() -> {
try {
if (w != null && !w.equals("") && !w.equalsIgnoreCase("global")) {
user.setInheritGroup(group, getServer(), w);
} else {
user.setInheritGroup(group, getServer());
}
DataMutateResult result;
if (w != null && !w.equals("") && !w.equalsIgnoreCase("global")) {
result = user.setInheritGroup(group, ImmutableContextSet.of("server", getServer(), "world", w));
} else {
result = user.setInheritGroup(group, ImmutableContextSet.singleton("server", getServer()));
}
if (result.asBoolean()) {
save(user);
} catch (ObjectAlreadyHasException ignored) {}
}
});
return true;
}
@@ -281,15 +321,16 @@ public class VaultPermissionHook extends Permission {
String w = world;
scheduler.execute(() -> {
try {
if (w != null && !w.equals("") && !w.equalsIgnoreCase("global")) {
user.unsetInheritGroup(group, getServer(), w);
} else {
user.unsetInheritGroup(group, getServer());
}
DataMutateResult result;
if (w != null && !w.equals("") && !w.equalsIgnoreCase("global")) {
result = user.unsetInheritGroup(group, ImmutableContextSet.of("server", getServer(), "world", w));
} else {
result = user.unsetInheritGroup(group, ImmutableContextSet.singleton("server", getServer()));
}
if (result.asBoolean()) {
save(user);
} catch (ObjectLacksException ignored) {}
}
});
return true;
}
@@ -303,10 +344,9 @@ public class VaultPermissionHook extends Permission {
if (user == null) return new String[0];
String w = world; // screw effectively final
return user.getNodes().stream()
return user.getNodes().values().stream()
.filter(Node::isGroupNode)
.filter(n -> n.shouldApplyOnServer(getServer(), isIncludeGlobal(), false))
.filter(n -> n.shouldApplyOnWorld(w, true, false))
.filter(n -> n.shouldApplyWithContext(createContextForWorldLookup(plugin.getPlayer(user), w).getContexts()))
.map(Node::getGroupName)
.toArray(String[]::new);
}
@@ -323,14 +363,14 @@ public class VaultPermissionHook extends Permission {
// nothing special, just return the value.
if (!isPgo()) {
String g = user.getPrimaryGroup();
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(createContextForWorld(world));
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;
@@ -352,7 +392,7 @@ public class VaultPermissionHook extends Permission {
}
} else {
// we need to check the users permissions only
for (LocalizedNode node : user.getPermissions(true)) {
for (Node node : user.mergePermissionsToList()) {
if (!node.getValue()) continue;
if (!node.getPermission().toLowerCase().startsWith("vault.primarygroup.")) continue;
if (!node.shouldApplyOnServer(getServer(), isIncludeGlobal(), false)) continue;
@@ -366,7 +406,15 @@ public class VaultPermissionHook extends Permission {
}
if (isPgoCheckMemberOf()) {
if (!user.getLocalGroups(getServer(), world, isIncludeGlobal()).contains(group.toLowerCase())) {
String finalWorld = world;
List<String> localGroups = user.mergePermissionsToList().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;
}
}
@@ -376,7 +424,7 @@ public class VaultPermissionHook extends Permission {
}
// Fallback
String g = user.getPrimaryGroup();
String g = user.getPrimaryGroup().getValue();
return plugin.getConfiguration().get(ConfigKeys.GROUP_NAME_REWRITES).getOrDefault(g, g);
}
@@ -1,5 +1,8 @@
/*
* Copyright (c) 2016 Lucko (Luck) <luck@lucko.me>
* 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
+81 -40
View File
@@ -26,26 +26,38 @@ apply-global-groups: true
# If users on this server should have global (non-world specific) groups applied
apply-global-world-groups: true
# If this server is in offline or online mode.
# This setting allows a player to have the same UUID across a network of offline mode/mixed servers.
# If UUIDs should be pulled from the server, or looked up by username based upon previous connections.
#
# You should generally reflect the setting in server.properties here. Except when...
# This option should be set to true in most cases. When set to false, in order to get a player's UUID, LuckPerms will:
# 1. Check if a player with the given username has joined before, if they have, use the UUID they used on their previous login.
# 2. Save and return the players "current" uuid.
#
# 1. You have Spigot servers connected to a BungeeCord proxy, with online-mode set to false, but 'bungeecord' set to true in the spigot.yml
# AND 'ip-forward' set to true in the BungeeCord config.yml
# In this case, set online-mode in LuckPerms to true, despite the server being in offline mode.
# For offline mode (cracked) servers, a players UUID is generated based upon their username.
#
# 2. You are only running one server instance using LuckPerms, (not a network)
# In this case, set online-mode to true no matter what is set in server.properties. (we can just fallback to the servers uuid cache)
# IMPORTANT:
# If you are using BungeeCord proxy running in online mode, it is important that "online-mode=false" is set in server.properties, but
# "bungeecord: true" is set in the spigot.yml. You also need to set "ip_forward: true" in BungeeCord's config.yml.
# If for whatever reason you are not able to do this, and do not have ip-forward enabled, then you may need to set "use-server-uuids" to false.
#
# 3. If your proxy is running in offline mode, and you are using PaperSpigot (https://ci.destroystokyo.com/job/PaperSpigot/),
# you should set "bungee-online-mode" to false in the paper.yml, and set "online-mode" to true in all LuckPerms configs.
# This approach is thoroughly recommended for offline mode networks.
online-mode: true
# If your proxy is running in offline mode, you should still be setting up ip-forwarding as described above, but may also find that you need to set
# "bungee-online-mode" to false in paper.yml, if you are using PaperSpigot. (https://ci.destroystokyo.com/job/PaperSpigot/)
#
# This option only really exists for networks who for whatever reason cannot setup proper ip forwarding.
use-server-uuids: true
# If the plugin should send log notifications to users whenever permissions are modified.
log-notify: true
# Mirrors world names. Whenever LuckPerms checks what world a user is in, if the world name is in this list, the value assigned
# will be sent forward for permission calculation instead.
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"
@@ -54,6 +66,14 @@ log-notify: true
# If "deny": the command will just fail if you try to add another node with the same expiry
temporary-add-behaviour: deny
# How should LuckPerms determine a users "primary" group.
#
# Available Options:
# -> stored use the value stored against the users record in the file/database
# -> 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
@@ -75,6 +95,24 @@ apply-regex: true
# If set to true, LuckPerms will detect and expand shorthand node patterns.
apply-shorthand: true
# If the plugin should apply Bukkit child permissions.
# Plugin authors can define custom permissions structures for their plugin, which will be resolved and used
# by LuckPerms if this setting is enabled.
apply-bukkit-child-permissions: true
# If the plugin should apply Bukkit default permissions.
# Plugin authors can define permissions which should be given to all users by default, or setup permissions
# which should/shouldn't be given to opped players.
# If this option is set to false, LuckPerms will ignore these defaults.
apply-bukkit-default-permissions: true
# If the plugin should apply attachment permissions.
# Other plugins on the server are able to add their own "permission attachments" to players. This allows
# them to grant players additional permissions which last until the end of the session, or until they're removed.
# If this option is set to false, LuckPerms will not include these attachment permissions when considering if a
# player should have access to a certain permission.
apply-bukkit-attachment-permissions: true
# Define special group weights for this server.
# Default is just 0.
group-weight:
@@ -96,6 +134,8 @@ group-weight:
# - lowest_own
# - highest_on_track_<track>
# - lowest_on_track_<track>
# - highest_not_on_track_<track>
# - lowest_not_on_track_<track>
#
# Each element is added in the order listed.
meta-formatting:
@@ -144,8 +184,14 @@ commands-allow-op: true
# | Vault | #
# +------------------------------------------------------------------------+ #
# If the vault-server option below should be used.
# When this option is set to false, the server value defined above under "server" is used.
use-vault-server: false
# The name of the server used within Vault operations. If you don't want Vault operations to be server specific, set this
# to "global".
#
# Will only take effect if use-vault-server is set to true above.
vault-server: global
# If global permissions should be considered when retrieving meta or player groups
@@ -154,32 +200,9 @@ vault-include-global: true
# If Vault operations should ignore any world arguments if supplied.
vault-ignore-world: false
# This block controls the Primary Group override feature
# See the wiki for more information.
vault-primary-groups-overrides:
# If the feature is enabled
enabled: false
# If the check should query the user's inherited permissions.
# (a value of false only checks the permissions they explicitly have)
check-inherited-permissions: false
# If LuckPerms should check if the group exists
check-group-exists: true
# If LuckPerms should check if the user is actually a member of the group
check-user-member-of: true
# If LuckPerms should print debugging info to console when a plugin uses a Vault function
vault-debug: false
# Mirrors world names. Whenever LuckPerms checks what world a user is in, if the world name is in this list, the value assigned
# will be sent forward for permission calculation instead.
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
@@ -188,10 +211,20 @@ group-name-rewrite:
# +------------------------------------------------------------------------+ #
# Which storage method the plugin should use.
# Currently supported: mysql, postgresql, sqlite, h2, json, yaml, mongodb
# Fill out connection info below if you're using MySQL, PostgreSQL or MongoDB
#
# See: https://github.com/lucko/LuckPerms/wiki/Choosing-a-Storage-type
# Currently supported: mysql, mariadb, postgresql, sqlite, h2, json, yaml, 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".
storage-method: h2
# When using a file-based storage type, LuckPerms can monitor the data files for changes, and then schedule automatic
# updates when changes are detected.
#
# If you don't want this to happen, set this option to false.
watch-files: true
# This block enables support for split datastores.
split-storage:
enabled: false
@@ -217,9 +250,14 @@ data:
# This should *not* be set to "lp_" if you have previously ran LuckPerms v2.16.81 or earlier with this database.
table_prefix: 'luckperms_'
# Set to -1 to disable. If this is the only instance accessing the datastore, you can disable syncing.
# e.g. if you're using sqlite or flatfile, this can be set to -1 to save resources.
sync-minutes: 3
# This option controls how frequently LuckPerms will perform a sync task.
# A sync task will refresh all data from the storage, and ensure that the most up-to-date data is being used by the plugin.
#
# This is disabled by default, as most users will not need it. However, if you're using a remote storage type
# without a messaging service setup, you may wish to set this value to something like 3.
#
# Set to -1 to disable the task completely.
sync-minutes: -1
# Settings for the messaging service
#
@@ -237,6 +275,9 @@ data:
# none ==> nothing
messaging-service: none
# If LuckPerms should automatically push updates after a change has been made with a command.
auto-push-updates: true
# Settings for Redis.
# Port 6379 is used by default; set address to "host:port" if differs
redis:
+3 -3
View File
@@ -1,5 +1,5 @@
name: LuckPerms
version: ${release.version}.${patch.version}
version: ${full.version}
description: A permissions plugin
author: Luck
website: https://github.com/lucko/LuckPerms
@@ -16,8 +16,8 @@ softdepend: [LilyPad-Connect]
commands:
luckperms:
description: Manage permissions
aliases: [perms, permissions, lp, perm]
aliases: [lp, perm, perms, permission, permissions]
# Permissions are registered programmatically instead of here.
# See: the last method in me.lucko.luckperms.bukkit.LPBukkitPlugin
# See the last method in me.lucko.luckperms.bukkit.LPBukkitPlugin
+36 -24
View File
@@ -5,7 +5,7 @@
<parent>
<artifactId>luckperms</artifactId>
<groupId>me.lucko.luckperms</groupId>
<version>3.0-SNAPSHOT</version>
<version>3.2-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -14,26 +14,18 @@
<build>
<defaultGoal>clean package</defaultGoal>
<finalName>LuckPerms-Bungee-${release.version}.${patch.version}</finalName>
<sourceDirectory>${basedir}/src/main/java</sourceDirectory>
<finalName>LuckPerms-Bungee-${full.version}</finalName>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
<resource>
<directory>../</directory>
<filtering>false</filtering>
<includes>
<include>LICENSE.txt</include>
</includes>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<version>${compiler.version}</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
@@ -42,7 +34,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.4.3</version>
<version>${shade.version}</version>
<executions>
<execution>
<phase>package</phase>
@@ -66,11 +58,11 @@
</build>
<dependencies>
<!-- BungeeCord API -->
<!-- BungeeCord -->
<dependency>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-api</artifactId>
<version>1.9-SNAPSHOT</version>
<version>1.11-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<!-- LuckPerms Common -->
@@ -87,26 +79,46 @@
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
<!-- slf4j library -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.9</version>
<scope>provided</scope>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.10</version>
<version>${lombok.version}</version>
<scope>provided</scope>
</dependency>
<!-- **************Migration Plugins************** -->
<!-- ************** Migration Plugins ************** -->
<!-- BungeePerms -->
<dependency>
<groupId>net.alpenblock</groupId>
<artifactId>BungeePerms</artifactId>
<version>3.0-dev-52</version>
<version>3.0</version>
<scope>provided</scope>
<exclusions>
<exclusion>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-api</artifactId>
</exclusion>
<exclusion>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclusion>
<exclusion>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
</exclusion>
<exclusion>
<groupId>com.sk89q</groupId>
<artifactId>worldedit</artifactId>
</exclusion>
<exclusion>
<groupId>net.milkbowl.vault</groupId>
<artifactId>Vault</artifactId>
</exclusion>
<exclusion>
<groupId>net.ess3</groupId>
<artifactId>Essentials</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
@@ -1,5 +1,8 @@
/*
* Copyright (c) 2016 Lucko (Luck) <luck@lucko.me>
* 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
@@ -22,15 +25,16 @@
package me.lucko.luckperms.bungee;
import com.google.common.collect.Maps;
import lombok.RequiredArgsConstructor;
import me.lucko.luckperms.api.context.ContextCalculator;
import me.lucko.luckperms.api.context.MutableContextSet;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import java.util.Map;
@RequiredArgsConstructor
public class BackendServerCalculator implements ContextCalculator<ProxiedPlayer> {
private static final String WORLD_KEY = "world";
@@ -38,24 +42,16 @@ public class BackendServerCalculator implements ContextCalculator<ProxiedPlayer>
return player.getServer() == null ? null : (player.getServer().getInfo() == null ? null : player.getServer().getInfo().getName());
}
private final LuckPermsPlugin plugin;
@Override
public MutableContextSet giveApplicableContext(ProxiedPlayer subject, MutableContextSet accumulator) {
String server = getServer(subject);
if (server != null) {
accumulator.add(Maps.immutableEntry(WORLD_KEY, server));
while (server != null && !accumulator.has(WORLD_KEY, server)) {
accumulator.add(WORLD_KEY, server);
server = plugin.getConfiguration().get(ConfigKeys.WORLD_REWRITES).getOrDefault(server, server);
}
return accumulator;
}
@Override
public boolean isContextApplicable(ProxiedPlayer subject, Map.Entry<String, String> context) {
if (!context.getKey().equals(WORLD_KEY)) {
return false;
}
String server = getServer(subject);
return server != null && server.equals(context.getValue());
}
}
@@ -1,5 +1,8 @@
/*
* Copyright (c) 2016 Lucko (Luck) <luck@lucko.me>
* 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
@@ -43,14 +46,17 @@ public class BungeeCalculatorFactory extends AbstractCalculatorFactory {
@Override
public PermissionCalculator build(Contexts contexts, User user) {
ImmutableList.Builder<PermissionProcessor> processors = ImmutableList.builder();
processors.add(new MapProcessor());
if (plugin.getConfiguration().get(ConfigKeys.APPLYING_REGEX)) {
processors.add(new RegexProcessor());
}
if (plugin.getConfiguration().get(ConfigKeys.APPLYING_WILDCARDS)) {
processors.add(new WildcardProcessor());
}
return registerCalculator(new PermissionCalculator(plugin, user.getName(), processors.build()));
return registerCalculator(new PermissionCalculator(plugin, user.getFriendlyName(), processors.build()));
}
}
@@ -1,5 +1,8 @@
/*
* Copyright (c) 2016 Lucko (Luck) <luck@lucko.me>
* 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
@@ -27,7 +30,6 @@ import com.google.common.base.Splitter;
import me.lucko.luckperms.common.commands.CommandManager;
import me.lucko.luckperms.common.commands.utils.Util;
import me.lucko.luckperms.common.constants.Patterns;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.plugin.Command;
@@ -40,7 +42,7 @@ class BungeeCommand extends Command implements TabExecutor {
private final CommandManager manager;
BungeeCommand(LPBungeePlugin plugin, CommandManager manager) {
super("luckpermsbungee", null, "bperms", "lpb", "bpermissions", "bperm");
super("luckpermsbungee", null, "lpb", "bperm", "bperms", "bpermission", "bpermissions");
this.plugin = plugin;
this.manager = manager;
}
@@ -50,7 +52,7 @@ class BungeeCommand extends Command implements TabExecutor {
manager.onCommand(
plugin.getSenderFactory().wrap(sender),
"lpb",
Util.stripQuotes(Splitter.on(Patterns.COMMAND_SEPARATOR).omitEmptyStrings().splitToList(Joiner.on(' ').join(args)))
Util.stripQuotes(Splitter.on(CommandManager.COMMAND_SEPARATOR_PATTERN).omitEmptyStrings().splitToList(Joiner.on(' ').join(args)))
);
}
@@ -1,5 +1,8 @@
/*
* Copyright (c) 2016 Lucko (Luck) <luck@lucko.me>
* 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
@@ -35,11 +38,11 @@ import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
@RequiredArgsConstructor
public class BungeeConfig extends AbstractConfiguration {
@@ -72,6 +75,11 @@ public class BungeeConfig extends AbstractConfiguration {
}
}
@Override
public boolean contains(String path) {
return configuration.contains(path);
}
@Override
public String getString(String path, String def) {
return configuration.getString(path, def);
@@ -99,7 +107,7 @@ public class BungeeConfig extends AbstractConfiguration {
return def;
}
return Optional.ofNullable(section.getKeys().stream().collect(Collectors.toList())).orElse(def);
return Optional.of((List<String>) new ArrayList<>(section.getKeys())).orElse(def);
}
@Override
@@ -1,5 +1,8 @@
/*
* Copyright (c) 2016 Lucko (Luck) <luck@lucko.me>
* 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
@@ -22,13 +25,15 @@
package me.lucko.luckperms.bungee;
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.common.config.ConfigKeys;
import me.lucko.luckperms.common.constants.Message;
import me.lucko.luckperms.common.core.UuidCache;
import me.lucko.luckperms.common.core.model.User;
import me.lucko.luckperms.common.defaults.Rule;
import me.lucko.luckperms.common.utils.AbstractListener;
import me.lucko.luckperms.common.utils.LoginHelper;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.connection.PendingConnection;
@@ -37,39 +42,126 @@ 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.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
@SuppressWarnings("WeakerAccess")
public class BungeeListener extends AbstractListener implements Listener {
private static final TextComponent WARN_MESSAGE = new TextComponent(Message.LOADING_ERROR.toString());
@RequiredArgsConstructor
public class BungeeListener implements Listener {
private final LPBungeePlugin plugin;
BungeeListener(LPBungeePlugin plugin) {
super(plugin);
this.plugin = plugin;
private final Set<UUID> deniedLogin = Collections.synchronizedSet(new HashSet<>());
@EventHandler(priority = EventPriority.LOW)
public void onPlayerLogin(LoginEvent e) {
/* Called when the player first attempts a connection with the server.
Listening on LOW priority to allow plugins to modify username / UUID data here. (auth plugins)
Delay the login here, as we want to cache UUID data before the player is connected to a backend bukkit server.
This means that a player will have the same UUID across the network, even if parts of the network are running in
Offline mode. */
/* registers the plugins intent to modify this events state going forward.
this will prevent the event from completing until we're finished handling. */
e.registerIntent(plugin);
final PendingConnection c = e.getConnection();
/* another plugin (or the proxy itself) has cancelled this connection already */
if (e.isCancelled()) {
// log that we are not loading any data
plugin.getLog().warn("Connection from " + c.getUniqueId() + " was already denied. No permissions data will be loaded.");
deniedLogin.add(c.getUniqueId());
e.completeIntent(plugin);
return;
}
/* 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 yet. No permissions data will be loaded for: " + c.getUniqueId() + " - " + c.getName());
deniedLogin.add(c.getUniqueId());
e.completeIntent(plugin);
return;
}
plugin.doAsync(() -> {
/* Actually process the login for the connection.
We do this here to delay the login until the data is ready.
If the login gets cancelled later on, then this will be cleaned up.
This includes:
- loading uuid data
- loading permissions
- creating a user instance in the UserManager for this connection.
- setting up cached data. */
try {
LoginHelper.loadUser(plugin, c.getUniqueId(), c.getName(), true);
} 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());
deniedLogin.add(c.getUniqueId());
}
// finally, complete our intent to modify state, so the proxy can continue handling the connection.
e.completeIntent(plugin);
// schedule a cleanup of the users data in a few seconds.
// this should cover the eventuality that the login fails.
plugin.getUserManager().scheduleUnload(c.getUniqueId());
});
}
@EventHandler
public void onPlayerPostLogin(PostLoginEvent e) {
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;
}
player.sendMessage(new TextComponent(Message.LOADING_ERROR.asString(plugin.getLocaleManager())));
}, 3, TimeUnit.SECONDS);
}
}
// Wait until the last priority to unload, so plugins can still perform permission checks on this event
@EventHandler(priority = EventPriority.HIGHEST)
public void onPlayerQuit(PlayerDisconnectEvent e) {
// Request that the users data is unloaded.
plugin.getUserManager().scheduleUnload(e.getPlayer().getUniqueId());
}
@EventHandler(priority = EventPriority.HIGH)
public void onPlayerPermissionCheck(PermissionCheckEvent e) {
if (!(e.getSender() instanceof ProxiedPlayer)) {
e.setHasPermission(true);
return;
}
final ProxiedPlayer player = ((ProxiedPlayer) e.getSender());
User user = plugin.getUserManager().get(plugin.getUuidCache().getUUID(player.getUniqueId()));
User user = plugin.getUserManager().getIfLoaded(plugin.getUuidCache().getUUID(player.getUniqueId()));
if (user == null) {
return;
}
if (user.getUserData() == null) {
plugin.getLog().warn("Player " + player.getName() + " does not have any user data setup.");
e.setHasPermission(false);
return;
}
@@ -83,83 +175,41 @@ public class BungeeListener extends AbstractListener implements Listener {
false
);
e.setHasPermission(user.getUserData().getPermissionData(contexts).getPermissionValue(e.getPermission()).asBoolean());
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());
}
// We don't pre-process all servers, so we have to do it here.
@EventHandler(priority = EventPriority.LOWEST)
public void onPlayerLogin(LoginEvent e) {
/* Delay the login here, as we want to cache UUID data before the player is connected to a backend bukkit server.
This means that a player will have the same UUID across the network, even if parts of the network are running in
Offline mode. */
e.registerIntent(plugin);
public void onServerSwitch(ServerConnectEvent e) {
String serverName = e.getTarget().getName();
UUID uuid = e.getPlayer().getUniqueId();
plugin.doAsync(() -> {
final long startTime = System.currentTimeMillis();
final UuidCache cache = plugin.getUuidCache();
final PendingConnection c = e.getConnection();
MutableContextSet set = MutableContextSet.create();
set.add("server", plugin.getConfiguration().get(ConfigKeys.SERVER));
set.add("world", serverName);
if (!plugin.getConfiguration().get(ConfigKeys.ONLINE_MODE)) {
UUID uuid = plugin.getStorage().getUUID(c.getName()).join();
if (uuid != null) {
cache.addToCache(c.getUniqueId(), uuid);
} else {
// No previous data for this player
plugin.getApiProvider().getEventFactory().handleUserFirstLogin(c.getUniqueId(), c.getName());
cache.addToCache(c.getUniqueId(), c.getUniqueId());
plugin.getStorage().force().saveUUIDData(c.getName(), c.getUniqueId()).join();
}
} else {
String name = plugin.getStorage().getName(c.getUniqueId()).join();
if (name == null) {
plugin.getApiProvider().getEventFactory().handleUserFirstLogin(c.getUniqueId(), c.getName());
}
// Online mode, no cache needed. This is just for name -> uuid lookup.
plugin.getStorage().force().saveUUIDData(c.getName(), c.getUniqueId()).join();
}
// We have to make a new user on this thread whilst the connection is being held, or we get concurrency issues as the Bukkit server
// and the BungeeCord server try to make a new user at the same time.
plugin.getStorage().force().loadUser(cache.getUUID(c.getUniqueId()), c.getName()).join();
User user = plugin.getUserManager().get(cache.getUUID(c.getUniqueId()));
User user = plugin.getUserManager().getIfLoaded(plugin.getUuidCache().getUUID(uuid));
if (user == null) {
plugin.getLog().warn("Failed to load user: " + c.getName());
} else {
// Setup defaults for the user
boolean save = false;
for (Rule rule : plugin.getConfiguration().get(ConfigKeys.DEFAULT_ASSIGNMENTS)) {
if (rule.apply(user)) {
save = true;
}
}
// If they were given a default, persist the new assignments back to the storage.
if (save) {
plugin.getStorage().force().saveUser(user).join();
}
user.setupData(false); // Pretty nasty calculation call. Sets up the caching system so data is ready when the user joins.
return;
}
final long time = System.currentTimeMillis() - startTime;
if (time >= 1000) {
plugin.getLog().warn("Processing login for " + c.getName() + " took " + time + "ms.");
}
e.completeIntent(plugin);
Contexts contexts = new Contexts(
set.makeImmutable(),
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
);
user.getUserData().preCalculate(contexts);
});
}
@EventHandler
public void onPlayerPostLogin(PostLoginEvent e) {
final ProxiedPlayer player = e.getPlayer();
final User user = plugin.getUserManager().get(plugin.getUuidCache().getUUID(e.getPlayer().getUniqueId()));
if (user == null) {
plugin.getProxy().getScheduler().schedule(plugin, () -> player.sendMessage(WARN_MESSAGE), 3, TimeUnit.SECONDS);
}
}
@EventHandler
public void onPlayerQuit(PlayerDisconnectEvent e) {
onLeave(e.getPlayer().getUniqueId());
}
}
@@ -1,5 +1,8 @@
/*
* Copyright (c) 2016 Lucko (Luck) <luck@lucko.me>
* 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
@@ -64,7 +67,7 @@ public class BungeeSenderFactory extends SenderFactory<CommandSender> {
@Override
protected void sendMessage(CommandSender sender, FancyMessage message) {
try {
sender.sendMessage(ComponentSerializer.parse(message.toJSONString()));
sender.sendMessage(ComponentSerializer.parse(message.exportToJson()));
} catch (Exception e) {
sendMessage(sender, message.toOldMessageFormat());
}
@@ -1,5 +1,8 @@
/*
* Copyright (c) 2016 Lucko (Luck) <luck@lucko.me>
* 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
@@ -28,7 +31,6 @@ 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.MutableContextSet;
import me.lucko.luckperms.bungee.messaging.BungeeMessagingService;
import me.lucko.luckperms.common.api.ApiHandler;
import me.lucko.luckperms.common.api.ApiProvider;
@@ -39,11 +41,10 @@ import me.lucko.luckperms.common.commands.sender.Sender;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.config.LuckPermsConfiguration;
import me.lucko.luckperms.common.contexts.ContextManager;
import me.lucko.luckperms.common.contexts.ServerCalculator;
import me.lucko.luckperms.common.contexts.StaticCalculator;
import me.lucko.luckperms.common.core.UuidCache;
import me.lucko.luckperms.common.core.model.User;
import me.lucko.luckperms.common.data.Importer;
import me.lucko.luckperms.common.debug.DebugHandler;
import me.lucko.luckperms.common.dependencies.Dependency;
import me.lucko.luckperms.common.dependencies.DependencyManager;
import me.lucko.luckperms.common.locale.LocaleManager;
import me.lucko.luckperms.common.locale.NoopLocaleManager;
@@ -55,6 +56,7 @@ import me.lucko.luckperms.common.managers.impl.GenericGroupManager;
import me.lucko.luckperms.common.managers.impl.GenericTrackManager;
import me.lucko.luckperms.common.managers.impl.GenericUserManager;
import me.lucko.luckperms.common.messaging.InternalMessagingService;
import me.lucko.luckperms.common.messaging.NoopMessagingService;
import me.lucko.luckperms.common.messaging.RedisMessaging;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.plugin.LuckPermsScheduler;
@@ -66,14 +68,16 @@ import me.lucko.luckperms.common.tasks.ExpireTemporaryTask;
import me.lucko.luckperms.common.tasks.UpdateTask;
import me.lucko.luckperms.common.treeview.PermissionVault;
import me.lucko.luckperms.common.utils.BufferedRequest;
import me.lucko.luckperms.common.utils.LoggerImpl;
import me.lucko.luckperms.common.utils.FileWatcher;
import me.lucko.luckperms.common.utils.SenderLogger;
import me.lucko.luckperms.common.verbose.VerboseHandler;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.connection.ProxiedPlayer;
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.Set;
@@ -85,34 +89,42 @@ import java.util.stream.Collectors;
public class LPBungeePlugin extends Plugin implements LuckPermsPlugin {
private final Set<UUID> ignoringLogs = ConcurrentHashMap.newKeySet();
private LuckPermsScheduler scheduler;
private CommandManager commandManager;
private LuckPermsConfiguration configuration;
private UserManager userManager;
private GroupManager groupManager;
private TrackManager trackManager;
private Storage storage;
private FileWatcher fileWatcher = null;
private InternalMessagingService messagingService = null;
private UuidCache uuidCache;
private ApiProvider apiProvider;
private Logger log;
private Importer importer;
private LocaleManager localeManager;
private CachedStateManager cachedStateManager;
private ContextManager<ProxiedPlayer> contextManager;
private CalculatorFactory calculatorFactory;
private BufferedRequest<Void> updateTaskBuffer;
private DebugHandler debugHandler;
private VerboseHandler verboseHandler;
private BungeeSenderFactory senderFactory;
private PermissionVault permissionVault;
@Override
public void onEnable() {
public void onLoad() {
// setup minimal functionality in order to load initial dependencies
scheduler = new LPBungeeScheduler(this);
localeManager = new NoopLocaleManager();
senderFactory = new BungeeSenderFactory(this);
log = new LoggerImpl(getConsoleSender());
log = new SenderLogger(getConsoleSender());
DependencyManager.loadDependencies(this, Collections.singletonList(Dependency.CAFFEINE));
}
@Override
public void onEnable() {
LuckPermsPlugin.sendStartupBanner(getConsoleSender(), this);
debugHandler = new DebugHandler(scheduler.getAsyncExecutor(), getVersion());
permissionVault = new PermissionVault(scheduler.getAsyncExecutor());
verboseHandler = new VerboseHandler(scheduler.async(), getVersion());
permissionVault = new PermissionVault(scheduler.async());
getLog().info("Loading configuration...");
configuration = new BungeeConfig(this);
@@ -125,6 +137,11 @@ public class LPBungeePlugin extends Plugin implements LuckPermsPlugin {
// register events
getProxy().getPluginManager().registerListener(this, new BungeeListener(this));
if (getConfiguration().get(ConfigKeys.WATCH_FILES)) {
fileWatcher = new FileWatcher(this);
getScheduler().asyncRepeating(fileWatcher, 30L);
}
// initialise datastore
storage = StorageFactory.getInstance(this, StorageType.H2);
@@ -158,6 +175,10 @@ public class LPBungeePlugin extends Plugin implements LuckPermsPlugin {
getLog().warn("Messaging service '" + messagingType + "' not recognised.");
}
if (messagingService == null) {
messagingService = new NoopMessagingService();
}
// setup the update task buffer
updateTaskBuffer = new BufferedRequest<Void>(1000L, this::doAsync) {
@Override
@@ -181,7 +202,7 @@ public class LPBungeePlugin extends Plugin implements LuckPermsPlugin {
// register commands
getLog().info("Registering commands...");
CommandManager commandManager = new CommandManager(this);
commandManager = new CommandManager(this);
getProxy().getPluginManager().registerCommand(this, new BungeeCommand(this, commandManager));
// disable the default Bungee /perms command so it gets handled by the Bukkit plugin
@@ -193,14 +214,16 @@ public class LPBungeePlugin extends Plugin implements LuckPermsPlugin {
userManager = new GenericUserManager(this);
groupManager = new GenericGroupManager(this);
trackManager = new GenericTrackManager(this);
importer = new Importer(commandManager);
calculatorFactory = new BungeeCalculatorFactory(this);
cachedStateManager = new CachedStateManager(this);
contextManager = new ContextManager<>();
BackendServerCalculator serverCalculator = new BackendServerCalculator();
BackendServerCalculator serverCalculator = new BackendServerCalculator(this);
contextManager.registerCalculator(serverCalculator);
contextManager.registerCalculator(new ServerCalculator<>(configuration));
StaticCalculator<ProxiedPlayer> staticCalculator = new StaticCalculator<>(getConfiguration());
contextManager.registerCalculator(staticCalculator);
contextManager.registerStaticCalculator(staticCalculator);
// register with the LP API
getLog().info("Registering API...");
@@ -211,16 +234,16 @@ public class LPBungeePlugin extends Plugin implements LuckPermsPlugin {
int mins = getConfiguration().get(ConfigKeys.SYNC_TIME);
if (mins > 0) {
long ticks = mins * 60 * 20;
scheduler.doAsyncRepeating(() -> updateTaskBuffer.request(), ticks);
scheduler.asyncRepeating(() -> updateTaskBuffer.request(), ticks);
}
scheduler.doAsyncLater(() -> updateTaskBuffer.request(), 40L);
scheduler.asyncLater(() -> updateTaskBuffer.request(), 40L);
// run an update instantly.
updateTaskBuffer.requestDirectly();
// register tasks
scheduler.doAsyncRepeating(new ExpireTemporaryTask(this), 60L);
scheduler.doAsyncRepeating(new CacheHousekeepingTask(this), 2400L);
scheduler.asyncRepeating(new ExpireTemporaryTask(this), 60L);
scheduler.asyncRepeating(new CacheHousekeepingTask(this), 2400L);
getLog().info("Successfully loaded.");
}
@@ -230,6 +253,10 @@ public class LPBungeePlugin extends Plugin implements LuckPermsPlugin {
getLog().info("Closing datastore...");
storage.shutdown();
if (fileWatcher != null) {
fileWatcher.close();
}
if (messagingService != null) {
getLog().info("Closing messaging service...");
messagingService.close();
@@ -311,7 +338,8 @@ public class LPBungeePlugin extends Plugin implements LuckPermsPlugin {
@Override
public boolean isPlayerOnline(UUID external) {
return getProxy().getPlayer(external) != null;
ProxiedPlayer player = getProxy().getPlayer(external);
return player != null && player.isConnected();
}
@Override
@@ -331,17 +359,6 @@ public class LPBungeePlugin extends Plugin implements LuckPermsPlugin {
Set<ContextSet> c = new HashSet<>();
c.add(ContextSet.empty());
c.add(ContextSet.singleton("server", getConfiguration().get(ConfigKeys.SERVER)));
c.addAll(getProxy().getServers().values().stream()
.map(ServerInfo::getName)
.map(s -> {
MutableContextSet set = MutableContextSet.create();
set.add("server", getConfiguration().get(ConfigKeys.SERVER));
set.add("world", s);
return set.makeImmutable();
})
.collect(Collectors.toList())
);
return c.stream()
.map(set -> new Contexts(
set,
@@ -354,9 +371,4 @@ public class LPBungeePlugin extends Plugin implements LuckPermsPlugin {
))
.collect(Collectors.toSet());
}
@Override
public UUID getUuidFromUsername(String playerName) {
return null; // Not needed on Bungee
}
}
@@ -1,5 +1,8 @@
/*
* Copyright (c) 2016 Lucko (Luck) <luck@lucko.me>
* 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
@@ -43,46 +46,46 @@ public class LPBungeeScheduler implements LuckPermsScheduler {
}
@Override
public Executor getAsyncExecutor() {
public Executor async() {
return asyncExecutor;
}
@Override
public Executor getSyncExecutor() {
public Executor sync() {
return asyncExecutor;
}
@Override
public void doAsync(Runnable r) {
asyncExecutor.execute(r);
public void doAsync(Runnable runnable) {
asyncExecutor.execute(runnable);
}
@Override
public void doSync(Runnable r) {
doAsync(r);
public void doSync(Runnable runnable) {
doAsync(runnable);
}
@Override
public void doAsyncRepeating(Runnable r, long interval) {
long millis = interval * 50L; // convert from ticks to milliseconds
ScheduledTask task = plugin.getProxy().getScheduler().schedule(plugin, r, millis, millis, TimeUnit.MILLISECONDS);
public void asyncRepeating(Runnable runnable, long intervalTicks) {
long millis = intervalTicks * 50L; // convert from ticks to milliseconds
ScheduledTask task = plugin.getProxy().getScheduler().schedule(plugin, runnable, millis, millis, TimeUnit.MILLISECONDS);
tasks.add(task);
}
@Override
public void doSyncRepeating(Runnable r, long interval) {
doAsyncRepeating(r, interval);
public void syncRepeating(Runnable runnable, long intervalTicks) {
asyncRepeating(runnable, intervalTicks);
}
@Override
public void doAsyncLater(Runnable r, long delay) {
long millis = delay * 50L; // convert from ticks to milliseconds
plugin.getProxy().getScheduler().schedule(plugin, r, millis, TimeUnit.MILLISECONDS);
public void asyncLater(Runnable runnable, long delayTicks) {
long millis = delayTicks * 50L; // convert from ticks to milliseconds
plugin.getProxy().getScheduler().schedule(plugin, runnable, millis, TimeUnit.MILLISECONDS);
}
@Override
public void doSyncLater(Runnable r, long delay) {
doAsyncLater(r, delay);
public void syncLater(Runnable runnable, long delayTicks) {
asyncLater(runnable, delayTicks);
}
@Override
@@ -1,5 +1,8 @@
/*
* Copyright (c) 2016 Lucko (Luck) <luck@lucko.me>
* 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
@@ -1,5 +1,8 @@
/*
* Copyright (c) 2016 Lucko (Luck) <luck@lucko.me>
* 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
@@ -22,13 +25,14 @@
package me.lucko.luckperms.bungee.migration;
import me.lucko.luckperms.api.MetaUtils;
import me.lucko.luckperms.api.event.cause.CreationCause;
import me.lucko.luckperms.common.commands.CommandException;
import me.lucko.luckperms.common.commands.CommandResult;
import me.lucko.luckperms.common.commands.SubCommand;
import me.lucko.luckperms.common.commands.abstraction.SubCommand;
import me.lucko.luckperms.common.commands.impl.migration.MigrationUtils;
import me.lucko.luckperms.common.commands.sender.Sender;
import me.lucko.luckperms.common.constants.Permission;
import me.lucko.luckperms.common.core.NodeFactory;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.utils.Predicates;
import me.lucko.luckperms.common.utils.ProgressLogger;
@@ -63,53 +67,66 @@ public class MigrationBungeePerms extends SubCommand<Object> {
return CommandResult.STATE_ERROR;
}
List<Group> groups = bp.getPermissionsManager().getBackEnd().loadGroups();
log.log("Calculating group weightings.");
int maxWeight = 0;
for (Group group : groups) {
maxWeight = Math.max(maxWeight, group.getRank());
}
maxWeight += 5;
// Migrate all groups.
log.log("Starting group migration.");
AtomicInteger groupCount = new AtomicInteger(0);
for (Group g : bp.getPermissionsManager().getBackEnd().loadGroups()) {
for (Group g : groups) {
int groupWeight = maxWeight - g.getRank();
// Make a LuckPerms group for the one being migrated
plugin.getStorage().createAndLoadGroup(g.getName().toLowerCase(), CreationCause.INTERNAL).join();
me.lucko.luckperms.common.core.model.Group group = plugin.getGroupManager().getIfLoaded(g.getName().toLowerCase());
String groupName = MigrationUtils.standardizeName(g.getName());
plugin.getStorage().createAndLoadGroup(groupName, CreationCause.INTERNAL).join();
me.lucko.luckperms.common.core.model.Group group = plugin.getGroupManager().getIfLoaded(groupName);
MigrationUtils.setGroupWeight(group, groupWeight);
// Migrate global perms
for (String perm : g.getPerms()) {
try {
group.setPermission(perm, true);
} catch (Exception ex) {
log.handleException(ex);
if (perm.isEmpty()) {
continue;
}
group.setPermission(MigrationUtils.parseNode(perm, true).build());
}
// Migrate per-server perms
for (Map.Entry<String, Server> e : g.getServers().entrySet()) {
for (String perm : e.getValue().getPerms()) {
try {
group.setPermission(perm, true, e.getKey());
} catch (Exception ex) {
log.handleException(ex);
if (perm.isEmpty()) {
continue;
}
group.setPermission(MigrationUtils.parseNode(perm, true).setWorld(e.getKey()).build());
}
// Migrate per-world perms
for (Map.Entry<String, World> we : e.getValue().getWorlds().entrySet()) {
for (String perm : we.getValue().getPerms()) {
try {
group.setPermission(perm, true, e.getKey(), we.getKey());
} catch (Exception ex) {
log.handleException(ex);
if (perm.isEmpty()) {
continue;
}
group.setPermission(MigrationUtils.parseNode(perm, true).setServer(e.getKey()).setWorld(we.getKey()).build());
}
}
}
// Migrate any parent groups
for (String inherit : g.getInheritances()) {
try {
group.setPermission("group." + inherit, true);
} catch (Exception ex) {
log.handleException(ex);
if (inherit.isEmpty()) {
continue;
}
group.setPermission(NodeFactory.make("group." + MigrationUtils.standardizeName(inherit)));
}
// Migrate prefix and suffix
@@ -117,25 +134,13 @@ public class MigrationBungeePerms extends SubCommand<Object> {
String suffix = g.getSuffix();
if (prefix != null && !prefix.equals("")) {
prefix = MetaUtils.escapeCharacters(prefix);
try {
group.setPermission("prefix.50." + prefix, true);
} catch (Exception ex) {
log.handleException(ex);
}
group.setPermission(NodeFactory.makePrefixNode(groupWeight, prefix).build());
}
if (suffix != null && !suffix.equals("")) {
suffix = MetaUtils.escapeCharacters(suffix);
try {
group.setPermission("suffix.50." + suffix, true);
} catch (Exception ex) {
log.handleException(ex);
}
group.setPermission(NodeFactory.makeSuffixNode(groupWeight, suffix).build());
}
plugin.getStorage().saveGroup(group);
log.logAllProgress("Migrated {} groups so far.", groupCount.incrementAndGet());
}
log.log("Migrated " + groupCount.get() + " groups");
@@ -143,6 +148,10 @@ public class MigrationBungeePerms extends SubCommand<Object> {
// Migrate all users.
log.log("Starting user migration.");
AtomicInteger userCount = new AtomicInteger(0);
// Increment the max weight from the group migrations. All user meta should override.
maxWeight += 5;
for (User u : bp.getPermissionsManager().getBackEnd().loadUsers()) {
if (u.getUUID() == null) {
log.logErr("Could not parse UUID for user: " + u.getName());
@@ -150,47 +159,47 @@ public class MigrationBungeePerms extends SubCommand<Object> {
}
// Make a LuckPerms user for the one being migrated.
plugin.getStorage().loadUser(u.getUUID(), "null").join();
me.lucko.luckperms.common.core.model.User user = plugin.getUserManager().get(u.getUUID());
plugin.getStorage().loadUser(u.getUUID(), u.getName()).join();
me.lucko.luckperms.common.core.model.User user = plugin.getUserManager().getIfLoaded(u.getUUID());
// Migrate global perms
for (String perm : u.getPerms()) {
try {
user.setPermission(perm, true);
} catch (Exception ex) {
log.handleException(ex);
if (perm.isEmpty()) {
continue;
}
user.setPermission(MigrationUtils.parseNode(perm, true).build());
}
// Migrate per-server perms
for (Map.Entry<String, Server> e : u.getServers().entrySet()) {
for (String perm : e.getValue().getPerms()) {
try {
user.setPermission(perm, true, e.getKey());
} catch (Exception ex) {
log.handleException(ex);
if (perm.isEmpty()) {
continue;
}
user.setPermission(MigrationUtils.parseNode(perm, true).setWorld(e.getKey()).build());
}
// Migrate per-world perms
for (Map.Entry<String, World> we : e.getValue().getWorlds().entrySet()) {
for (String perm : we.getValue().getPerms()) {
try {
user.setPermission(perm, true, e.getKey(), we.getKey());
} catch (Exception ex) {
log.handleException(ex);
if (perm.isEmpty()) {
continue;
}
user.setPermission(MigrationUtils.parseNode(perm, true).setServer(e.getKey()).setWorld(we.getKey()).build());
}
}
}
// Migrate groups
for (String group : u.getGroupsString()) {
try {
user.setPermission("group." + group, true);
} catch (Exception ex) {
log.handleException(ex);
if (group.isEmpty()) {
continue;
}
user.setPermission(NodeFactory.make("group." + MigrationUtils.standardizeName(group)));
}
// Migrate prefix & suffix
@@ -198,21 +207,10 @@ public class MigrationBungeePerms extends SubCommand<Object> {
String suffix = u.getSuffix();
if (prefix != null && !prefix.equals("")) {
prefix = MetaUtils.escapeCharacters(prefix);
try {
user.setPermission("prefix.100." + prefix, true);
} catch (Exception ex) {
log.handleException(ex);
}
user.setPermission(NodeFactory.makePrefixNode(maxWeight, prefix).build());
}
if (suffix != null && !suffix.equals("")) {
suffix = MetaUtils.escapeCharacters(suffix);
try {
user.setPermission("suffix.100." + suffix, true);
} catch (Exception ex) {
log.handleException(ex);
}
user.setPermission(NodeFactory.makeSuffixNode(maxWeight, suffix).build());
}
plugin.getStorage().saveUser(user);
+52 -18
View File
@@ -14,7 +14,7 @@ server: bungee
# If users on this server should have their global permissions applied.
# If set to false, only server specific permissions will apply for users on this server
include-global: false
include-global: true
# If users on this server should have their global world permissions applied.
# If set to false, only world specific permissions will apply for users on this server
@@ -26,22 +26,24 @@ apply-global-groups: true
# If users on this server should have global (non-world specific) groups applied
apply-global-world-groups: true
# If this server is in offline or online mode.
# This setting allows a player to have the same UUID across a network of offline mode/mixed servers.
# If UUIDs should be pulled from the server, or looked up by username based upon previous connections.
#
# You should generally reflect the setting in server.properties here. Except when...
# This option should be set to true in most cases. When set to false, in order to get a player's UUID, LuckPerms will:
# 1. Check if a player with the given username has joined before, if they have, use the UUID they used on their previous login.
# 2. Save and return the players "current" uuid.
#
# 1. You have Spigot servers connected to a BungeeCord proxy, with online-mode set to false, but 'bungeecord' set to true in the spigot.yml
# AND 'ip-forward' set to true in the BungeeCord config.yml
# In this case, set online-mode in LuckPerms to true, despite the server being in offline mode.
# For offline mode (cracked) servers, a players UUID is generated based upon their username.
#
# 2. You are only running one server instance using LuckPerms, (not a network)
# In this case, set online-mode to true no matter what is set in server.properties. (we can just fallback to the servers uuid cache)
# IMPORTANT:
# If you are using BungeeCord proxy running in online mode, it is important that "online-mode=false" is set all backend server.properties, but
# "bungeecord: true" is set in the spigot.yml. You also need to set "ip_forward: true" in BungeeCord's config.yml.
# If for whatever reason you are not able to do this, and do not have ip-forward enabled, then you may need to set "use-server-uuids" to false.
#
# 3. If your proxy is running in offline mode, and you are using PaperSpigot (https://ci.destroystokyo.com/job/PaperSpigot/),
# you should set "bungee-online-mode" to false in the paper.yml, and set "online-mode" to true in all LuckPerms configs.
# This approach is thoroughly recommended for offline mode networks.
online-mode: true
# If your proxy is running in offline mode, you should still be setting up ip-forwarding as described above, but may also find that you need to set
# "bungee-online-mode" to false in paper.yml, if you are using PaperSpigot. (https://ci.destroystokyo.com/job/PaperSpigot/)
#
# This option should always be set to true on the BungeeCord side. Don't change unless you're sure you know what you're doing.
use-server-uuids: true
# If the plugin should send log notifications to users whenever permissions are modified.
log-notify: true
@@ -64,6 +66,14 @@ group-name-rewrite:
# If "deny": the command will just fail if you try to add another node with the same expiry
temporary-add-behaviour: deny
# How should LuckPerms determine a users "primary" group.
#
# Available Options:
# -> stored use the value stored against the users record in the file/database
# -> 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
@@ -85,6 +95,10 @@ apply-regex: true
# If set to true, LuckPerms will detect and expand shorthand node patterns.
apply-shorthand: true
# If the plugin should apply the permissions & groups defined in the BungeeCord config.yml
# If set to false, LuckPerms will ignore these values.
apply-bungee-config-permissions: false
# Define special group weights for this server.
# Default is just 0.
group-weight:
@@ -106,6 +120,8 @@ group-weight:
# - lowest_own
# - highest_on_track_<track>
# - lowest_on_track_<track>
# - highest_not_on_track_<track>
# - lowest_not_on_track_<track>
#
# Each element is added in the order listed.
meta-formatting:
@@ -130,10 +146,20 @@ meta-formatting:
# +------------------------------------------------------------------------+ #
# Which storage method the plugin should use.
# Currently supported: mysql, postgresql, sqlite, h2, json, yaml, mongodb
# Fill out connection info below if you're using MySQL, PostgreSQL or MongoDB
#
# See: https://github.com/lucko/LuckPerms/wiki/Choosing-a-Storage-type
# Currently supported: mysql, mariadb, postgresql, sqlite, h2, json, yaml, 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".
storage-method: h2
# When using a file-based storage type, LuckPerms can monitor the data files for changes, and then schedule automatic
# updates when changes are detected.
#
# If you don't want this to happen, set this option to false.
watch-files: true
# This block enables support for split datastores.
split-storage:
enabled: false
@@ -159,9 +185,14 @@ data:
# This should *not* be set to "lp_" if you have previously ran LuckPerms v2.16.81 or earlier with this database.
table_prefix: 'luckperms_'
# Set to -1 to disable. If this is the only instance accessing the datastore, you can disable syncing.
# e.g. if you're using sqlite or flatfile, this can be set to -1 to save resources.
sync-minutes: 3
# This option controls how frequently LuckPerms will perform a sync task.
# A sync task will refresh all data from the storage, and ensure that the most up-to-date data is being used by the plugin.
#
# This is disabled by default, as most users will not need it. However, if you're using a remote storage type
# without a messaging service setup, you may wish to set this value to something like 3.
#
# Set to -1 to disable the task completely.
sync-minutes: -1
# Settings for the messaging service
#
@@ -178,6 +209,9 @@ data:
# none ==> nothing
messaging-service: none
# If LuckPerms should automatically push updates after a change has been made with a command.
auto-push-updates: true
# Settings for Redis.
# Port 6379 is used by default; set address to "host:port" if differs
redis:
+1 -2
View File
@@ -1,6 +1,5 @@
name: LuckPerms
version: ${release.version}.${patch.version}
version: ${full.version}
description: A permissions plugin
author: Luck
main: me.lucko.luckperms.bungee.LPBungeePlugin
softdepends: [PowerfulPerms] # For migration
+15 -40
View File
@@ -5,17 +5,20 @@
<parent>
<artifactId>luckperms</artifactId>
<groupId>me.lucko.luckperms</groupId>
<version>3.0-SNAPSHOT</version>
<version>3.2-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>luckperms-common</artifactId>
<packaging>jar</packaging>
<build>
<finalName>LuckPerms-Common-${full.version}</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<version>${compiler.version}</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
@@ -36,21 +39,14 @@
<dependency>
<groupId>io.github.mkremins</groupId>
<artifactId>fanciful</artifactId>
<version>1.2.0</version>
<version>1.2.5</version>
<scope>compile</scope>
</dependency>
<!-- MySQL -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.40</version>
<scope>provided</scope>
</dependency>
<!-- HikariCP -->
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>2.5.1</version>
<version>2.6.1</version>
<scope>provided</scope>
</dependency>
<!-- Jedis -->
@@ -60,27 +56,6 @@
<version>2.9.1-shaded</version>
<scope>provided</scope>
</dependency>
<!-- SQLite -->
<dependency>
<groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId>
<version>3.8.11.2</version>
<scope>provided</scope>
</dependency>
<!-- H2 -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.192</version>
<scope>provided</scope>
</dependency>
<!-- PostgreSQL -->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>9.4.1212</version>
<scope>provided</scope>
</dependency>
<!-- MongoDB -->
<dependency>
<groupId>org.mongodb</groupId>
@@ -88,13 +63,6 @@
<version>3.3.0</version>
<scope>provided</scope>
</dependency>
<!-- slf4j -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.9</version>
<scope>provided</scope>
</dependency>
<!-- SnakeYAML -->
<dependency>
<groupId>org.yaml</groupId>
@@ -116,11 +84,18 @@
<version>19.0</version>
<scope>provided</scope>
</dependency>
<!-- caffeine -->
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>2.4.0</version>
<scope>provided</scope>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.10</version>
<version>${lombok.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
@@ -1,5 +1,8 @@
/*
* Copyright (c) 2016 Lucko (Luck) <luck@lucko.me>
* 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
@@ -1,5 +1,8 @@
/*
* Copyright (c) 2016 Lucko (Luck) <luck@lucko.me>
* 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
@@ -40,12 +43,14 @@ 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.common.core.NodeBuilder;
import me.lucko.luckperms.common.core.UserIdentifier;
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 java.util.Optional;
@@ -65,10 +70,14 @@ public class ApiProvider implements LuckPermsApi {
@Getter
private final EventFactory eventFactory;
@Getter
private final MetaStackFactoryDelegate metaStackFactory;
public ApiProvider(LuckPermsPlugin plugin) {
this.plugin = plugin;
this.eventBus = new LuckPermsEventBus(plugin);
this.eventFactory = new EventFactory(eventBus);
this.metaStackFactory = new MetaStackFactoryDelegate(plugin);
}
@Override
@@ -78,7 +87,7 @@ public class ApiProvider implements LuckPermsApi {
@Override
public double getApiVersion() {
return 3.0;
return 3.2;
}
@Override
@@ -103,7 +112,8 @@ public class ApiProvider implements LuckPermsApi {
@Override
public Optional<MessagingService> getMessagingService() {
return Optional.ofNullable(plugin.getMessagingService());
InternalMessagingService service = plugin.getMessagingService();
return service instanceof NoopMessagingService ? Optional.empty() : Optional.of(service);
}
@Override
@@ -118,7 +128,7 @@ public class ApiProvider implements LuckPermsApi {
@Override
public User getUser(@NonNull UUID uuid) {
final me.lucko.luckperms.common.core.model.User user = plugin.getUserManager().get(uuid);
final me.lucko.luckperms.common.core.model.User user = plugin.getUserManager().getIfLoaded(uuid);
return user == null ? null : user.getDelegate();
}
@@ -150,8 +160,7 @@ public class ApiProvider implements LuckPermsApi {
@Override
public void cleanupUser(@NonNull User user) {
ApiUtils.checkUser(user);
plugin.getUserManager().cleanup(((UserDelegate) user).getMaster());
plugin.getUserManager().cleanup(UserDelegate.cast(user));
}
@Override
@@ -203,7 +212,7 @@ public class ApiProvider implements LuckPermsApi {
@Override
public Node.Builder buildNode(@NonNull String permission) throws IllegalArgumentException {
return new NodeBuilder(permission);
return me.lucko.luckperms.common.core.NodeFactory.newBuilder(permission);
}
@SuppressWarnings("unchecked")
@@ -214,8 +223,7 @@ public class ApiProvider implements LuckPermsApi {
@Override
public Optional<Contexts> getContextForUser(User user) {
ApiUtils.checkUser(user);
return Optional.ofNullable(plugin.getContextForUser(((UserDelegate) user).getMaster()));
return Optional.ofNullable(plugin.getContextForUser(UserDelegate.cast(user)));
}
@SuppressWarnings("unchecked")
@@ -1,5 +1,8 @@
/*
* Copyright (c) 2016 Lucko (Luck) <luck@lucko.me>
* 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
@@ -26,32 +29,14 @@ import lombok.experimental.UtilityClass;
import com.google.common.base.Preconditions;
import me.lucko.luckperms.api.Group;
import me.lucko.luckperms.api.Track;
import me.lucko.luckperms.api.User;
import me.lucko.luckperms.common.api.delegates.GroupDelegate;
import me.lucko.luckperms.common.api.delegates.TrackDelegate;
import me.lucko.luckperms.common.api.delegates.UserDelegate;
import me.lucko.luckperms.common.utils.ArgumentChecker;
import me.lucko.luckperms.common.constants.DataConstraints;
@UtilityClass
public class ApiUtils {
public static void checkUser(User user) {
Preconditions.checkState(user instanceof UserDelegate, "User instance cannot be handled by this implementation.");
}
public static void checkGroup(Group group) {
Preconditions.checkState(group instanceof GroupDelegate, "Group instance cannot be handled by this implementation.");
}
public static void checkTrack(Track track) {
Preconditions.checkState(track instanceof TrackDelegate, "Track instance cannot be handled by this implementation.");
}
public static String checkUsername(String s) {
Preconditions.checkArgument(
!ArgumentChecker.checkUsername(s),
DataConstraints.PLAYER_USERNAME_TEST.test(s),
"Invalid username entry '" + s + "'. Usernames must be less than 16 chars and only contain 'a-z A-Z 1-9 _'."
);
return s;
@@ -59,14 +44,14 @@ public class ApiUtils {
public static String checkName(String s) {
Preconditions.checkArgument(
!ArgumentChecker.checkName(s),
DataConstraints.GROUP_NAME_TEST.test(s),
"Invalid name entry '" + s + "'. Names must be less than 37 chars and only contain 'a-z A-Z 1-9'."
);
return s.toLowerCase();
}
public static long checkTime(long l) {
Preconditions.checkArgument(!ArgumentChecker.checkTime(l), "Unix time '" + l + "' is invalid, as it has already passed.");
Preconditions.checkArgument(DataConstraints.TIME_TEST.test(l), "Unix time '" + l + "' is invalid, as it has already passed.");
return l;
}
@@ -1,5 +1,8 @@
/*
* Copyright (c) 2016 Lucko (Luck) <luck@lucko.me>
* 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
@@ -23,150 +26,172 @@
package me.lucko.luckperms.common.api.delegates;
import lombok.AccessLevel;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NonNull;
import com.google.common.base.Preconditions;
import me.lucko.luckperms.api.Group;
import me.lucko.luckperms.api.Node;
import me.lucko.luckperms.api.context.ContextSet;
import me.lucko.luckperms.common.core.NodeFactory;
import me.lucko.luckperms.exceptions.ObjectAlreadyHasException;
import me.lucko.luckperms.exceptions.ObjectLacksException;
import java.util.List;
import java.util.OptionalInt;
import java.util.stream.Collectors;
import static me.lucko.luckperms.common.api.ApiUtils.checkGroup;
import static me.lucko.luckperms.common.api.ApiUtils.checkTime;
/**
* Provides a link between {@link Group} and {@link me.lucko.luckperms.common.core.model.Group}
*/
@EqualsAndHashCode(of = {"name"}, callSuper = false)
public class GroupDelegate extends PermissionHolderDelegate implements Group {
public final class GroupDelegate extends PermissionHolderDelegate implements Group {
public static me.lucko.luckperms.common.core.model.Group cast(Group g) {
Preconditions.checkState(g instanceof GroupDelegate, "Illegal instance " + g.getClass() + " cannot be handled by this implementation.");
return ((GroupDelegate) g).getHandle();
}
@Getter(AccessLevel.PACKAGE)
private final me.lucko.luckperms.common.core.model.Group master;
private final me.lucko.luckperms.common.core.model.Group handle;
@Getter
private final String name;
public GroupDelegate(@NonNull me.lucko.luckperms.common.core.model.Group handle) {
super(handle);
this.handle = handle;
}
public GroupDelegate(@NonNull me.lucko.luckperms.common.core.model.Group master) {
super(master);
this.master = master;
this.name = master.getName();
@Override
public String getName() {
return handle.getName();
}
@Override
public boolean inheritsGroup(@NonNull Group group) {
checkGroup(group);
return master.inheritsGroup(((GroupDelegate) group).getMaster());
return handle.inheritsGroup(cast(group));
}
@Override
public boolean inheritsGroup(@NonNull Group group, @NonNull ContextSet contextSet) {
return handle.inheritsGroup(cast(group), contextSet);
}
@Override
public boolean inheritsGroup(@NonNull Group group, @NonNull String server) {
checkGroup(group);
return master.inheritsGroup(((GroupDelegate) group).getMaster(), server);
return handle.inheritsGroup(cast(group), server);
}
@Override
public boolean inheritsGroup(@NonNull Group group, @NonNull String server, @NonNull String world) {
checkGroup(group);
return master.inheritsGroup(((GroupDelegate) group).getMaster(), server, world);
return handle.inheritsGroup(cast(group), server, world);
}
@Override
public void setInheritGroup(@NonNull Group group) throws ObjectAlreadyHasException {
checkGroup(group);
master.setInheritGroup(((GroupDelegate) group).getMaster());
handle.setPermission(NodeFactory.make(cast(group))).throwException();
}
@Override
public void setInheritGroup(@NonNull Group group, @NonNull String server) throws ObjectAlreadyHasException {
checkGroup(group);
master.setInheritGroup(((GroupDelegate) group).getMaster(), server);
handle.setPermission(NodeFactory.make(cast(group), server)).throwException();
}
@Override
public void setInheritGroup(@NonNull Group group, @NonNull String server, @NonNull String world) throws ObjectAlreadyHasException {
checkGroup(group);
master.setInheritGroup(((GroupDelegate) group).getMaster(), server, world);
handle.setPermission(NodeFactory.make(cast(group), server, world)).throwException();
}
@Override
public void setInheritGroup(@NonNull Group group, @NonNull long expireAt) throws ObjectAlreadyHasException {
checkGroup(group);
master.setInheritGroup(((GroupDelegate) group).getMaster(), checkTime(expireAt));
handle.setPermission(NodeFactory.make(cast(group), checkTime(expireAt))).throwException();
}
@Override
public void setInheritGroup(@NonNull Group group, @NonNull String server, @NonNull long expireAt) throws ObjectAlreadyHasException {
checkGroup(group);
master.setInheritGroup(((GroupDelegate) group).getMaster(), server, checkTime(expireAt));
handle.setPermission(NodeFactory.make(cast(group), server, checkTime(expireAt))).throwException();
}
@Override
public void setInheritGroup(@NonNull Group group, @NonNull String server, @NonNull String world, @NonNull long expireAt) throws ObjectAlreadyHasException {
checkGroup(group);
master.setInheritGroup(((GroupDelegate) group).getMaster(), server, world, checkTime(expireAt));
handle.setPermission(NodeFactory.make(cast(group), server, world, checkTime(expireAt))).throwException();
}
@Override
public void unsetInheritGroup(@NonNull Group group) throws ObjectLacksException {
checkGroup(group);
master.unsetInheritGroup(((GroupDelegate) group).getMaster());
handle.unsetPermission(NodeFactory.make(cast(group))).throwException();
}
@Override
public void unsetInheritGroup(@NonNull Group group, @NonNull boolean temporary) throws ObjectLacksException {
checkGroup(group);
master.unsetInheritGroup(((GroupDelegate) group).getMaster(), temporary);
handle.unsetPermission(NodeFactory.make(cast(group), temporary)).throwException();
}
@Override
public void unsetInheritGroup(@NonNull Group group, @NonNull String server) throws ObjectLacksException {
checkGroup(group);
master.unsetInheritGroup(((GroupDelegate) group).getMaster(), server);
handle.unsetPermission(NodeFactory.make(cast(group), server)).throwException();
}
@Override
public void unsetInheritGroup(@NonNull Group group, @NonNull String server, @NonNull String world) throws ObjectLacksException {
checkGroup(group);
master.unsetInheritGroup(((GroupDelegate) group).getMaster(), server, world);
handle.unsetPermission(NodeFactory.make(cast(group), server, world)).throwException();
}
@Override
public void unsetInheritGroup(@NonNull Group group, @NonNull String server, @NonNull boolean temporary) throws ObjectLacksException {
checkGroup(group);
master.unsetInheritGroup(((GroupDelegate) group).getMaster(), server, temporary);
handle.unsetPermission(NodeFactory.make(cast(group), server, temporary)).throwException();
}
@Override
public void unsetInheritGroup(@NonNull Group group, @NonNull String server, @NonNull String world, @NonNull boolean temporary) throws ObjectLacksException {
checkGroup(group);
master.unsetInheritGroup(((GroupDelegate) group).getMaster(), server, world, temporary);
handle.unsetPermission(NodeFactory.make(cast(group), server, world, temporary)).throwException();
}
@Override
public void clearNodes() {
master.clearNodes();
handle.clearNodes();
}
@Override
public List<String> getGroupNames() {
return master.getGroupNames();
return handle.mergePermissionsToList().stream()
.filter(Node::isGroupNode)
.map(Node::getGroupName)
.collect(Collectors.toList());
}
@Override
public List<String> getLocalGroups(@NonNull String server, @NonNull String world) {
return master.getLocalGroups(server, world);
return handle.mergePermissionsToList().stream()
.filter(Node::isGroupNode)
.filter(n -> n.shouldApplyOnWorld(world, false, true))
.filter(n -> n.shouldApplyOnServer(server, false, true))
.map(Node::getGroupName)
.collect(Collectors.toList());
}
@Override
public OptionalInt getWeight() {
return master.getWeight();
return handle.getWeight();
}
@Override
public List<String> getLocalGroups(@NonNull String server) {
return master.getLocalGroups(server);
return handle.mergePermissionsToList().stream()
.filter(Node::isGroupNode)
.filter(n -> n.shouldApplyOnServer(server, false, true))
.map(Node::getGroupName)
.collect(Collectors.toList());
}
public boolean equals(Object o) {
if (o == this) return true;
if (!(o instanceof GroupDelegate)) return false;
GroupDelegate other = (GroupDelegate) o;
return this.getName() == null ? other.getName() == null : this.getName().equals(other.getName());
}
public int hashCode() {
return this.getName().hashCode();
}
}
@@ -1,5 +1,8 @@
/*
* Copyright (c) 2016 Lucko (Luck) <luck@lucko.me>
* 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
@@ -36,105 +39,105 @@ import java.util.Map;
*/
@AllArgsConstructor
public class LPConfigurationDelegate implements LPConfiguration {
private final LuckPermsConfiguration master;
private final LuckPermsConfiguration handle;
@Override
public String getServer() {
return master.get(ConfigKeys.SERVER);
return handle.get(ConfigKeys.SERVER);
}
@Override
public int getSyncTime() {
return master.get(ConfigKeys.SYNC_TIME);
return handle.get(ConfigKeys.SYNC_TIME);
}
@Override
public boolean getIncludeGlobalPerms() {
return master.get(ConfigKeys.INCLUDING_GLOBAL_PERMS);
return handle.get(ConfigKeys.INCLUDING_GLOBAL_PERMS);
}
@Override
public boolean getIncludeGlobalWorldPerms() {
return master.get(ConfigKeys.INCLUDING_GLOBAL_WORLD_PERMS);
return handle.get(ConfigKeys.INCLUDING_GLOBAL_WORLD_PERMS);
}
@Override
public boolean getApplyGlobalGroups() {
return master.get(ConfigKeys.APPLYING_GLOBAL_GROUPS);
return handle.get(ConfigKeys.APPLYING_GLOBAL_GROUPS);
}
@Override
public boolean getApplyGlobalWorldGroups() {
return master.get(ConfigKeys.APPLYING_GLOBAL_WORLD_GROUPS);
return handle.get(ConfigKeys.APPLYING_GLOBAL_WORLD_GROUPS);
}
@Override
public boolean getOnlineMode() {
return master.get(ConfigKeys.ONLINE_MODE);
return handle.get(ConfigKeys.USE_SERVER_UUIDS);
}
@Override
public boolean getApplyWildcards() {
return master.get(ConfigKeys.APPLYING_WILDCARDS);
return handle.get(ConfigKeys.APPLYING_WILDCARDS);
}
@Override
public boolean getApplyRegex() {
return master.get(ConfigKeys.APPLYING_REGEX);
return handle.get(ConfigKeys.APPLYING_REGEX);
}
@Override
public boolean getApplyShorthand() {
return master.get(ConfigKeys.APPLYING_SHORTHAND);
return handle.get(ConfigKeys.APPLYING_SHORTHAND);
}
@Override
public boolean getLogNotify() {
return master.get(ConfigKeys.LOG_NOTIFY);
return handle.get(ConfigKeys.LOG_NOTIFY);
}
@Override
public boolean getEnableOps() {
return master.get(ConfigKeys.OPS_ENABLED);
return handle.get(ConfigKeys.OPS_ENABLED);
}
@Override
public boolean getCommandsAllowOp() {
return master.get(ConfigKeys.COMMANDS_ALLOW_OP);
return handle.get(ConfigKeys.COMMANDS_ALLOW_OP);
}
@Override
public boolean getAutoOp() {
return master.get(ConfigKeys.AUTO_OP);
return handle.get(ConfigKeys.AUTO_OP);
}
@Override
public String getVaultServer() {
return master.get(ConfigKeys.VAULT_SERVER);
return handle.get(ConfigKeys.VAULT_SERVER);
}
@Override
public boolean getVaultIncludeGlobal() {
return master.get(ConfigKeys.VAULT_INCLUDING_GLOBAL);
return handle.get(ConfigKeys.VAULT_INCLUDING_GLOBAL);
}
@Override
public DatastoreConfiguration getDatastoreConfig() {
return master.get(ConfigKeys.DATABASE_VALUES);
return handle.get(ConfigKeys.DATABASE_VALUES);
}
@Override
public String getStorageMethod() {
return master.get(ConfigKeys.STORAGE_METHOD);
return handle.get(ConfigKeys.STORAGE_METHOD);
}
@Override
public boolean getSplitStorage() {
return master.get(ConfigKeys.SPLIT_STORAGE);
return handle.get(ConfigKeys.SPLIT_STORAGE);
}
@Override
public Map<String, String> getSplitStorageOptions() {
return master.get(ConfigKeys.SPLIT_STORAGE_OPTIONS);
return handle.get(ConfigKeys.SPLIT_STORAGE_OPTIONS);
}
}
@@ -1,5 +1,8 @@
/*
* Copyright (c) 2016 Lucko (Luck) <luck@lucko.me>
* 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
@@ -39,100 +42,100 @@ import static me.lucko.luckperms.common.api.ApiUtils.checkName;
*/
@AllArgsConstructor
public class LogDelegate implements Log {
private final me.lucko.luckperms.common.data.Log master;
private final me.lucko.luckperms.common.data.Log handle;
@Override
public SortedSet<LogEntry> getContent() {
return master.getContent();
return handle.getContent();
}
@Override
public SortedSet<LogEntry> getRecent() {
return master.getRecent();
return handle.getRecent();
}
@Override
public SortedMap<Integer, LogEntry> getRecent(int pageNo) {
return master.getRecent(pageNo);
return handle.getRecent(pageNo);
}
@Override
public int getRecentMaxPages() {
return master.getRecentMaxPages();
return handle.getRecentMaxPages();
}
@Override
public SortedSet<LogEntry> getRecent(@NonNull UUID actor) {
return master.getRecent(actor);
return handle.getRecent(actor);
}
@Override
public SortedMap<Integer, LogEntry> getRecent(int pageNo, @NonNull UUID actor) {
return master.getRecent(pageNo, actor);
return handle.getRecent(pageNo, actor);
}
@Override
public int getRecentMaxPages(@NonNull UUID actor) {
return master.getRecentMaxPages(actor);
return handle.getRecentMaxPages(actor);
}
@Override
public SortedSet<LogEntry> getUserHistory(@NonNull UUID uuid) {
return master.getUserHistory(uuid);
return handle.getUserHistory(uuid);
}
@Override
public SortedMap<Integer, LogEntry> getUserHistory(int pageNo, @NonNull UUID uuid) {
return master.getUserHistory(pageNo, uuid);
return handle.getUserHistory(pageNo, uuid);
}
@Override
public int getUserHistoryMaxPages(@NonNull UUID uuid) {
return master.getUserHistoryMaxPages(uuid);
return handle.getUserHistoryMaxPages(uuid);
}
@Override
public SortedSet<LogEntry> getGroupHistory(@NonNull String name) {
return master.getGroupHistory(checkName(name));
return handle.getGroupHistory(checkName(name));
}
@Override
public SortedMap<Integer, LogEntry> getGroupHistory(int pageNo, @NonNull String name) {
return master.getGroupHistory(pageNo, checkName(name));
return handle.getGroupHistory(pageNo, checkName(name));
}
@Override
public int getGroupHistoryMaxPages(@NonNull String name) {
return master.getGroupHistoryMaxPages(checkName(name));
return handle.getGroupHistoryMaxPages(checkName(name));
}
@Override
public SortedSet<LogEntry> getTrackHistory(@NonNull String name) {
return master.getTrackHistory(checkName(name));
return handle.getTrackHistory(checkName(name));
}
@Override
public SortedMap<Integer, LogEntry> getTrackHistory(int pageNo, @NonNull String name) {
return master.getTrackHistory(pageNo, checkName(name));
return handle.getTrackHistory(pageNo, checkName(name));
}
@Override
public int getTrackHistoryMaxPages(@NonNull String name) {
return master.getTrackHistoryMaxPages(checkName(name));
return handle.getTrackHistoryMaxPages(checkName(name));
}
@Override
public SortedSet<LogEntry> getSearch(@NonNull String query) {
return master.getSearch(query);
return handle.getSearch(query);
}
@Override
public SortedMap<Integer, LogEntry> getSearch(int pageNo, @NonNull String query) {
return master.getSearch(pageNo, query);
return handle.getSearch(pageNo, query);
}
@Override
public int getSearchMaxPages(@NonNull String query) {
return master.getSearchMaxPages(query);
return handle.getSearchMaxPages(query);
}
}
@@ -0,0 +1,64 @@
/*
* 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.api.delegates;
import lombok.AllArgsConstructor;
import lombok.NonNull;
import com.google.common.collect.ImmutableList;
import me.lucko.luckperms.api.metastacking.MetaStackDefinition;
import me.lucko.luckperms.api.metastacking.MetaStackElement;
import me.lucko.luckperms.api.metastacking.MetaStackFactory;
import me.lucko.luckperms.common.metastacking.definition.SimpleMetaStackDefinition;
import me.lucko.luckperms.common.metastacking.definition.StandardStackElements;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import java.util.List;
import java.util.Optional;
@AllArgsConstructor
public class MetaStackFactoryDelegate implements MetaStackFactory {
public final LuckPermsPlugin plugin;
@Override
public Optional<MetaStackElement> fromString(@NonNull String definition) {
return StandardStackElements.parseFromString(plugin, definition);
}
@Override
public List<MetaStackElement> fromStrings(@NonNull List<String> definitions) {
if (definitions.isEmpty()) {
return ImmutableList.of();
}
return StandardStackElements.parseList(plugin, definitions);
}
@Override
public MetaStackDefinition createDefinition(List<MetaStackElement> elements, String startSpacer, String middleSpacer, String endSpacer) {
return new SimpleMetaStackDefinition(elements, startSpacer, middleSpacer, endSpacer);
}
}
Loaded 100 of 483 files, more files were not shown because too many files have changed in this diff. Show more