GriefDefender/sponge/src/main/java/com/griefdefender/command/ClaimOptionBase.java

1066 lines
53 KiB
Java

/*
* 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.spongeapi.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.spongepowered.api.Sponge;
import org.spongepowered.api.command.CommandSource;
import org.spongepowered.api.entity.living.player.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<UUID, MenuType> 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<Context> contextSet = CauseContextHelper.generateContexts(optionPermission, player, claim, contexts);
if (contextSet == null) {
return;
}
if (option == Options.PLAYER_COMMAND_ENTER || option == Options.PLAYER_COMMAND_EXIT) {
final Set<String> requiredKeys = (Set<String>) 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<Context> defaultContexts = new HashSet<>();
Set<Context> 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<String, OptionData> filteredContextMap = new HashMap<>();
for (Map.Entry<Set<Context>, Map<String, List<String>>> mapEntry : PermissionUtil.getInstance().getTransientOptions(GriefDefenderPlugin.GD_OPTION_HOLDER).entrySet()) {
final Set<Context> 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<Context> 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<String, OptionData> 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<Set<Context>, Map<String, List<String>>> mapEntry : PermissionUtil.getInstance().getPermanentOptions(this.subject).entrySet()) {
final Set<Context> 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<Set<Context>, ClaimClickData> inheritPermissionMap = Maps.newHashMap();
final List<Claim> inheritParents = claim.getInheritedParents();
Collections.reverse(inheritParents);
for (Claim current : inheritParents) {
GDClaim currentClaim = (GDClaim) current;
for (Map.Entry<Set<Context>, Map<String, List<String>>> mapEntry : PermissionUtil.getInstance().getPermanentOptions(this.subject).entrySet()) {
final Set<Context> contextSet = mapEntry.getKey();
if (contextSet.contains(currentClaim.getContext())) {
inheritPermissionMap.put(mapEntry.getKey(), new ClaimClickData(currentClaim, mapEntry.getValue()));
}
}
}
final Map<String, Map<Integer, Component>> textMap = new TreeMap<>();
for (Entry<String, OptionData> 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<Context> contexts = optionHolder.getAllContexts();
Component optionText = getClickableOptionComponent(src, claim, option, optionHolder, contexts, displayType);
final int hashCode = Objects.hash(option.getPermission(), contexts);
Map<Integer, Component> 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<Component> textList = new ArrayList<>();
for (Entry<String, Map<Integer, Component>> 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<String, OptionData> filteredContextMap, Set<Context> contexts, MenuType type, Map<String, List<String>> permissions) {
final Player player = src.getOnlinePlayer();
final GDPlayerData playerData = src.getInternalPlayerData();
for (Map.Entry<String, List<String>> 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<Context> 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<Context> 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<Context> 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<CommandSource> newOptionValueConsumer(GDPermissionUser src, GDClaim claim, Option option, OptionContextHolder optionHolder, Set<Context> 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<Context> newContexts = new HashSet<>(contexts);
if (displayType == MenuType.CLAIM) {
final Iterator<Context> 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<Context> 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<PermissionResult> 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<PermissionResult> newFuture = PermissionUtil.getInstance().setOptionValue(this.subject, option.getPermission(), permValue, newContexts, false);
newFuture.thenAccept(r2 -> {
if (r2.successful()) {
Sponge.getScheduler().createSyncExecutor(GDBootstrap.getInstance()).execute(() -> {
if (option == Options.PLAYER_WEATHER) {
CommonEntityEventHandler.getInstance().checkPlayerWeather(src, claim, claim, true);
}
showOptionPermissions(src, claim, displayType);
});
}
});
} else {
Sponge.getScheduler().createSyncExecutor(GDBootstrap.getInstance()).execute(() -> {
if (option == Options.PLAYER_WEATHER) {
CommonEntityEventHandler.getInstance().checkPlayerWeather(src, claim, claim, true);
}
showOptionPermissions(src, claim, displayType);
});
}
});
};
}
private Consumer<CommandSource> removeOptionValueConsumer(GDPermissionUser src, GDClaim claim, Option option, OptionContextHolder optionHolder, Set<Context> contexts, MenuType displayType) {
return consumer -> {
final CompletableFuture<PermissionResult> future = PermissionUtil.getInstance().setOptionValue(this.subject, option.getPermission(), "undefined", contexts);
future.thenAccept(r -> {
if (r.successful()) {
Sponge.getScheduler().createSyncExecutor(GDBootstrap.getInstance()).execute(() -> {
showOptionPermissions(src, claim, displayType);
});
}
});
};
}
private Component getOptionText(Option option, Set<Context> 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> T getMenuTypeValue(TypeToken<T> 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<CommandSource> createClaimOptionConsumer(GDPermissionUser src, GDClaim claim, MenuType optionType) {
return consumer -> {
showOptionPermissions(src, claim, optionType);
};
}
private boolean containsCustomContext(Option option, Set<Context> 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;
}
}