/* * This file is part of GriefDefender, licensed under the MIT License (MIT). * * Copyright (c) bloodmc * 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 com.griefdefender.command; import co.aikar.commands.BaseCommand; import co.aikar.commands.InvalidCommandArgument; import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Maps; import com.google.common.reflect.TypeToken; import com.griefdefender.GDBootstrap; import com.griefdefender.GDPlayerData; import com.griefdefender.GriefDefenderPlugin; import com.griefdefender.api.GriefDefender; import com.griefdefender.api.Tristate; import com.griefdefender.api.claim.Claim; import com.griefdefender.api.claim.ClaimContexts; import com.griefdefender.api.permission.Context; import com.griefdefender.api.permission.ContextKeys; import com.griefdefender.api.permission.PermissionResult; import com.griefdefender.api.permission.option.Option; import com.griefdefender.api.permission.option.Options; import com.griefdefender.api.permission.option.type.CreateModeType; import com.griefdefender.api.permission.option.type.CreateModeTypes; import com.griefdefender.api.permission.option.type.GameModeType; import com.griefdefender.api.permission.option.type.GameModeTypes; import com.griefdefender.api.permission.option.type.WeatherType; import com.griefdefender.api.permission.option.type.WeatherTypes; import com.griefdefender.cache.MessageCache; import com.griefdefender.cache.PermissionHolderCache; import com.griefdefender.claim.GDClaim; import com.griefdefender.configuration.MessageStorage; import com.griefdefender.event.GDCauseStackManager; import com.griefdefender.internal.pagination.PaginationList; import com.griefdefender.listener.CommonEntityEventHandler; import com.griefdefender.permission.GDPermissionHolder; import com.griefdefender.permission.GDPermissionUser; import com.griefdefender.permission.GDPermissions; import com.griefdefender.permission.option.GDOption; import com.griefdefender.permission.option.GDOptions; import com.griefdefender.permission.ui.ClaimClickData; import com.griefdefender.permission.ui.MenuType; import com.griefdefender.permission.ui.OptionData; import com.griefdefender.permission.ui.OptionData.OptionContextHolder; import com.griefdefender.permission.ui.UIHelper; import com.griefdefender.registry.OptionRegistryModule; import com.griefdefender.text.action.GDCallbackHolder; import com.griefdefender.util.CauseContextHelper; import com.griefdefender.util.ChatCaptureUtil; import com.griefdefender.util.PaginationUtil; import com.griefdefender.util.PermissionUtil; import net.kyori.text.Component; import net.kyori.text.TextComponent; import net.kyori.text.adapter.bukkit.TextAdapter; import net.kyori.text.event.ClickEvent; import net.kyori.text.event.HoverEvent; import net.kyori.text.format.TextColor; import net.kyori.text.format.TextDecoration; import org.bukkit.Bukkit; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Objects; import java.util.Set; import java.util.TreeMap; import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; import java.util.regex.Matcher; import java.util.regex.Pattern; @SuppressWarnings({ "unchecked", "rawtypes" }) public abstract class ClaimOptionBase extends BaseCommand { protected GDPermissionHolder subject; protected ClaimSubjectType subjectType; protected String friendlySubjectName; private final Cache lastActiveMenuTypeMap = Caffeine.newBuilder().expireAfterAccess(10, TimeUnit.MINUTES) .build(); protected ClaimOptionBase(ClaimSubjectType type) { this.subjectType = type; } public void execute(Player player, String[] args) throws InvalidCommandArgument { final GDPermissionUser src = PermissionHolderCache.getInstance().getOrCreateUser(player); final GDPermissionHolder commandSubject = subject; String commandOption = null; String value = null; String contexts = null; final String arguments = String.join(" ", args); int index = arguments.indexOf("context["); if (index != -1) { contexts = arguments.substring(index, arguments.length()); } if (args.length > 0) { if (args.length < 2) { throw new InvalidCommandArgument(); } commandOption = args[0]; // Check for quoted string Pattern pattern = Pattern.compile("\"(.*)\""); Matcher matcher = pattern.matcher(arguments); if (matcher.find()) { value = matcher.group(1); } else { value = args[1]; } } Option option = null; if (commandOption != null) { option = GriefDefender.getRegistry().getType(Option.class, commandOption).orElse(null); if (option == null) { TextAdapter.sendComponent(player, MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.OPTION_NOT_FOUND, ImmutableMap.of( "option", commandOption))); return; } if (option != null && !GDOptions.isOptionEnabled(option)) { TextAdapter.sendComponent(player, MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.OPTION_NOT_ENABLED, ImmutableMap.of( "option", commandOption))); return; } if (option == Options.PLAYER_COMMAND_ENTER || option == Options.PLAYER_COMMAND_EXIT) { if (contexts == null) { TextAdapter.sendComponent(player, MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.OPTION_REQUIRES_CONTEXTS, ImmutableMap.of( "contexts", "run-as=[player|console] run-for=[public|owner|member]"))); return; } } } final GDPlayerData playerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(player.getWorld(), player.getUniqueId()); final GDClaim claim = GriefDefenderPlugin.getInstance().dataStore.getClaimAtPlayer(playerData, player.getLocation()); if (claim.isWilderness()) { if(!playerData.canManageWilderness && !playerData.canIgnoreClaim(claim)) { GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().PERMISSION_GLOBAL_OPTION); return; } } else if (!claim.isTown() && !playerData.canManageAdminClaims && !playerData.canIgnoreClaim(claim)) { GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().PERMISSION_PLAYER_OPTION); return; } if (option != null) { if (option.isGlobal()) { if (!player.hasPermission(GDPermissions.MANAGE_GLOBAL_OPTIONS +"." + option.getPermission().toLowerCase())) { GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().PERMISSION_GLOBAL_OPTION); return; } } else if (!player.hasPermission(GDPermissions.USER_CLAIM_OPTIONS +"." + option.getPermission().toLowerCase())) { GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().PERMISSION_PLAYER_OPTION); return; } } if (!playerData.canManageAdminClaims && !playerData.canIgnoreClaim(claim)) { final Component denyMessage = claim.allowEdit(player); if (denyMessage != null) { GriefDefenderPlugin.sendMessage(player, denyMessage); return; } } String optionPermission = option != null ? option.getPermission() : ""; final Set contextSet = CauseContextHelper.generateContexts(optionPermission, player, claim, contexts); if (contextSet == null) { return; } if (option == Options.PLAYER_COMMAND_ENTER || option == Options.PLAYER_COMMAND_EXIT) { final Set requiredKeys = (Set) option.getRequiredContextKeys(); for (String key : requiredKeys) { boolean found = false; for (Context context : contextSet) { if (context.getKey().equalsIgnoreCase(key)) { found = true; break; } } if (!found) { TextAdapter.sendComponent(player, MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.OPTION_REQUIRES_CONTEXTS, ImmutableMap.of( "contexts", key))); return; } } if (contexts == null) { TextAdapter.sendComponent(player, MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.OPTION_REQUIRES_CONTEXTS, ImmutableMap.of( "contexts", "run-as=[player|console] run-for=[public|owner|member]"))); return; } } if (claim != null) { if (commandOption == null && value == null && player.hasPermission(GDPermissions.COMMAND_LIST_CLAIM_OPTIONS)) { showOptionPermissions(src, (GDClaim) claim, MenuType.CLAIM); return; } else if (option != null && value != null) { if (!value.equalsIgnoreCase("undefined") && !((GDOption) option).validateStringValue(value, false)) { GriefDefenderPlugin.sendMessage(player, MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.OPTION_INVALID_VALUE, ImmutableMap.of( "value", value, "option", option.getName(), "type", option.getAllowedType().getSimpleName()))); return; } MenuType type = MenuType.DEFAULT; boolean useClaimContext = true; for (Context context : contextSet) { if (context.getKey().equals(ContextKeys.CLAIM_DEFAULT)) { useClaimContext = false; break; } if (context.getKey().equals(ContextKeys.CLAIM)) { type = MenuType.CLAIM; break; } if (context.getKey().equals(ContextKeys.CLAIM_OVERRIDE)) { type = MenuType.OVERRIDE; useClaimContext = false; break; } } if (!option.isGlobal() && useClaimContext) { contextSet.add(claim.getContext()); if (contextSet.isEmpty() ) { type = MenuType.CLAIM; } } GDCauseStackManager.getInstance().pushCause(player); PermissionUtil.getInstance().setOptionValue(this.subject, option.getPermission(), value, contextSet); TextAdapter.sendComponent(player, MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.OPTION_SET_TARGET, ImmutableMap.of( "type", type.name().toUpperCase(), "option", option.getName(), "contexts", UIHelper.getFriendlyContextString(claim, contextSet), "value", TextComponent.of(value).color(TextColor.LIGHT_PURPLE), "target", subject.getFriendlyName()))); GDCauseStackManager.getInstance().popCause(); return; } } } protected void showOptionPermissions(GDPermissionUser src, GDClaim claim, MenuType displayType) { boolean isAdmin = false; final Player player = src.getOnlinePlayer(); final GDPlayerData playerData = src.getInternalPlayerData(); final boolean isTaxEnabled = GriefDefenderPlugin.getGlobalConfig().getConfig().economy.taxSystem; if (player.hasPermission(GDPermissions.DELETE_CLAIM_ADMIN)) { isAdmin = true; } final MenuType lastFlagType = this.lastActiveMenuTypeMap.getIfPresent(player.getUniqueId()); if (lastFlagType != null && lastFlagType != displayType) { PaginationUtil.getInstance().resetActivePage(player.getUniqueId()); } final Component whiteOpenBracket = TextComponent.of("[", TextColor.AQUA); final Component whiteCloseBracket = TextComponent.of("]", TextColor.AQUA); final Component showOverrideText = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.UI_CLICK_FILTER_TYPE, ImmutableMap.of("type", TextComponent.of("OVERRIDE", TextColor.RED))); final Component showDefaultText = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.UI_CLICK_FILTER_TYPE, ImmutableMap.of("type", TextComponent.of("DEFAULT", TextColor.LIGHT_PURPLE))); final Component showClaimText = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.UI_CLICK_FILTER_TYPE, ImmutableMap.of("type", TextComponent.of("CLAIM", TextColor.GOLD))); final Component showInheritText = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.UI_CLICK_FILTER_TYPE, ImmutableMap.of("type", TextComponent.of("INHERIT", TextColor.AQUA))); Component defaultFlagText = TextComponent.empty(); if (isAdmin) { defaultFlagText = TextComponent.builder("") .append(displayType == MenuType.DEFAULT ? TextComponent.builder("") .append(whiteOpenBracket) .append("DEFAULT", TextColor.LIGHT_PURPLE) .append(whiteCloseBracket).build() : TextComponent.of("DEFAULT", TextColor.GRAY)) .clickEvent(ClickEvent.runCommand(GDCallbackHolder.getInstance().createCallbackRunCommand(createClaimOptionConsumer(src, claim, MenuType.DEFAULT)))) .hoverEvent(HoverEvent.showText(showDefaultText)).build(); } final Component overrideFlagText = TextComponent.builder("") .append(displayType == MenuType.OVERRIDE ? TextComponent.builder("") .append(whiteOpenBracket) .append("OVERRIDE", TextColor.RED) .append(whiteCloseBracket).build() : TextComponent.of("OVERRIDE", TextColor.GRAY)) .clickEvent(ClickEvent.runCommand(GDCallbackHolder.getInstance().createCallbackRunCommand(createClaimOptionConsumer(src, claim, MenuType.OVERRIDE)))) .hoverEvent(HoverEvent.showText(showOverrideText)).build(); final Component claimFlagText = TextComponent.builder("") .append(displayType == MenuType.CLAIM ? TextComponent.builder("") .append(whiteOpenBracket) .append("CLAIM", TextColor.YELLOW) .append(whiteCloseBracket).build() : TextComponent.of("CLAIM", TextColor.GRAY)) .clickEvent(ClickEvent.runCommand(GDCallbackHolder.getInstance().createCallbackRunCommand(createClaimOptionConsumer(src, claim, MenuType.CLAIM)))) .hoverEvent(HoverEvent.showText(showClaimText)).build(); final Component inheritFlagText = TextComponent.builder("") .append(displayType == MenuType.INHERIT ? TextComponent.builder("") .append(whiteOpenBracket) .append("INHERIT", TextColor.AQUA) .append(whiteCloseBracket).build() : TextComponent.of("INHERIT", TextColor.GRAY)) .clickEvent(ClickEvent.runCommand(GDCallbackHolder.getInstance().createCallbackRunCommand(createClaimOptionConsumer(src, claim, MenuType.INHERIT)))) .hoverEvent(HoverEvent.showText(showInheritText)).build(); Component claimOptionHead = TextComponent.empty(); if (this.subjectType == ClaimSubjectType.GLOBAL) { if (isAdmin) { claimOptionHead = TextComponent.builder("") .append(" Displaying : ", TextColor.AQUA) .append(defaultFlagText) .append(" ") .append(claimFlagText) .append(" ") .append(inheritFlagText) .append(" ") .append(overrideFlagText).build(); } else { claimOptionHead = TextComponent.builder("") .append(" Displaying : ", TextColor.AQUA) .append(claimFlagText) .append(" ") .append(inheritFlagText) .append(" ") .append(overrideFlagText).build(); } } else { claimOptionHead = TextComponent.builder("") .append(" " + this.subjectType.getFriendlyName() + " ", TextColor.AQUA) .append(this.friendlySubjectName, TextColor.YELLOW) .append(" : ", TextColor.AQUA) .append(claimFlagText) .append(" ") .append(inheritFlagText) .append(" ") .append(overrideFlagText).build(); } Set defaultContexts = new HashSet<>(); Set overrideContexts = new HashSet<>(); if (claim.isAdminClaim()) { defaultContexts.add(ClaimContexts.ADMIN_DEFAULT_CONTEXT); overrideContexts.add(ClaimContexts.ADMIN_OVERRIDE_CONTEXT); } else if (claim.isBasicClaim() || claim.isSubdivision()) { defaultContexts.add(ClaimContexts.BASIC_DEFAULT_CONTEXT); overrideContexts.add(ClaimContexts.BASIC_OVERRIDE_CONTEXT); } else if (claim.isTown()) { defaultContexts.add(ClaimContexts.TOWN_DEFAULT_CONTEXT); overrideContexts.add(ClaimContexts.TOWN_OVERRIDE_CONTEXT); } else { defaultContexts.add(ClaimContexts.WILDERNESS_DEFAULT_CONTEXT); overrideContexts.add(ClaimContexts.WILDERNESS_OVERRIDE_CONTEXT); } if (!claim.isWilderness()) { defaultContexts.add(ClaimContexts.USER_DEFAULT_CONTEXT); overrideContexts.add(ClaimContexts.USER_OVERRIDE_CONTEXT); } defaultContexts.add(ClaimContexts.GLOBAL_DEFAULT_CONTEXT); overrideContexts.add(ClaimContexts.GLOBAL_OVERRIDE_CONTEXT); overrideContexts.add(claim.getOverrideClaimContext()); Map filteredContextMap = new HashMap<>(); for (Map.Entry, Map>> mapEntry : PermissionUtil.getInstance().getTransientOptions(GriefDefenderPlugin.GD_OPTION_HOLDER).entrySet()) { final Set contextSet = mapEntry.getKey(); if (contextSet.contains(claim.getDefaultTypeContext()) || (contextSet.contains(ClaimContexts.GLOBAL_DEFAULT_CONTEXT) || (!claim.isWilderness() && contextSet.contains(ClaimContexts.USER_DEFAULT_CONTEXT)))) { this.addFilteredContexts(src, filteredContextMap, contextSet, MenuType.DEFAULT, mapEntry.getValue()); } } if (displayType == MenuType.DEFAULT || displayType == MenuType.CLAIM) { final Set contexts = new HashSet<>(); contexts.add(ClaimContexts.GLOBAL_DEFAULT_CONTEXT); if (!claim.isWilderness()) { contexts.add(ClaimContexts.USER_DEFAULT_CONTEXT); } for (Option option : OptionRegistryModule.getInstance().getAll()) { if (option.isGlobal() && displayType == MenuType.CLAIM) { continue; } if (!GDOptions.isOptionEnabled(option)) { continue; } // commands are special-cased as they use a List and cannot show up with no data if (option == Options.PLAYER_COMMAND_ENTER || option == Options.PLAYER_COMMAND_EXIT) { continue; } boolean found = false; for (Entry optionEntry : filteredContextMap.entrySet()) { if (optionEntry.getValue().option == option) { found = true; break; } } if (!found) { filteredContextMap.put(option.getPermission(), new OptionData(option, option.getDefaultValue().toString(), MenuType.DEFAULT, contexts)); } } } for (Map.Entry, Map>> mapEntry : PermissionUtil.getInstance().getPermanentOptions(this.subject).entrySet()) { final Set contextSet = mapEntry.getKey(); if (contextSet.contains(ClaimContexts.GLOBAL_DEFAULT_CONTEXT) || (!claim.isWilderness() && contextSet.contains(ClaimContexts.USER_DEFAULT_CONTEXT))) { this.addFilteredContexts(src, filteredContextMap, contextSet, MenuType.DEFAULT, mapEntry.getValue()); } if (contextSet.contains(claim.getDefaultTypeContext())) { this.addFilteredContexts(src, filteredContextMap, contextSet, MenuType.DEFAULT, mapEntry.getValue()); } if (displayType != MenuType.DEFAULT) { //if (claim.isTown() || isAdmin) { if (contextSet.contains(claim.getContext())) { this.addFilteredContexts(src, filteredContextMap, contextSet, MenuType.CLAIM, mapEntry.getValue()); } //} if (contextSet.contains(ClaimContexts.GLOBAL_OVERRIDE_CONTEXT) || (!claim.isWilderness() && contextSet.contains(ClaimContexts.USER_OVERRIDE_CONTEXT))) { this.addFilteredContexts(src, filteredContextMap, contextSet, MenuType.OVERRIDE, mapEntry.getValue()); } if (contextSet.contains(claim.getOverrideClaimContext())) { this.addFilteredContexts(src, filteredContextMap, contextSet, MenuType.OVERRIDE, mapEntry.getValue()); } else if (contextSet.contains(claim.getOverrideTypeContext())) { this.addFilteredContexts(src, filteredContextMap, contextSet, MenuType.OVERRIDE, mapEntry.getValue()); } } } Map, ClaimClickData> inheritPermissionMap = Maps.newHashMap(); final List inheritParents = claim.getInheritedParents(); Collections.reverse(inheritParents); for (Claim current : inheritParents) { GDClaim currentClaim = (GDClaim) current; for (Map.Entry, Map>> mapEntry : PermissionUtil.getInstance().getPermanentOptions(this.subject).entrySet()) { final Set contextSet = mapEntry.getKey(); if (contextSet.contains(currentClaim.getContext())) { inheritPermissionMap.put(mapEntry.getKey(), new ClaimClickData(currentClaim, mapEntry.getValue())); } } } final Map> textMap = new TreeMap<>(); for (Entry mapEntry : filteredContextMap.entrySet()) { final OptionData optionData = mapEntry.getValue(); final Option option = optionData.option; if (option.getName().contains("tax") && !GriefDefenderPlugin.getGlobalConfig().getConfig().economy.taxSystem) { continue; } if (option.isGlobal() && displayType == MenuType.CLAIM) { continue; } for (OptionContextHolder optionHolder : optionData.optionContextMap.values()) { if (displayType != MenuType.CLAIM && optionHolder.getType() != displayType) { continue; } final Set contexts = optionHolder.getAllContexts(); Component optionText = getClickableOptionComponent(src, claim, option, optionHolder, contexts, displayType); final int hashCode = Objects.hash(option.getPermission(), contexts); Map componentMap = textMap.get(option.getPermission()); if (componentMap == null) { componentMap = new HashMap<>(); componentMap.put(hashCode, optionText); textMap.put(option.getPermission(), componentMap); } else { componentMap.put(hashCode, optionText); } } } List textList = new ArrayList<>(); for (Entry> mapEntry : textMap.entrySet()) { textList.addAll(mapEntry.getValue().values()); } Collections.sort(textList, UIHelper.PLAIN_COMPARATOR); int fillSize = 20 - (textList.size() + 2); Component footer = null; if (player != null && player.hasPermission(GDPermissions.CHAT_CAPTURE)) { footer = ChatCaptureUtil.getInstance().createRecordChatComponent(player, claim, playerData, "claimoption"); fillSize = 20 - (textList.size() + 3); } for (int i = 0; i < fillSize; i++) { textList.add(TextComponent.of(" ")); } PaginationList.Builder paginationBuilder = PaginationList.builder() .title(claimOptionHead).padding(TextComponent.builder(" ").decoration(TextDecoration.STRIKETHROUGH, true).build()).contents(textList).footer(footer); final PaginationList paginationList = paginationBuilder.build(); Integer activePage = 1; activePage = PaginationUtil.getInstance().getActivePage(player.getUniqueId()); if (activePage == null) { activePage = 1; } this.lastActiveMenuTypeMap.put(player.getUniqueId(), displayType); paginationList.sendTo(player, activePage); } private void addFilteredContexts(GDPermissionUser src, Map filteredContextMap, Set contexts, MenuType type, Map> permissions) { final Player player = src.getOnlinePlayer(); final GDPlayerData playerData = src.getInternalPlayerData(); for (Map.Entry> permissionEntry : permissions.entrySet()) { final Option option = OptionRegistryModule.getInstance().getById(permissionEntry.getKey()).orElse(null); if (option == null) { continue; } if (option.getName().contains("tax") && !GriefDefenderPlugin.getGlobalConfig().getConfig().economy.taxSystem) { continue; } if (option.isGlobal()) { if (!player.hasPermission(GDPermissions.MANAGE_GLOBAL_OPTIONS +"." + option.getName().toLowerCase())) { continue; } } else if (((GDOption) option).isAdmin()) { if (!player.hasPermission(GDPermissions.MANAGE_ADMIN_OPTIONS +"." + option.getName().toLowerCase())) { continue; } } else { if (!player.hasPermission(GDPermissions.USER_CLAIM_OPTIONS +"." + option.getName().toLowerCase())) { continue; } } final OptionData optionData = filteredContextMap.get(permissionEntry.getKey()); String optionValue = permissionEntry.getValue().get(0); if (option.multiValued()) { optionValue = ""; for (String entry : permissionEntry.getValue()) { if (optionValue.isEmpty()) { optionValue += entry; } else { optionValue = optionValue + "\\|" + entry; } } } if (optionData != null) { optionData.addContexts(option, optionValue, type, contexts); } else { filteredContextMap.put(permissionEntry.getKey(), new OptionData(option, optionValue, type, contexts)); } } } private Component getClickableOptionComponent(GDPermissionUser src, GDClaim claim, Option option, OptionContextHolder optionHolder, Set contexts, MenuType displayType) { final Player player = src.getOnlinePlayer(); final GDPlayerData playerData = src.getInternalPlayerData(); boolean hasEditPermission = true; Component hoverEventText = TextComponent.empty(); final MenuType flagType = optionHolder.getType(); if (flagType == MenuType.DEFAULT) { if (!playerData.canManageGlobalOptions) { hoverEventText = MessageCache.getInstance().PERMISSION_OPTION_DEFAULTS; hasEditPermission = false; } } else if (flagType == MenuType.OVERRIDE) { if (!playerData.canManageOverrideOptions) { hoverEventText = MessageCache.getInstance().PERMISSION_OPTION_OVERRIDES; hasEditPermission = false; } } else if (flagType == MenuType.INHERIT) { hoverEventText = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.OPTION_UI_INHERIT_PARENT, ImmutableMap.of("name", claim.getFriendlyNameType())); hasEditPermission = false; } if (displayType == MenuType.CLAIM) { Component denyReason = claim.allowEdit(player); if (denyReason != null) { hoverEventText = denyReason; hasEditPermission = false; } else { if (option.isGlobal()) { if (!player.hasPermission(GDPermissions.MANAGE_GLOBAL_OPTIONS +"." + option.getName().toLowerCase())) { hoverEventText = MessageCache.getInstance().PERMISSION_OPTION_USE; hasEditPermission = false; } } else if (((GDOption) option).isAdmin()) { if (!player.hasPermission(GDPermissions.MANAGE_ADMIN_OPTIONS +"." + option.getName().toLowerCase())) { hoverEventText = MessageCache.getInstance().PERMISSION_OPTION_USE; hasEditPermission = false; } } else { if (!player.hasPermission(GDPermissions.USER_CLAIM_OPTIONS +"." + option.getName().toLowerCase())) { hoverEventText = MessageCache.getInstance().PERMISSION_OPTION_USE; hasEditPermission = false; } } } } boolean customContexts = this.containsCustomContext(option, contexts); Component optionContexts = UIHelper.getFriendlyContextString(claim, contexts); String currentValue = optionHolder.getValue(); TextColor color = optionHolder.getColor(); boolean isNumber = false; if (option.getAllowedType().isAssignableFrom(Integer.class) || option.getAllowedType().isAssignableFrom(Double.class)) { isNumber = true; } TextComponent.Builder builder = null; if (isNumber && hasEditPermission) { builder = TextComponent.builder() .append(getOptionText(option, contexts)) .append(" ") .append(TextComponent.builder() .append(TextComponent.of("< ").decoration(TextDecoration.BOLD, true)) .clickEvent(ClickEvent.runCommand(GDCallbackHolder.getInstance().createCallbackRunCommand(newOptionValueConsumer(src, claim, option, optionHolder, contexts, displayType, true))))) .append(currentValue.toLowerCase(), color); } else { if (hoverEventText == TextComponent.empty() && hasEditPermission) { hoverEventText = MessageCache.getInstance().CLAIMINFO_UI_CLICK_TOGGLE; } final TextComponent valueNoHover = TextComponent.builder() .append(currentValue.toLowerCase(), color).build(); final TextComponent valueHover = TextComponent.builder() .append(currentValue.toLowerCase(), color) .hoverEvent(HoverEvent.showText( hoverEventText .append(this.getHoverContextComponent(contexts)))) .build(); builder = TextComponent.builder() .append(getOptionText(option, contexts)) .append(" ") .append(hoverEventText != TextComponent.empty() ? valueHover : valueNoHover); } if (hasEditPermission) { if (!option.getAllowedType().isAssignableFrom(Integer.class) && !option.getAllowedType().isAssignableFrom(Double.class)) { this.appendContexts(builder, contexts); builder.clickEvent(ClickEvent.runCommand(GDCallbackHolder.getInstance().createCallbackRunCommand(newOptionValueConsumer(src, claim, option, optionHolder, contexts, displayType, false)))); } else { builder.append(TextComponent.builder().append(TextComponent.of(" >").decoration(TextDecoration.BOLD, true))); this.appendContexts(builder, contexts); builder.clickEvent(ClickEvent.runCommand(GDCallbackHolder.getInstance().createCallbackRunCommand(newOptionValueConsumer(src, claim, option, optionHolder, contexts, displayType, false)))); } if (option.getAllowedType().isAssignableFrom(String.class)) { builder.clickEvent(createClickEvent(player, option)); } } if (displayType == MenuType.DEFAULT) { builder.hoverEvent(HoverEvent.showText(TextComponent.builder().append(MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.OPTION_NOT_SET, ImmutableMap.of( "option", TextComponent.of(option.getName().toLowerCase()).color(TextColor.GREEN), "value", TextComponent.of(currentValue).color(TextColor.GOLD)))).build())); } if (customContexts) { builder.append(" ") .append("[", TextColor.WHITE) .append(TextComponent.builder() .append("x", TextColor.RED) .hoverEvent(HoverEvent.showText(MessageCache.getInstance().FLAG_UI_CLICK_REMOVE)) .clickEvent(ClickEvent.runCommand(GDCallbackHolder.getInstance().createCallbackRunCommand(removeOptionValueConsumer(src, claim, option, optionHolder, contexts, displayType)))) .build()) .append("]", TextColor.WHITE); } return builder.build(); } private Component getHoverContextComponent(Set contexts) { if (contexts.isEmpty()) { return TextComponent.empty(); } TextComponent.Builder builder = TextComponent.builder() .append("\n\n").append(MessageCache.getInstance().LABEL_CONTEXT).append(": \n"); for (Context context : contexts) { final String key = context.getKey(); final String value = context.getValue(); TextColor keyColor = TextColor.AQUA; builder.append(key, keyColor) .append("=", TextColor.WHITE) .append(value.replace("minecraft:", ""), TextColor.GRAY) .append("\n"); } return builder.build(); } private void appendContexts(TextComponent.Builder builder, Set contexts) { // check source/target Component source = null; Component target = null; final Component whiteOpenBracket = TextComponent.of("[", TextColor.WHITE); final Component whiteCloseBracket = TextComponent.of("]", TextColor.WHITE); for (Context context : contexts) { if (context.getKey().equals(ContextKeys.SOURCE)) { source = TextComponent.builder() .append(whiteOpenBracket) .append("s", TextColor.GREEN) .append("=", TextColor.WHITE) .append(context.getValue().replace("minecraft:", ""), TextColor.GOLD) .append(whiteCloseBracket) .hoverEvent(HoverEvent.showText(MessageCache.getInstance().LABEL_SOURCE)) .build(); builder.append(" ").append(source); } else if (context.getKey().equals(ContextKeys.TARGET)) { target = TextComponent.builder() .append(whiteOpenBracket) .append("t", TextColor.GREEN) .append("=", TextColor.WHITE) .append(context.getValue().replace("minecraft:", ""), TextColor.GOLD) .append(whiteCloseBracket) .hoverEvent(HoverEvent.showText(MessageCache.getInstance().LABEL_TARGET)) .build(); builder.append(" ").append(target); } } } private ClickEvent createClickEvent(Player src, Option option) { return ClickEvent.suggestCommand("/gd option " + option.getName() + " "); } private Consumer newOptionValueConsumer(GDPermissionUser src, GDClaim claim, Option option, OptionContextHolder optionHolder, Set contexts, MenuType displayType, boolean leftArrow) { final String currentValue = optionHolder.getValue(); return consumer -> { String newValue = ""; if (option.getAllowedType().isAssignableFrom(Tristate.class)) { Tristate value = getMenuTypeValue(TypeToken.of(Tristate.class), currentValue); if (value == Tristate.TRUE) { newValue = "false"; } else if (value == Tristate.FALSE && optionHolder.getType() != MenuType.DEFAULT) { newValue = "undefined"; } else { newValue = "true"; } if (displayType == MenuType.CLAIM && optionHolder.getType() == MenuType.DEFAULT && newValue.equalsIgnoreCase(currentValue)) { newValue = "undefined"; } } if (option.getAllowedType().isAssignableFrom(Boolean.class)) { Boolean value = getMenuTypeValue(TypeToken.of(Boolean.class), currentValue); Tristate result = Tristate.UNDEFINED; if (displayType == MenuType.DEFAULT || (displayType == MenuType.CLAIM && optionHolder.getType() == MenuType.DEFAULT)) { result = Tristate.fromBoolean(!value); } else { // Always fall back to transient default result = Tristate.UNDEFINED; } newValue = result.toString().toLowerCase(); } if (option.getAllowedType().isAssignableFrom(GameModeType.class)) { GameModeType value = getMenuTypeValue(TypeToken.of(GameModeType.class), currentValue); if (value == null || value == GameModeTypes.UNDEFINED) { newValue = "adventure"; } else if (value == GameModeTypes.ADVENTURE) { newValue = "creative"; } else if (value == GameModeTypes.CREATIVE) { newValue = "survival"; } else if (value == GameModeTypes.SURVIVAL) { newValue = "spectator"; } else { newValue = "undefined"; } } if (option.getAllowedType().isAssignableFrom(CreateModeType.class)) { CreateModeType value = getMenuTypeValue(TypeToken.of(CreateModeType.class), currentValue); if (value == null || value == CreateModeTypes.UNDEFINED) { newValue = "area"; } else if (value == CreateModeTypes.AREA) { newValue = "volume"; } else { newValue = "undefined"; } } if (option.getAllowedType().isAssignableFrom(WeatherType.class)) { WeatherType value = getMenuTypeValue(TypeToken.of(WeatherType.class), currentValue); if (value == null || value == WeatherTypes.UNDEFINED) { newValue = "clear"; } else if (value == WeatherTypes.CLEAR) { newValue = "downfall"; } else { newValue = "undefined"; } } if (option.getAllowedType().isAssignableFrom(Integer.class)) { Integer value = getMenuTypeValue(TypeToken.of(Integer.class), currentValue); if (leftArrow) { if (value == null || value < 1) { TextAdapter.sendComponent(src.getOnlinePlayer(), MessageCache.getInstance().OPTION_UI_NOT_DEFINED.color(TextColor.RED)); } else { if ((option == Options.MIN_LEVEL || option == Options.MAX_LEVEL || option == Options.MIN_SIZE_Y || option == Options.MAX_SIZE_Y) && value == 1) { value = null; } else { value -= 1; if (value <= 0) { if (option == Options.MAX_LEVEL) { value = 255; } } } } } else { if (value == null) { value = 1; } else { if ((option == Options.MIN_SIZE_Y || option == Options.MAX_SIZE_Y) && value == 256) { value = null; } else if ((option == Options.MIN_LEVEL || option == Options.MAX_LEVEL) && value == 255) { value = null; } else { value += 1; } } } newValue = value == null ? "undefined" :String.valueOf(value); } if (option.getAllowedType().isAssignableFrom(Double.class)) { Double value = getMenuTypeValue(TypeToken.of(Double.class), currentValue); if (leftArrow) { if (value == null || value < 0) { TextAdapter.sendComponent(src.getOnlinePlayer(), MessageCache.getInstance().OPTION_UI_NOT_DEFINED.color(TextColor.RED)); } else { value -= 0.1; if (option == Options.ABANDON_RETURN_RATIO && value <= 0) { value = null; } else { if (value < 0) { value = 0.0; } } } } else { if (value == null) { value = 1.0; } else { value += 0.1; } } newValue = value == null ? "undefined" : String.format("%.1f", value); } Set newContexts = new HashSet<>(contexts); if (displayType == MenuType.CLAIM) { final Iterator iterator = newContexts.iterator(); while (iterator.hasNext()) { final Context context = iterator.next(); if (context.getKey().equals("gd_claim_default")) { iterator.remove(); } } newContexts.add(claim.getContext()); } Context serverContext = null; final String serverName = PermissionUtil.getInstance().getServerName(); if (serverName != null) { serverContext = new Context("server", serverName); } // Check server context final Iterator iterator = newContexts.iterator(); boolean hasServerContext = false; while (iterator.hasNext()) { final Context context = iterator.next(); if (context.getKey().equals("server")) { hasServerContext = true; break; } } if (!hasServerContext && serverContext != null) { newContexts.add(serverContext); } final Context permServerContext = serverContext; final String permValue = newValue; final CompletableFuture future = PermissionUtil.getInstance().setOptionValue(this.subject, option.getPermission(), newValue, newContexts); future.thenAcceptAsync(r -> { if (!r.successful()) { // Try again without server context newContexts.remove(permServerContext); CompletableFuture newFuture = PermissionUtil.getInstance().setOptionValue(this.subject, option.getPermission(), permValue, newContexts, false); newFuture.thenAccept(r2 -> { if (r2.successful()) { Bukkit.getScheduler().runTask(GDBootstrap.getInstance(), () -> { if (option == Options.PLAYER_WEATHER) { CommonEntityEventHandler.getInstance().checkPlayerWeather(src, claim, claim, true); } showOptionPermissions(src, claim, displayType); }); } }); } else { Bukkit.getScheduler().runTask(GDBootstrap.getInstance(), () -> { if (option == Options.PLAYER_WEATHER) { CommonEntityEventHandler.getInstance().checkPlayerWeather(src, claim, claim, true); } showOptionPermissions(src, claim, displayType); }); } }); }; } private Consumer removeOptionValueConsumer(GDPermissionUser src, GDClaim claim, Option option, OptionContextHolder optionHolder, Set contexts, MenuType displayType) { return consumer -> { final CompletableFuture future = PermissionUtil.getInstance().setOptionValue(this.subject, option.getPermission(), "undefined", contexts); future.thenAccept(r -> { if (r.successful()) { Bukkit.getScheduler().runTask(GDBootstrap.getInstance(), () -> { showOptionPermissions(src, claim, displayType); }); } }); }; } private Component getOptionText(Option option, Set contexts) { boolean customContext = this.containsCustomContext(option, contexts); final Component optionText = TextComponent.builder().color(customContext ? TextColor.YELLOW : TextColor.GREEN).append(option.getName() + " ") .hoverEvent(HoverEvent.showText(TextComponent.builder() .append(option.getDescription()) .build())).build(); return optionText; } private static T getMenuTypeValue(TypeToken type, String value) { if (type.getRawType().isAssignableFrom(Double.class)) { Double newValue = null; try { newValue = Double.valueOf(value); } catch (NumberFormatException e) { return null; } return (T) newValue; } if (type.getRawType().isAssignableFrom(Integer.class)) { Integer newValue = null; try { newValue = Integer.valueOf(value); } catch (NumberFormatException e) { return null; } return (T) newValue; } if (type.getRawType().isAssignableFrom(String.class)) { return (T) value; } if (type.getRawType().isAssignableFrom(Boolean.class)) { if (value.equalsIgnoreCase("false")) { return (T) Boolean.valueOf(value); } else if (value.equalsIgnoreCase("true")) { return (T) Boolean.valueOf(value); } } if (type.getRawType().isAssignableFrom(Tristate.class)) { if (value.equalsIgnoreCase("undefined")) { return (T) Tristate.UNDEFINED; } if (value.equalsIgnoreCase("true")) { return (T) Tristate.TRUE; } if (value.equalsIgnoreCase("false")) { return (T) Tristate.FALSE; } int permValue = 0; try { permValue = Integer.parseInt(value); } catch (NumberFormatException e) { return (T) Tristate.UNDEFINED; } if (permValue == 0) { return (T) Tristate.UNDEFINED; } return (T) (permValue == 1 ? Tristate.TRUE : Tristate.FALSE); } if (type.getRawType().isAssignableFrom(GameModeType.class)) { if (value.equalsIgnoreCase("undefined")) { return (T) GameModeTypes.UNDEFINED; } if (value.equalsIgnoreCase("adventure")) { return (T) GameModeTypes.ADVENTURE; } if (value.equalsIgnoreCase("creative")) { return (T) GameModeTypes.CREATIVE; } if (value.equalsIgnoreCase("survival")) { return (T) GameModeTypes.SURVIVAL; } if (value.equalsIgnoreCase("spectator")) { return (T) GameModeTypes.SPECTATOR; } } if (type.getRawType().isAssignableFrom(WeatherType.class)) { if (value.equalsIgnoreCase("undefined")) { return (T) WeatherTypes.UNDEFINED; } if (value.equalsIgnoreCase("clear")) { return (T) WeatherTypes.CLEAR; } if (value.equalsIgnoreCase("downfall")) { return (T) WeatherTypes.DOWNFALL; } } if (type.getRawType().isAssignableFrom(CreateModeType.class)) { if (value.equalsIgnoreCase("undefined")) { return (T) CreateModeTypes.UNDEFINED; } if (value.equalsIgnoreCase("area")) { return (T) CreateModeTypes.AREA; } if (value.equalsIgnoreCase("volume")) { return (T) CreateModeTypes.VOLUME; } } return null; } private Consumer createClaimOptionConsumer(GDPermissionUser src, GDClaim claim, MenuType optionType) { return consumer -> { showOptionPermissions(src, claim, optionType); }; } private boolean containsCustomContext(Option option, Set contexts) { boolean hasClaimContext = false; for (Context context : contexts) { if (context.getKey().equals("gd_claim")) { hasClaimContext = true; continue; } // Options with a claim context is considered custom if (context.getKey().equals("gd_claim_default") || context.getKey().equals("server")) { continue; } return true; } // Always treat double and integer options as custom if not default if (hasClaimContext) { if (option.getAllowedType().isAssignableFrom(Double.class) || option.getAllowedType().isAssignableFrom(Integer.class)) { return true; } } return false; } }