me.ryanhamshire.griefprevention.command.CommandHelper.java Source code

Java tutorial

Introduction

Here is the source code for me.ryanhamshire.griefprevention.command.CommandHelper.java

Source

/*
 * This file is part of GriefPrevention, licensed under the MIT License (MIT).
 *
 * Copyright (c) Ryan Hamshire
 * 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 me.ryanhamshire.griefprevention.command;

import com.flowpowered.math.vector.Vector3d;
import com.flowpowered.math.vector.Vector3i;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.gson.Gson;
import me.ryanhamshire.griefprevention.GPFlags;
import me.ryanhamshire.griefprevention.GPPlayerData;
import me.ryanhamshire.griefprevention.GriefPreventionPlugin;
import me.ryanhamshire.griefprevention.api.claim.Claim;
import me.ryanhamshire.griefprevention.api.claim.ClaimContexts;
import me.ryanhamshire.griefprevention.api.claim.ClaimFlag;
import me.ryanhamshire.griefprevention.api.claim.ClaimResult;
import me.ryanhamshire.griefprevention.api.claim.FlagResult;
import me.ryanhamshire.griefprevention.api.claim.FlagResultType;
import me.ryanhamshire.griefprevention.api.claim.TrustType;
import me.ryanhamshire.griefprevention.api.economy.BankTransactionType;
import me.ryanhamshire.griefprevention.claim.GPClaim;
import me.ryanhamshire.griefprevention.claim.GPFlagResult;
import me.ryanhamshire.griefprevention.command.ClaimFlagBase.FlagType;
import me.ryanhamshire.griefprevention.configuration.GriefPreventionConfig;
import me.ryanhamshire.griefprevention.configuration.MessageStorage;
import me.ryanhamshire.griefprevention.economy.GPBankTransaction;
import me.ryanhamshire.griefprevention.event.GPGroupTrustClaimEvent;
import me.ryanhamshire.griefprevention.event.GPUserTrustClaimEvent;
import me.ryanhamshire.griefprevention.permission.GPOptionHandler;
import me.ryanhamshire.griefprevention.permission.GPOptions;
import me.ryanhamshire.griefprevention.permission.GPPermissions;
import me.ryanhamshire.griefprevention.util.PermissionUtils;
import me.ryanhamshire.griefprevention.util.TaskUtils;
import me.ryanhamshire.griefprevention.visual.Visualization;
import org.apache.commons.lang3.StringUtils;
import org.spongepowered.api.Sponge;
import org.spongepowered.api.block.BlockState;
import org.spongepowered.api.block.BlockType;
import org.spongepowered.api.command.CommandException;
import org.spongepowered.api.command.CommandMapping;
import org.spongepowered.api.command.CommandSource;
import org.spongepowered.api.command.args.CommandContext;
import org.spongepowered.api.data.property.entity.EyeLocationProperty;
import org.spongepowered.api.entity.EntityType;
import org.spongepowered.api.entity.living.player.Player;
import org.spongepowered.api.entity.living.player.User;
import org.spongepowered.api.event.CauseStackManager;
import org.spongepowered.api.item.ItemType;
import org.spongepowered.api.plugin.PluginContainer;
import org.spongepowered.api.service.context.Context;
import org.spongepowered.api.service.economy.Currency;
import org.spongepowered.api.service.economy.EconomyService;
import org.spongepowered.api.service.economy.account.Account;
import org.spongepowered.api.service.economy.account.UniqueAccount;
import org.spongepowered.api.service.economy.transaction.ResultType;
import org.spongepowered.api.service.economy.transaction.TransactionResult;
import org.spongepowered.api.service.pagination.PaginationList;
import org.spongepowered.api.service.pagination.PaginationService;
import org.spongepowered.api.service.permission.Subject;
import org.spongepowered.api.service.user.UserStorageService;
import org.spongepowered.api.text.Text;
import org.spongepowered.api.text.action.TextActions;
import org.spongepowered.api.text.format.TextColor;
import org.spongepowered.api.text.format.TextColors;
import org.spongepowered.api.text.format.TextStyles;
import org.spongepowered.api.util.Tristate;
import org.spongepowered.api.world.DimensionTypes;
import org.spongepowered.api.world.Location;
import org.spongepowered.api.world.World;

import java.math.BigDecimal;
import java.time.Duration;
import java.time.Instant;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.function.Consumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class CommandHelper {

    public static Player checkPlayer(CommandSource source) throws CommandException {
        if (source instanceof Player) {
            return ((Player) source);
        } else {
            throw new CommandException(Text.of("You must be a player to run this command!"));
        }
    }

    // helper method to resolve a player name from the player's UUID
    public static String lookupPlayerName(UUID playerID) {
        // parameter validation
        if (playerID == null) {
            return "somebody";
        }

        // check the cache
        Optional<User> player = Sponge.getGame().getServiceManager().provide(UserStorageService.class).get()
                .get(playerID);
        if (player.isPresent()) {
            return player.get().getName();
        } else {
            try {
                return Sponge.getServer().getGameProfileManager().get(playerID).get().getName().get();
            } catch (Exception e) {
                return "someone";
            }
        }
    }

    public static boolean validateFlagTarget(ClaimFlag flag, String target) {
        switch (flag) {
        case BLOCK_BREAK:
        case BLOCK_PLACE:
        case ENTITY_COLLIDE_BLOCK:
            if (validateBlockTarget(target) || validateItemTarget(target)) {
                return true;
            }

            return false;
        case ENTER_CLAIM:
        case EXIT_CLAIM:
        case ENTITY_RIDING:
        case ENTITY_DAMAGE:
        case PORTAL_USE:
            if (validateEntityTarget(target) || validateBlockTarget(target) || validateItemTarget(target)) {
                return true;
            }

            return false;
        case INTERACT_INVENTORY:
        case LIQUID_FLOW:
            return validateBlockTarget(target);
        case INTERACT_BLOCK_PRIMARY:
        case INTERACT_BLOCK_SECONDARY:
            return validateBlockTarget(target);
        case ENTITY_CHUNK_SPAWN:
        case ENTITY_SPAWN:
        case INTERACT_ENTITY_PRIMARY:
        case INTERACT_ENTITY_SECONDARY:
            return validateEntityTarget(target);
        case ITEM_DROP:
        case ITEM_PICKUP:
        case ITEM_SPAWN:
        case ITEM_USE:
            return validateItemTarget(target);
        default:
            return true;
        }
    }

    private static boolean validateEntityTarget(String target) {
        Optional<EntityType> entityType = Sponge.getRegistry().getType(EntityType.class, target);
        if (entityType.isPresent()) {
            return true;
        }

        return false;
    }

    private static boolean validateItemTarget(String target) {
        Optional<ItemType> itemType = Sponge.getRegistry().getType(ItemType.class, target);
        if (itemType.isPresent()) {
            return true;
        }
        // target could be an item block, so validate blockstate
        Optional<BlockState> blockState = Sponge.getRegistry().getType(BlockState.class, target);
        if (blockState.isPresent()) {
            return true;
        }

        return false;
    }

    private static boolean validateBlockTarget(String target) {
        Optional<BlockType> blockType = Sponge.getRegistry().getType(BlockType.class, target);
        if (blockType.isPresent()) {
            return true;
        }

        Optional<BlockState> blockState = Sponge.getRegistry().getType(BlockState.class, target);
        if (blockState.isPresent()) {
            return true;
        }
        return false;
    }

    public static Context validateCustomContext(CommandSource src, GPClaim claim, String context) {
        if (context == null) {
            return null;
        }
        if (context.equalsIgnoreCase("default") || context.equalsIgnoreCase("defaults")) {
            return claim.getDefaultContext();
        } else if (context.equalsIgnoreCase("override") || context.equalsIgnoreCase("overrides")
                || context.equalsIgnoreCase("force") || context.equalsIgnoreCase("forced")) {
            return claim.getOverrideContext();
        } else if (context.equalsIgnoreCase("ban")) {
            return ClaimContexts.WILDERNESS_OVERRIDE_CONTEXT;
        } else {
            final Text message = GriefPreventionPlugin.instance.messageData.claimContextNotFound
                    .apply(ImmutableMap.of("context", context)).build();
            GriefPreventionPlugin.sendMessage(src, message);
            return null;
        }
    }

    public static FlagResult addFlagPermission(CommandSource src, Subject subject, String subjectName,
            GPClaim claim, ClaimFlag claimFlag, String source, String target, Tristate value, Context context,
            Text reason) {
        if (src instanceof Player) {
            Text denyReason = claim.allowEdit((Player) src);
            if (denyReason != null) {
                GriefPreventionPlugin.sendMessage(src, denyReason);
                return new GPFlagResult(FlagResultType.NO_PERMISSION);
            }
        }

        final String baseFlag = claimFlag.toString().toLowerCase();
        String flagPermission = GPPermissions.FLAG_BASE + "." + baseFlag;
        // special handling for commands
        if (baseFlag.equals(ClaimFlag.COMMAND_EXECUTE.name())
                || baseFlag.equals(ClaimFlag.COMMAND_EXECUTE_PVP.name())) {
            target = handleCommandFlag(src, target);
            if (target == null) {
                // failed
                return new GPFlagResult(FlagResultType.TARGET_NOT_VALID);
            }
            flagPermission = GPPermissions.FLAG_BASE + "." + baseFlag + "." + target;
        } else {
            if (!target.equalsIgnoreCase("any")) {
                if (!target.contains(":")) {
                    // assume vanilla
                    target = "minecraft:" + target;
                }

                String[] parts = target.split(":");
                if (parts[1].equalsIgnoreCase("any")) {
                    target = baseFlag + "." + parts[0];
                } else {
                    // check for meta
                    parts = target.split("\\.");
                    String targetFlag = parts[0];
                    if (parts.length > 1) {
                        try {
                            Integer.parseInt(parts[1]);
                        } catch (NumberFormatException e) {
                            final Text message = GriefPreventionPlugin.instance.messageData.permissionClaimManage
                                    .apply(ImmutableMap.of("meta", parts[1], "flag", baseFlag)).build();
                            GriefPreventionPlugin.sendMessage(src, message);
                            return new GPFlagResult(FlagResultType.TARGET_NOT_VALID);
                        }
                    }
                    String entitySpawnFlag = GPFlags.getEntitySpawnFlag(claimFlag, targetFlag);
                    if (entitySpawnFlag == null && !CommandHelper.validateFlagTarget(claimFlag, targetFlag)) {
                        //TODO
                        /*final Text message = GriefPreventionPlugin.instance.messageData.permissionClaimManage
                            .apply(ImmutableMap.of(
                            "target", targetFlag,
                            "flag", baseFlag)).build();*/
                        GriefPreventionPlugin.sendMessage(src,
                                Text.of(TextColors.RED, "Invalid flag " + targetFlag));
                        return new GPFlagResult(FlagResultType.TARGET_NOT_VALID);
                    }

                    if (entitySpawnFlag != null) {
                        target = entitySpawnFlag;
                    } else {
                        target = baseFlag + "." + target.replace(":", ".");//.replace("[", ".[");
                    }
                }

                flagPermission = GPPermissions.FLAG_BASE + "." + target;
            } else {
                if (source != null) {
                    flagPermission += ".minecraft";
                } else {
                    target = "";
                }
            }
        }

        return applyFlagPermission(src, subject, subjectName, claim, flagPermission, source, target, value, context,
                null, reason, false);
    }

    public static FlagResult applyFlagPermission(CommandSource src, Subject subject, String subjectName,
            GPClaim claim, String flagPermission, String source, String target, Tristate value, Context context,
            FlagType flagType) {
        return applyFlagPermission(src, subject, subjectName, claim, flagPermission, source, target, value, context,
                flagType, null, false);
    }

    public static FlagResult applyFlagPermission(CommandSource src, Subject subject, String subjectName,
            GPClaim claim, String flagPermission, String source, String target, Tristate value, Context context,
            FlagType flagType, Text reason, boolean clicked) {
        // Remove "any" in source
        if (source != null) {
            if (source.equalsIgnoreCase("any")) {
                source = "";
            } else {
                String[] parts = source.split(":");
                if (parts[1].equalsIgnoreCase("any")) {
                    source = parts[0];
                }
            }
        }

        String basePermission = flagPermission.replace(GPPermissions.FLAG_BASE + ".", "");
        int endIndex = basePermission.indexOf(".");
        if (endIndex != -1) {
            basePermission = basePermission.substring(0, endIndex);
        }

        // Check if player can manage flag
        if (src instanceof Player) {
            Player player = (Player) src;
            GPPlayerData playerData = GriefPreventionPlugin.instance.dataStore
                    .getOrCreatePlayerData(player.getWorld(), player.getUniqueId());
            Tristate result = Tristate.UNDEFINED;
            if (playerData.canManageAdminClaims) {
                result = Tristate
                        .fromBoolean(src.hasPermission(GPPermissions.ADMIN_CLAIM_FLAGS + "." + basePermission));
            } else if (GriefPreventionPlugin.getActiveConfig(player.getWorld().getProperties()).getConfig().flags
                    .getUserClaimFlags().contains(basePermission)) {
                result = Tristate
                        .fromBoolean(src.hasPermission(GPPermissions.USER_CLAIM_FLAGS + "." + basePermission));
            }

            if (result != Tristate.TRUE) {
                GriefPreventionPlugin.sendMessage(src,
                        GriefPreventionPlugin.instance.messageData.permissionFlagUse.toText());
                return new GPFlagResult(FlagResultType.NO_PERMISSION);
            }
        }

        Set<Context> contexts = new HashSet<>();
        if (context != claim.getContext()) {
            // validate perms
            if (context == ClaimContexts.ADMIN_DEFAULT_CONTEXT || context == ClaimContexts.BASIC_DEFAULT_CONTEXT
                    || context == ClaimContexts.TOWN_DEFAULT_CONTEXT
                    || context == ClaimContexts.WILDERNESS_DEFAULT_CONTEXT) {
                if (!src.hasPermission(GPPermissions.MANAGE_FLAG_DEFAULTS)) {
                    GriefPreventionPlugin.sendMessage(src,
                            GriefPreventionPlugin.instance.messageData.permissionFlagDefaults.toText());
                    return new GPFlagResult(FlagResultType.NO_PERMISSION);
                }
                if (flagType == null) {
                    flagType = FlagType.DEFAULT;
                }
            } else if (context == ClaimContexts.ADMIN_OVERRIDE_CONTEXT
                    || context == ClaimContexts.TOWN_OVERRIDE_CONTEXT
                    || context == ClaimContexts.BASIC_OVERRIDE_CONTEXT
                    || context == ClaimContexts.WILDERNESS_OVERRIDE_CONTEXT) {
                if (!src.hasPermission(GPPermissions.MANAGE_FLAG_OVERRIDES)) {
                    GriefPreventionPlugin.sendMessage(src,
                            GriefPreventionPlugin.instance.messageData.permissionFlagOverrides.toText());
                    return new GPFlagResult(FlagResultType.NO_PERMISSION);
                }
                if (flagType == null) {
                    flagType = FlagType.OVERRIDE;
                }
            }
            contexts.add(context);
        } else {
            if (flagType == null) {
                flagType = FlagType.CLAIM;
            }
        }

        Text flagTypeText = Text.of();
        if (flagType == FlagType.OVERRIDE) {
            flagTypeText = Text.of(TextColors.RED, "OVERRIDE");
        } else if (flagType == FlagType.DEFAULT) {
            flagTypeText = Text.of(TextColors.LIGHT_PURPLE, "DEFAULT");
        } else if (flagType == FlagType.CLAIM) {
            flagTypeText = Text.of(TextColors.GOLD, "CLAIM");
        }

        if (source != null) {
            Pattern p = Pattern.compile("\\.[\\d+]*$");
            Matcher m = p.matcher(flagPermission);
            String targetMeta = "";
            if (m.find()) {
                targetMeta = m.group(0);
                flagPermission = flagPermission.replace(targetMeta, "");
            }
            flagPermission += ".source." + source + targetMeta;
            flagPermission = StringUtils.replace(flagPermission, ":", ".");
        }

        if (context == ClaimContexts.WILDERNESS_OVERRIDE_CONTEXT) {
            if (reason != null && !reason.isEmpty()) {
                GriefPreventionPlugin.getGlobalConfig().getConfig().bans.addBan(flagPermission, reason);
                GriefPreventionPlugin.getGlobalConfig().save();
            }
        }

        if (subject == GriefPreventionPlugin.GLOBAL_SUBJECT) {
            if (context == claim.getContext() || !ClaimContexts.CONTEXT_LIST.contains(context)) {
                contexts.add(claim.getContext());
            } else {
                // wilderness overrides affect all worlds
                if (context != ClaimContexts.WILDERNESS_OVERRIDE_CONTEXT) {
                    contexts.add(claim.world.getContext());
                }
            }

            GriefPreventionPlugin.GLOBAL_SUBJECT.getSubjectData().setPermission(contexts, flagPermission, value);
            if (!clicked) {
                src.sendMessage(Text.of(
                        Text.builder()
                                .append(Text.of(TextColors.WHITE, "\n[", TextColors.AQUA, "Return to flags",
                                        TextColors.WHITE, "]\n"))
                                .onClick(TextActions.executeCallback(createCommandConsumer(src, "claimflag", "")))
                                .build(),
                        TextColors.GREEN, "Set ", flagTypeText, " permission ", TextColors.AQUA,
                        flagPermission.replace(GPPermissions.FLAG_BASE + ".", ""), TextColors.GREEN, "\n to ",
                        TextColors.LIGHT_PURPLE,
                        getClickableText(src, GriefPreventionPlugin.GLOBAL_SUBJECT, subjectName, contexts,
                                flagPermission, value, flagType),
                        TextColors.GREEN, " on ", TextColors.GOLD, "ALL"));
            }
        } else {
            if (context == claim.getContext() || !ClaimContexts.CONTEXT_LIST.contains(context)) {
                contexts.add(claim.getContext());
            } else {
                // wilderness overrides affect all worlds
                if (context != ClaimContexts.WILDERNESS_OVERRIDE_CONTEXT) {
                    contexts.add(claim.world.getContext());
                }
            }

            subject.getSubjectData().setPermission(contexts, flagPermission, value);
            if (!clicked) {
                src.sendMessage(Text.of(
                        Text.builder()
                                .append(Text.of(TextColors.WHITE, "\n[", TextColors.AQUA, "Return to flags",
                                        TextColors.WHITE, "]\n"))
                                .onClick(TextActions.executeCallback(createCommandConsumer(src,
                                        subject instanceof User ? "claimflagplayer" : "claimflaggroup",
                                        subjectName)))
                                .build(),
                        TextColors.GREEN, "Set ", flagTypeText, " permission ", TextColors.AQUA,
                        flagPermission.replace(GPPermissions.FLAG_BASE + ".", ""), TextColors.GREEN, "\n to ",
                        TextColors.LIGHT_PURPLE,
                        getClickableText(src, subject, subjectName, contexts, flagPermission, value, flagType),
                        TextColors.GREEN, " on ", TextColors.GOLD, subjectName));
            }
        }

        return new GPFlagResult(FlagResultType.SUCCESS);
    }

    public static TextColor getFlagTypeColor(FlagType type) {
        TextColor color = TextColors.LIGHT_PURPLE;
        if (type == FlagType.CLAIM) {
            color = TextColors.GOLD;
        } else if (type == FlagType.OVERRIDE) {
            color = TextColors.RED;
        }

        return color;
    }

    public static Consumer<CommandSource> createFlagConsumer(CommandSource src, Subject subject, String subjectName,
            Set<Context> contexts, String flagPermission, Tristate flagValue, FlagType flagType) {
        return consumer -> {
            Tristate newValue = Tristate.UNDEFINED;
            if (flagValue == Tristate.TRUE) {
                newValue = Tristate.FALSE;
            } else if (flagValue == Tristate.UNDEFINED) {
                newValue = Tristate.TRUE;
            }

            Text flagTypeText = Text.of();
            if (flagType == FlagType.OVERRIDE) {
                flagTypeText = Text.of(TextColors.RED, "OVERRIDE");
            } else if (flagType == FlagType.DEFAULT) {
                flagTypeText = Text.of(TextColors.LIGHT_PURPLE, "DEFAULT");
            } else if (flagType == FlagType.CLAIM) {
                flagTypeText = Text.of(TextColors.GOLD, "CLAIM");
            }
            String target = flagPermission.replace(GPPermissions.FLAG_BASE + ".", "");
            Set<Context> newContexts = new HashSet<>(contexts);
            subject.getSubjectData().setPermission(newContexts, flagPermission, newValue);
            src.sendMessage(
                    Text.of(TextColors.GREEN, "Set ", flagTypeText, " permission ", TextColors.AQUA, target,
                            TextColors.GREEN, "\n to ", TextColors.LIGHT_PURPLE, getClickableText(src, subject,
                                    subjectName, newContexts, flagPermission, newValue, flagType),
                            TextColors.GREEN, " for ", TextColors.GOLD, subjectName));
        };
    }

    public static Consumer<CommandSource> createCommandConsumer(CommandSource src, String command,
            String arguments) {
        return createCommandConsumer(src, command, arguments, null);
    }

    public static Consumer<CommandSource> createCommandConsumer(CommandSource src, String command, String arguments,
            Consumer<CommandSource> postConsumerTask) {
        return consumer -> {
            try {
                Sponge.getCommandManager().get(command).get().getCallable().process(src, arguments);
            } catch (CommandException e) {
                src.sendMessage(e.getText());
            }
            if (postConsumerTask != null) {
                postConsumerTask.accept(src);
            }
        };
    }

    public static void executeCommand(CommandSource src, String command, String arguments) {
        try {
            Sponge.getCommandManager().get(command).get().getCallable().process(src, arguments);
        } catch (CommandException e) {
            src.sendMessage(e.getText());
        }
    }

    public static void showClaims(CommandSource src, List<Claim> claims) {
        if (claims.isEmpty()) {
            // do nothing
            return;
        }
        showClaims(src, claims, 0, false);
    }

    public static void showClaims(CommandSource src, List<Claim> claims, int height, boolean visualizeClaims) {
        final String worldName = src instanceof Player ? ((Player) src).getWorld().getName()
                : Sponge.getServer().getDefaultWorldName();
        final boolean canListOthers = src.hasPermission(GPPermissions.LIST_OTHER_CLAIMS);
        List<Text> claimsTextList = generateClaimTextList(new ArrayList<Text>(), claims, worldName, null, src,
                createShowClaimsConsumer(src, claims, height, visualizeClaims), canListOthers, false);

        if (visualizeClaims && src instanceof Player) {
            Player player = (Player) src;
            final GPPlayerData playerData = GriefPreventionPlugin.instance.dataStore
                    .getOrCreatePlayerData(player.getWorld(), player.getUniqueId());
            if (claims.size() > 1) {
                if (height != 0) {
                    height = playerData.lastValidInspectLocation != null
                            ? playerData.lastValidInspectLocation.getBlockY()
                            : player.getProperty(EyeLocationProperty.class).get().getValue().getFloorY();
                }
                Visualization visualization = Visualization.fromClaims(claims,
                        playerData.optionClaimCreateMode == 1 ? height
                                : player.getProperty(EyeLocationProperty.class).get().getValue().getFloorY(),
                        player.getLocation(), playerData, null);
                visualization.apply(player);
            } else {
                GPClaim gpClaim = (GPClaim) claims.get(0);
                gpClaim.getVisualizer().createClaimBlockVisuals(height, player.getLocation(), playerData);
                gpClaim.getVisualizer().apply(player);
            }
        }

        PaginationService paginationService = Sponge.getServiceManager().provide(PaginationService.class).get();
        PaginationList.Builder paginationBuilder = paginationService.builder()
                .title(Text.of(TextColors.RED, "Claim list")).padding(Text.of(TextStyles.STRIKETHROUGH, "-"))
                .contents(claimsTextList);
        paginationBuilder.sendTo(src);
    }

    private static Consumer<CommandSource> createShowClaimsConsumer(CommandSource src, List<Claim> claims,
            int height, boolean visualizeClaims) {
        return consumer -> {
            showClaims(src, claims, height, visualizeClaims);
        };
    }

    public static List<Text> generateClaimTextList(List<Text> claimsTextList, List<Claim> claimList,
            String worldName, User user, CommandSource src, Consumer<CommandSource> returnCommand,
            boolean canListOthers, boolean listChildren) {
        final User sourceUser = src instanceof User ? (User) src : null;
        if (claimList.size() > 0) {
            for (Claim playerClaim : claimList) {
                GPClaim claim = (GPClaim) playerClaim;
                if (!listChildren && claim.isSubdivision()) {
                    continue;
                }
                // Only list claims trusted
                if (sourceUser != null && !claim.isUserTrusted(sourceUser, TrustType.ACCESSOR) && !canListOthers) {
                    continue;
                }

                double teleportHeight = claim.getOwnerPlayerData() == null ? 65.0D
                        : (claim.getOwnerPlayerData().getMinClaimLevel() > 65.0D
                                ? claim.getOwnerPlayerData().getMinClaimLevel()
                                : 65);
                Location<World> a = claim.lesserBoundaryCorner;
                Vector3d center = a.getPosition().add(claim.greaterBoundaryCorner.getPosition()).div(2);
                if (teleportHeight == 65 && claim.getWorld().getDimension().getType() == DimensionTypes.OVERWORLD) {
                    teleportHeight = claim.getWorld().getHighestYAt((int) center.getX(), (int) center.getZ());
                }
                Location<World> southWest = claim.lesserBoundaryCorner
                        .setPosition(new Vector3d(center.getX(), teleportHeight, center.getZ()));

                Text claimName = claim.getData().getName().orElse(Text.of());
                Text teleportName = claim.getData().getName().orElse(claim.getFriendlyNameType());
                Text ownerLine = Text.of(TextColors.YELLOW, "Owner", TextColors.WHITE, " : ", TextColors.GOLD,
                        claim.getOwnerName(), "\n");
                Text claimTypeInfo = Text.of(TextColors.YELLOW, "Type", TextColors.WHITE, " : ",
                        claim.getFriendlyNameType(), " ", TextColors.GRAY, claim.isCuboid() ? "3D " : "2D ",
                        TextColors.WHITE, " (Area: ", TextColors.GRAY, claim.getClaimBlocks(), " blocks",
                        TextColors.WHITE, ")\n");
                Text clickInfo = Text.of("Click to check more info.");
                Text basicInfo = Text.builder().append(ownerLine, claimTypeInfo, clickInfo).build();

                Text claimInfoCommandClick = Text.builder().append(claim.getFriendlyNameType())
                        .onClick(TextActions.executeCallback(CommandHelper.createCommandConsumer(src, "claiminfo",
                                claim.id.toString(), createReturnClaimListConsumer(src, returnCommand))))
                        .onHover(TextActions.showText(basicInfo)).build();

                Text claimCoordsTPClick = Text.builder()
                        .append(Text.of(TextColors.WHITE, "[", TextColors.LIGHT_PURPLE, "TP", TextColors.WHITE,
                                "]"))
                        .onClick(TextActions
                                .executeCallback(CommandHelper.createTeleportConsumer(src, southWest, claim)))
                        .onHover(TextActions.showText(Text.of("Click here to teleport to ", teleportName, " ",
                                southWest.getBlockPosition(), " in ", TextColors.LIGHT_PURPLE,
                                claim.getWorld().getProperties().getWorldName(), TextColors.WHITE, ".")))
                        .build();

                Text claimSpawn = null;
                if (claim.getData().getSpawnPos().isPresent()) {
                    Vector3i spawnPos = claim.getData().getSpawnPos().get();
                    Location<World> spawnLoc = new Location<>(claim.getWorld(), spawnPos);
                    claimSpawn = Text.builder()
                            .append(Text.of(TextColors.WHITE, "[", TextColors.LIGHT_PURPLE, "TP", TextColors.WHITE,
                                    "]"))
                            .onClick(TextActions
                                    .executeCallback(CommandHelper.createTeleportConsumer(src, spawnLoc, claim)))
                            .onHover(TextActions.showText(Text.of("Click here to teleport to ", teleportName,
                                    "'s spawn @ ", spawnPos, " in ", TextColors.LIGHT_PURPLE,
                                    claim.getWorld().getProperties().getWorldName(), TextColors.WHITE, ".")))
                            .build();
                } else {
                    claimSpawn = claimCoordsTPClick;
                }

                List<Text> childrenTextList = new ArrayList<>();
                if (!listChildren) {
                    childrenTextList = generateClaimTextList(new ArrayList<Text>(), claim.getChildren(true),
                            worldName, user, src, returnCommand, canListOthers, true);
                }
                final Player player = src instanceof Player ? (Player) src : null;
                Text buyClaim = Text.of();
                if (player != null && claim.getEconomyData().isForSale()
                        && claim.getEconomyData().getSalePrice() > -1) {
                    Text buyInfo = Text.of(TextColors.AQUA, "Price ", TextColors.WHITE, ":", TextColors.GOLD, " ",
                            claim.getEconomyData().getSalePrice(), "\nClick here to purchase claim.");
                    buyClaim = Text.builder()
                            .append(claim.getEconomyData().isForSale()
                                    ? Text.of(TextColors.WHITE, "[", TextColors.GREEN, "Buy", TextColors.WHITE, "]")
                                    : Text.of())
                            .onClick(TextActions.executeCallback(buyClaimConsumerConfirmation(src, claim)))
                            .onHover(TextActions
                                    .showText(Text.of(player.getUniqueId().equals(claim.getOwnerUniqueId())
                                            ? "You already own this claim."
                                            : buyInfo)))
                            .build();
                }
                if (!childrenTextList.isEmpty()) {
                    Text children = Text.builder()
                            .append(Text.of(TextColors.WHITE, "[", TextColors.AQUA, "children", TextColors.WHITE,
                                    "]"))
                            .onClick(TextActions
                                    .executeCallback(showChildrenList(childrenTextList, src, returnCommand, claim)))
                            .onHover(TextActions.showText(Text.of("Click here to view child claim list."))).build();
                    claimsTextList.add(Text.builder()
                            .append(Text.of(claimSpawn, " ", claimInfoCommandClick, TextColors.WHITE, " : ",
                                    TextColors.GOLD, claim.getOwnerName(), " ", children, " ",
                                    claimName.isEmpty() ? "" : claimName, " ", buyClaim))
                            .build());
                } else {
                    claimsTextList.add(Text.builder()
                            .append(Text.of(claimSpawn, " ", claimInfoCommandClick, TextColors.WHITE, " : ",
                                    TextColors.GOLD, claim.getOwnerName(), " ",
                                    claimName.isEmpty() ? "" : claimName, " ", buyClaim))
                            .build());
                }
            }
            if (claimsTextList.size() == 0) {
                claimsTextList.add(Text.of(TextColors.RED, "No claims found in world."));
            }
        }
        return claimsTextList;
    }

    private static Consumer<CommandSource> buyClaimConsumerConfirmation(CommandSource src, Claim claim) {
        return confirm -> {
            final Player player = (Player) src;
            if (player.getUniqueId().equals(claim.getOwnerUniqueId())) {
                return;
            }
            Account playerAccount = GriefPreventionPlugin.instance.economyService.get()
                    .getOrCreateAccount(player.getUniqueId()).orElse(null);
            if (playerAccount == null) {
                Map<String, ?> params = ImmutableMap.of("user", player.getName());
                GriefPreventionPlugin.sendMessage(player, MessageStorage.ECONOMY_USER_NOT_FOUND,
                        GriefPreventionPlugin.instance.messageData.economyUserNotFound, params);
                return;
            }

            final double balance = playerAccount
                    .getBalance(GriefPreventionPlugin.instance.economyService.get().getDefaultCurrency())
                    .doubleValue();
            if (balance < claim.getEconomyData().getSalePrice()) {
                Map<String, ?> params = ImmutableMap.of("sale_price", claim.getEconomyData().getSalePrice(),
                        "balance", balance, "amount_needed", claim.getEconomyData().getSalePrice() - balance);
                GriefPreventionPlugin.sendMessage(player, "economy-claim-buy-not-enough-funds",
                        GriefPreventionPlugin.instance.messageData.economyClaimBuyNotEnoughFunds, params);
                return;
            }
            final Text message = GriefPreventionPlugin.instance.messageData.economyClaimBuyConfirmation
                    .apply(ImmutableMap.of("sale_price", claim.getEconomyData().getSalePrice())).build();
            GriefPreventionPlugin.sendMessage(src, message);
            final Text buyConfirmationText = Text.builder()
                    .append(Text.of(TextColors.WHITE, "\n", TextColors.WHITE, "[", TextColors.GREEN, "Confirm",
                            TextColors.WHITE, "]\n"))
                    .onClick(TextActions.executeCallback(createBuyConsumerConfirmed(src, claim))).build();
            GriefPreventionPlugin.sendMessage(player, buyConfirmationText);
        };
    }

    private static Consumer<CommandSource> createBuyConsumerConfirmed(CommandSource src, Claim claim) {
        return confirm -> {
            final Player player = (Player) src;
            final Player ownerPlayer = Sponge.getServer().getPlayer(claim.getOwnerUniqueId()).orElse(null);
            final Account ownerAccount = GriefPreventionPlugin.instance.economyService.get()
                    .getOrCreateAccount(claim.getOwnerUniqueId()).orElse(null);
            if (ownerAccount == null) {
                src.sendMessage(
                        Text.of(TextColors.RED, "Buy cancelled! Could not locate an economy account for owner."));
                return;
            }

            final ClaimResult result = claim.transferOwner(player.getUniqueId());
            if (!result.successful()) {
                final Text defaultMessage = Text.of(TextColors.RED,
                        "Buy cancelled! Could not transfer owner. Result was ", result.getResultType().name());
                src.sendMessage(result.getMessage().orElse(defaultMessage));
                return;
            }

            try (CauseStackManager.StackFrame frame = Sponge.getCauseStackManager().pushCauseFrame()) {
                final Currency defaultCurrency = GriefPreventionPlugin.instance.economyService.get()
                        .getDefaultCurrency();
                final double salePrice = claim.getEconomyData().getSalePrice();
                Sponge.getCauseStackManager().pushCause(src);
                final TransactionResult ownerResult = ownerAccount.deposit(defaultCurrency,
                        BigDecimal.valueOf(salePrice), Sponge.getCauseStackManager().getCurrentCause());
                Account playerAccount = GriefPreventionPlugin.instance.economyService.get()
                        .getOrCreateAccount(player.getUniqueId()).orElse(null);
                final TransactionResult transactionResult = playerAccount.withdraw(defaultCurrency,
                        BigDecimal.valueOf(salePrice), Sponge.getCauseStackManager().getCurrentCause());
                final Text message = GriefPreventionPlugin.instance.messageData.economyClaimBuyConfirmed
                        .apply(ImmutableMap.of("sale_price", salePrice)).build();
                final Text saleMessage = GriefPreventionPlugin.instance.messageData.economyClaimSold.apply(
                        ImmutableMap.of("amount", salePrice, "balance", ownerAccount.getBalance(defaultCurrency)))
                        .build();
                if (ownerPlayer != null) {
                    ownerPlayer.sendMessage(saleMessage);
                }
                claim.getEconomyData().setForSale(false);
                claim.getEconomyData().setSalePrice(0);
                claim.getData().save();
                GriefPreventionPlugin.sendMessage(src, message);
            }
        };
    }

    public static Consumer<CommandSource> showChildrenList(List<Text> childrenTextList, CommandSource src,
            Consumer<CommandSource> returnCommand, GPClaim parent) {
        return consumer -> {
            Text claimListReturnCommand = Text.builder().append(Text.of(TextColors.WHITE, "\n[", TextColors.AQUA,
                    "Return to claimslist", TextColors.WHITE, "]\n"))
                    .onClick(TextActions.executeCallback(returnCommand)).build();

            List<Text> textList = new ArrayList<>();
            textList.add(claimListReturnCommand);
            textList.addAll(childrenTextList);
            PaginationService paginationService = Sponge.getServiceManager().provide(PaginationService.class).get();
            PaginationList.Builder paginationBuilder = paginationService.builder()
                    .title(Text.of(parent.getName().orElse(parent.getFriendlyNameType()), " Child Claims"))
                    .padding(Text.of(TextStyles.STRIKETHROUGH, "-")).contents(textList);
            paginationBuilder.sendTo(src);
        };
    }

    public static Consumer<CommandSource> createReturnClaimListConsumer(CommandSource src,
            Consumer<CommandSource> returnCommand) {
        return consumer -> {
            Text claimListReturnCommand = Text.builder().append(Text.of(TextColors.WHITE, "\n[", TextColors.AQUA,
                    "Return to claimslist", TextColors.WHITE, "]\n"))
                    .onClick(TextActions.executeCallback(returnCommand)).build();
            src.sendMessage(claimListReturnCommand);
        };
    }

    public static Consumer<CommandSource> createReturnClaimListConsumer(CommandSource src, String arguments) {
        return consumer -> {
            Text claimListReturnCommand = Text.builder()
                    .append(Text.of(TextColors.WHITE, "\n[", TextColors.AQUA, "Return to claimslist",
                            TextColors.WHITE, "]\n"))
                    .onClick(TextActions
                            .executeCallback(CommandHelper.createCommandConsumer(src, "claimslist", arguments)))
                    .build();
            src.sendMessage(claimListReturnCommand);
        };
    }

    public static Consumer<CommandSource> createFlagConsumer(CommandSource src, Subject subject, String subjectName,
            Set<Context> contexts, GPClaim claim, String flagPermission, Tristate flagValue, String source) {
        return consumer -> {
            String target = flagPermission.replace(GPPermissions.FLAG_BASE + ".", "");
            if (target.isEmpty()) {
                target = "any";
            }
            Tristate newValue = Tristate.UNDEFINED;
            if (flagValue == Tristate.TRUE) {
                newValue = Tristate.FALSE;
            } else if (flagValue == Tristate.UNDEFINED) {
                newValue = Tristate.TRUE;
            }

            CommandHelper.applyFlagPermission(src, subject, subjectName, claim, flagPermission, source, target,
                    newValue, null, FlagType.GROUP);
        };
    }

    public static Text getClickableText(CommandSource src, Subject subject, String subjectName,
            Set<Context> contexts, String flagPermission, Tristate flagValue, FlagType type) {
        String onClickText = "Click here to toggle " + type.name().toLowerCase() + " value.";
        Text.Builder textBuilder = Text.builder().append(Text.of(flagValue.toString().toLowerCase()))
                .onHover(TextActions.showText(Text.of(onClickText, "\n", getFlagTypeHoverText(type))))
                .onClick(TextActions.executeCallback(
                        createFlagConsumer(src, subject, subjectName, contexts, flagPermission, flagValue, type)));
        return textBuilder.build();
    }

    public static Text getClickableText(CommandSource src, Subject subject, String subjectName,
            Set<Context> contexts, GPClaim claim, String flagPermission, Tristate flagValue, String source,
            FlagType type) {
        Text onClickText = Text.of("Click here to toggle flag value.");
        boolean hasPermission = true;
        if (type == FlagType.INHERIT) {
            onClickText = Text.of("This flag is inherited from parent claim ",
                    claim.getName().orElse(claim.getFriendlyNameType()), " and ", TextStyles.UNDERLINE, "cannot",
                    TextStyles.RESET, " be changed.");
            hasPermission = false;
        } else if (src instanceof Player) {
            Text denyReason = claim.allowEdit((Player) src);
            if (denyReason != null) {
                onClickText = denyReason;
                hasPermission = false;
            }
        }

        Text.Builder textBuilder = Text.builder().append(Text.of(flagValue.toString().toLowerCase()))
                .onHover(TextActions.showText(Text.of(onClickText, "\n", getFlagTypeHoverText(type))));
        if (hasPermission) {
            textBuilder.onClick(TextActions.executeCallback(createFlagConsumer(src, subject, subjectName, contexts,
                    claim, flagPermission, flagValue, source)));
        }
        return textBuilder.build();
    }

    public static String handleCommandFlag(CommandSource src, String target) {
        String pluginId = "minecraft";
        String args = "";
        String command = "";
        int argsIndex = target.indexOf("[");
        if (argsIndex != -1) {
            if (argsIndex == 0) {
                // invalid
                src.sendMessage(Text.of(TextColors.RED, "No valid command entered."));
                return null;
            }
            command = target.substring(0, argsIndex);
            String[] parts = command.split(":");
            if (parts.length > 1) {
                pluginId = parts[0];
                command = parts[1];
            }
            if (!validateCommandMapping(src, command, pluginId)) {
                return null;
            }
            if (!pluginId.equals("minecraft")) {
                PluginContainer pluginContainer = Sponge.getPluginManager().getPlugin(pluginId).orElse(null);
                if (pluginContainer == null) {
                    src.sendMessage(Text.of(TextColors.RED, "Could not locate a plugin with id '", TextColors.AQUA,
                            pluginId, TextColors.RED, "'."));
                    return null;
                }
            }
            args = target.substring(argsIndex, target.length());
            Pattern p = Pattern.compile("\\[([^\\]]+)\\]");
            Matcher m = p.matcher(args);
            if (!m.find()) {
                // invalid
                src.sendMessage(Text.of(TextColors.RED, "Invalid arguments '", TextColors.AQUA, args,
                        TextColors.RED, "' entered. Check syntax matches  'command[arg1:arg2:etc]'"));
                return null;
            }
            args = m.group(1);
            target = pluginId + "." + command + "." + args.replace(":", ".");
        } else {
            String[] parts = target.split(":");
            if (parts.length > 1) {
                pluginId = parts[0];
                command = parts[1];
            } else {
                command = target;
            }
            target = pluginId + "." + command;
        }

        // validate command
        if (!validateCommandMapping(src, command, pluginId)) {
            return null;
        }

        return target;
    }

    private static boolean validateCommandMapping(CommandSource src, String command, String pluginId) {
        CommandMapping commandMapping = Sponge.getCommandManager().get(command).orElse(null);
        if (commandMapping == null) {
            src.sendMessage(Text.of(TextColors.RED, "Could not locate the command '", TextColors.GREEN, command,
                    TextColors.RED, "' for mod id '", TextColors.AQUA, pluginId, TextColors.RED, "'."));
            return false;
        }
        return true;
    }

    public static void handleUserTrustCommand(Player player, TrustType trustType, User user) {
        if (user == null) {
            GriefPreventionPlugin.sendMessage(player,
                    GriefPreventionPlugin.instance.messageData.commandPlayerInvalid.toText());
            return;
        }
        if (!GriefPreventionPlugin.instance.claimsEnabledForWorld(player.getWorld().getProperties())) {
            GriefPreventionPlugin.sendMessage(player,
                    GriefPreventionPlugin.instance.messageData.claimDisabledWorld.toText());
            return;
        }

        // determine which claim the player is standing in
        GPPlayerData playerData = GriefPreventionPlugin.instance.dataStore.getOrCreatePlayerData(player.getWorld(),
                player.getUniqueId());
        GPClaim claim = GriefPreventionPlugin.instance.dataStore.getClaimAtPlayer(playerData, player.getLocation());
        if (user != null && user.getUniqueId().equals(player.getUniqueId()) && !playerData.canIgnoreClaim(claim)) {
            GriefPreventionPlugin.sendMessage(player,
                    GriefPreventionPlugin.instance.messageData.trustSelf.toText());
            return;
        }

        ArrayList<Claim> targetClaims = new ArrayList<>();
        if (claim == null) {
            GriefPreventionPlugin.sendMessage(player,
                    GriefPreventionPlugin.instance.messageData.claimNotFound.toText());
            return;
        } else if (user != null && claim.getOwnerUniqueId().equals(user.getUniqueId())) {
            GriefPreventionPlugin.sendMessage(player,
                    GriefPreventionPlugin.instance.messageData.claimOwnerAlready.toText());
            return;
        } else {
            //check permission here
            if (claim.allowGrantPermission(player) != null) {
                final Text message = GriefPreventionPlugin.instance.messageData.permissionTrust
                        .apply(ImmutableMap.of("owner", claim.getOwnerName())).build();
                GriefPreventionPlugin.sendMessage(player, message);
                return;
            }

            if (trustType == TrustType.MANAGER) {
                Text denyReason = claim.allowEdit(player);
                if (denyReason != null) {
                    GriefPreventionPlugin.sendMessage(player,
                            GriefPreventionPlugin.instance.messageData.permissionGrant.toText());
                    return;
                }
            }

            targetClaims.add(claim);
        }

        try (final CauseStackManager.StackFrame frame = Sponge.getCauseStackManager().pushCauseFrame()) {
            Sponge.getCauseStackManager().pushCause(player);
            GPUserTrustClaimEvent.Add event = new GPUserTrustClaimEvent.Add(targetClaims,
                    ImmutableList.of(user.getUniqueId()), trustType);
            Sponge.getEventManager().post(event);
            if (event.isCancelled()) {
                player.sendMessage(Text.of(TextColors.RED, event.getMessage().orElse(
                        Text.of("Could not trust user '" + user.getName() + "'. A plugin has denied it."))));
                return;
            }

            for (Claim currentClaim : targetClaims) {
                GPClaim gpClaim = (GPClaim) currentClaim;
                final List<UUID> trustList = gpClaim.getUserTrustList(trustType);
                if (trustList.contains(user.getUniqueId())) {
                    final Text message = GriefPreventionPlugin.instance.messageData.trustAlreadyHas
                            .apply(ImmutableMap.of("target", user.getName(), "type", trustType.name())).build();
                    GriefPreventionPlugin.sendMessage(player, message);
                    return;
                }

                trustList.add(user.getUniqueId());
                gpClaim.getInternalClaimData().setRequiresSave(true);
                gpClaim.getInternalClaimData().save();
            }

            final Text message = GriefPreventionPlugin.instance.messageData.trustGrant
                    .apply(ImmutableMap.of("target", user.getName(), "type", trustType.name())).build();
            GriefPreventionPlugin.sendMessage(player, message);
        }
    }

    public static void handleGroupTrustCommand(Player player, TrustType trustType, String group) {
        final Text invalidGroup = GriefPreventionPlugin.instance.messageData.commandGroupInvalid
                .apply(ImmutableMap.of("group", group)).build();
        if (group == null) {
            GriefPreventionPlugin.sendMessage(player, invalidGroup);
            return;
        }
        if (!PermissionUtils.hasGroupSubject(group)) {
            GriefPreventionPlugin.sendMessage(player, invalidGroup);
            return;
        }
        if (!GriefPreventionPlugin.instance.claimsEnabledForWorld(player.getWorld().getProperties())) {
            GriefPreventionPlugin.sendMessage(player,
                    GriefPreventionPlugin.instance.messageData.claimDisabledWorld.toText());
            return;
        }

        Subject subject = PermissionUtils.getGroupSubject(group);
        // determine which claim the player is standing in
        GPPlayerData playerData = GriefPreventionPlugin.instance.dataStore.getOrCreatePlayerData(player.getWorld(),
                player.getUniqueId());
        GPClaim claim = GriefPreventionPlugin.instance.dataStore.getClaimAtPlayer(playerData, player.getLocation());
        ArrayList<Claim> targetClaims = new ArrayList<>();

        if (claim == null) {
            GriefPreventionPlugin.sendMessage(player,
                    GriefPreventionPlugin.instance.messageData.claimNotFound.toText());
            return;
        } else {
            //check permission here
            if (claim.allowGrantPermission(player) != null) {
                final Text message = GriefPreventionPlugin.instance.messageData.permissionTrust
                        .apply(ImmutableMap.of("owner", claim.getOwnerName())).build();
                GriefPreventionPlugin.sendMessage(player, message);
                return;
            }

            if (trustType == TrustType.MANAGER) {
                Text denyReason = claim.allowEdit(player);
                if (denyReason != null) {
                    GriefPreventionPlugin.sendMessage(player,
                            GriefPreventionPlugin.instance.messageData.permissionGrant.toText());
                    return;
                }
            }

            targetClaims.add(claim);
        }

        try (final CauseStackManager.StackFrame frame = Sponge.getCauseStackManager().pushCauseFrame()) {
            Sponge.getCauseStackManager().pushCause(player);
            GPGroupTrustClaimEvent.Add event = new GPGroupTrustClaimEvent.Add(targetClaims, ImmutableList.of(group),
                    trustType);
            Sponge.getEventManager().post(event);
            if (event.isCancelled()) {
                player.sendMessage(Text.of(TextColors.RED, event.getMessage()
                        .orElse(Text.of("Could not trust group '" + group + "'. A plugin has denied it."))));
                return;
            }
        }

        final String permission = getTrustPermission(trustType);
        for (Claim currentClaim : targetClaims) {
            GPClaim gpClaim = (GPClaim) currentClaim;
            Set<Context> contexts = new HashSet<>();
            contexts.add(gpClaim.getContext());
            if (!gpClaim.getGroupTrustList(trustType).contains(group)) {
                gpClaim.getGroupTrustList(trustType).add(group);
            }
            subject.getSubjectData().setPermission(contexts, permission, Tristate.TRUE);
            gpClaim.getInternalClaimData().setRequiresSave(true);
        }

        final Text message = GriefPreventionPlugin.instance.messageData.trustGrant
                .apply(ImmutableMap.of("target", group, "type", trustType.name())).build();
        GriefPreventionPlugin.sendMessage(player, message);
    }

    private static String getTrustPermission(TrustType trustType) {
        if (trustType == TrustType.ACCESSOR) {
            return GPPermissions.TRUST_ACCESSOR;
        } else if (trustType == TrustType.CONTAINER) {
            return GPPermissions.TRUST_CONTAINER;
        } else if (trustType == TrustType.BUILDER) {
            return GPPermissions.TRUST_BUILDER;
        } else {
            return GPPermissions.TRUST_MANAGER;
        }
    }

    public static Consumer<CommandSource> createTeleportConsumer(CommandSource src, Location<World> location,
            Claim claim) {
        return teleport -> {
            if (!(src instanceof Player)) {
                // ignore
                return;
            }
            Player player = (Player) src;
            GPClaim gpClaim = (GPClaim) claim;
            GPPlayerData playerData = GriefPreventionPlugin.instance.dataStore.getPlayerData(player.getWorld(),
                    player.getUniqueId());
            if (!playerData.canIgnoreClaim(gpClaim) && !playerData.canManageAdminClaims) {
                // if not owner of claim, validate perms
                if (!player.getUniqueId().equals(claim.getOwnerUniqueId())) {
                    if (!player.hasPermission(GPPermissions.COMMAND_CLAIM_INFO_TELEPORT_OTHERS)
                            || !gpClaim.isUserTrusted(player, TrustType.ACCESSOR)) {
                        player.sendMessage(Text.of(TextColors.RED,
                                "You do not have permission to use the teleport feature in this claim."));
                        return;
                    }
                } else if (!player.hasPermission(GPPermissions.COMMAND_CLAIM_INFO_TELEPORT_BASE)) {
                    player.sendMessage(Text.of(TextColors.RED,
                            "You do not have permission to use the teleport feature in your claim."));
                    return;
                }
            }

            Location<World> safeLocation = Sponge.getGame().getTeleportHelper().getSafeLocation(location, 64, 16)
                    .orElse(null);
            if (safeLocation == null) {
                player.sendMessage(Text.builder().append(Text.of(TextColors.RED, "Location is not safe. "),
                        Text.builder().append(Text.of(TextColors.GREEN, "Are you sure you want to teleport here?"))
                                .onClick(TextActions.executeCallback(createForceTeleportConsumer(player, location)))
                                .style(TextStyles.UNDERLINE).build())
                        .build());
            } else {
                player.setLocation(safeLocation);
            }
        };
    }

    public static Consumer<CommandSource> createForceTeleportConsumer(Player player, Location<World> location) {
        return teleport -> {
            player.setLocation(location);
        };
    }

    public static void handleBankTransaction(CommandSource src, CommandContext args, GPClaim claim) {
        final EconomyService economyService = GriefPreventionPlugin.instance.economyService.orElse(null);
        if (economyService == null) {
            GriefPreventionPlugin.sendMessage(src,
                    GriefPreventionPlugin.instance.messageData.economyNotInstalled.toText());
            return;
        }

        if (claim.isSubdivision() || claim.isAdminClaim()) {
            return;
        }

        Account bankAccount = claim.getEconomyAccount().orElse(null);
        if (bankAccount == null) {
            GriefPreventionPlugin.sendMessage(src,
                    GriefPreventionPlugin.instance.messageData.economyVirtualNotSupported.toText());
            return;
        }

        final String command = args.<String>getOne("command").orElse(null);
        final double amount = args.<Double>getOne("amount").get();

        final UUID playerSource = ((Player) src).getUniqueId();
        final GPPlayerData playerData = GriefPreventionPlugin.instance.dataStore.getPlayerData(claim.getWorld(),
                claim.getOwnerUniqueId());
        if (playerData.canIgnoreClaim(claim) || claim.getOwnerUniqueId().equals(playerSource)
                || claim.getUserTrusts(TrustType.MANAGER).contains(playerData.playerID)) {
            final UniqueAccount playerAccount = economyService.getOrCreateAccount(playerData.playerID).get();
            try (final CauseStackManager.StackFrame frame = Sponge.getCauseStackManager().pushCauseFrame()) {
                Sponge.getCauseStackManager().pushCause(src);
                Sponge.getCauseStackManager().addContext(GriefPreventionPlugin.PLUGIN_CONTEXT,
                        GriefPreventionPlugin.instance);
                if (command.equalsIgnoreCase("withdraw")) {
                    TransactionResult result = bankAccount.withdraw(economyService.getDefaultCurrency(),
                            BigDecimal.valueOf(amount), Sponge.getCauseStackManager().getCurrentCause());
                    if (result.getResult() == ResultType.SUCCESS) {
                        final Text message = GriefPreventionPlugin.instance.messageData.claimBankWithdraw
                                .apply(ImmutableMap.of("amount", amount)).build();
                        GriefPreventionPlugin.sendMessage(src, message);
                        playerAccount.deposit(economyService.getDefaultCurrency(), BigDecimal.valueOf(amount),
                                Sponge.getCauseStackManager().getCurrentCause());
                        claim.getData().getEconomyData().addBankTransaction(new GPBankTransaction(
                                BankTransactionType.WITHDRAW_SUCCESS, playerData.playerID, Instant.now(), amount));
                    } else {
                        final Text message = GriefPreventionPlugin.instance.messageData.claimBankWithdrawNoFunds
                                .apply(ImmutableMap.of("balance",
                                        bankAccount.getBalance(economyService.getDefaultCurrency()), "amount",
                                        amount))
                                .build();
                        GriefPreventionPlugin.sendMessage(src, message);
                        claim.getData().getEconomyData().addBankTransaction(new GPBankTransaction(
                                BankTransactionType.WITHDRAW_FAIL, playerData.playerID, Instant.now(), amount));
                        return;
                    }
                } else if (command.equalsIgnoreCase("deposit")) {
                    TransactionResult result = playerAccount.withdraw(economyService.getDefaultCurrency(),
                            BigDecimal.valueOf(amount), Sponge.getCauseStackManager().getCurrentCause());
                    if (result.getResult() == ResultType.SUCCESS) {
                        final Text message = GriefPreventionPlugin.instance.messageData.claimBankDeposit
                                .apply(ImmutableMap.of("amount", amount)).build();
                        GriefPreventionPlugin.sendMessage(src, message);
                        bankAccount.deposit(economyService.getDefaultCurrency(), BigDecimal.valueOf(amount),
                                Sponge.getCauseStackManager().getCurrentCause());
                        claim.getData().getEconomyData().addBankTransaction(new GPBankTransaction(
                                BankTransactionType.DEPOSIT_SUCCESS, playerData.playerID, Instant.now(), amount));
                    } else {
                        GriefPreventionPlugin.sendMessage(src,
                                GriefPreventionPlugin.instance.messageData.claimBankDepositNoFunds.toText());
                        claim.getData().getEconomyData().addBankTransaction(new GPBankTransaction(
                                BankTransactionType.DEPOSIT_FAIL, playerData.playerID, Instant.now(), amount));
                        return;
                    }
                }
            }
        } else {
            final Text message = GriefPreventionPlugin.instance.messageData.claimBankNoPermission
                    .apply(ImmutableMap.of("owner", claim.getOwnerName())).build();
            GriefPreventionPlugin.sendMessage(src, message);
        }
    }

    public static void displayClaimBankInfo(CommandSource src, GPClaim claim) {
        displayClaimBankInfo(src, claim, false, false);
    }

    public static void displayClaimBankInfo(CommandSource src, GPClaim claim, boolean checkTown,
            boolean returnToClaimInfo) {
        final EconomyService economyService = GriefPreventionPlugin.instance.economyService.orElse(null);
        if (economyService == null) {
            GriefPreventionPlugin.sendMessage(src,
                    GriefPreventionPlugin.instance.messageData.economyNotInstalled.toText());
            return;
        }

        if (checkTown && !claim.isInTown()) {
            GriefPreventionPlugin.sendMessage(src, GriefPreventionPlugin.instance.messageData.townNotIn.toText());
            return;
        }

        if (!checkTown && (claim.isSubdivision() || claim.isAdminClaim())) {
            return;
        }

        final GPClaim town = claim.getTownClaim();
        Account bankAccount = checkTown ? town.getEconomyAccount().orElse(null)
                : claim.getEconomyAccount().orElse(null);
        if (bankAccount == null) {
            GriefPreventionPlugin.sendMessage(src,
                    GriefPreventionPlugin.instance.messageData.economyVirtualNotSupported.toText());
            return;
        }

        final GPPlayerData playerData = GriefPreventionPlugin.instance.dataStore.getPlayerData(claim.getWorld(),
                claim.getOwnerUniqueId());
        final double claimBalance = bankAccount.getBalance(economyService.getDefaultCurrency()).doubleValue();
        double taxOwed = -1;
        final double playerTaxRate = GPOptionHandler.getClaimOptionDouble(playerData.getPlayerSubject(), claim,
                GPOptions.Type.TAX_RATE, playerData);
        if (checkTown) {
            if (!town.getOwnerUniqueId().equals(playerData.playerID)) {
                for (Claim playerClaim : playerData.getInternalClaims()) {
                    GPClaim playerTown = (GPClaim) playerClaim.getTown().orElse(null);
                    if (!playerClaim.isTown() && playerTown != null
                            && playerTown.getUniqueId().equals(claim.getUniqueId())) {
                        taxOwed += (playerTown.getClaimBlocks() / 256) * playerTaxRate;
                    }
                }
            } else {
                taxOwed = town.getClaimBlocks() * playerTaxRate;
            }
        } else {
            taxOwed = claim.getClaimBlocks() * playerTaxRate;
        }

        final GriefPreventionConfig<?> activeConfig = GriefPreventionPlugin
                .getActiveConfig(claim.getWorld().getProperties());
        final ZonedDateTime withdrawDate = TaskUtils
                .getNextTargetZoneDate(activeConfig.getConfig().claim.taxApplyHour, 0, 0);
        Duration duration = Duration.between(Instant.now().truncatedTo(ChronoUnit.SECONDS),
                withdrawDate.toInstant());
        final long s = duration.getSeconds();
        final String timeLeft = String.format("%d:%02d:%02d", s / 3600, (s % 3600) / 60, (s % 60));
        final Text message = GriefPreventionPlugin.instance.messageData.claimBankInfo
                .apply(ImmutableMap.of("balance", claimBalance, "amount", taxOwed, "time_remaining", timeLeft,
                        "tax_balance", claim.getData().getEconomyData().getTaxBalance()))
                .build();
        Text transactions = Text.builder().append(Text.of(TextStyles.ITALIC, TextColors.AQUA, "Bank Transactions"))
                .onClick(TextActions
                        .executeCallback(createBankTransactionsConsumer(src, claim, checkTown, returnToClaimInfo)))
                .onHover(TextActions.showText(Text.of("Click here to view bank transactions"))).build();
        List<Text> textList = new ArrayList<>();
        if (returnToClaimInfo) {
            textList.add(Text.builder()
                    .append(Text.of(TextColors.WHITE, "\n[", TextColors.AQUA, "Return to claim info",
                            TextColors.WHITE, "]\n"))
                    .onClick(TextActions.executeCallback(CommandHelper.createCommandConsumer(src, "claiminfo", "")))
                    .build());
        }
        textList.add(message);
        textList.add(transactions);
        PaginationService paginationService = Sponge.getServiceManager().provide(PaginationService.class).get();
        PaginationList.Builder paginationBuilder = paginationService.builder()
                .title(Text.of(TextColors.AQUA, "Bank Info")).padding(Text.of(TextStyles.STRIKETHROUGH, "-"))
                .contents(textList);
        paginationBuilder.sendTo(src);
    }

    public static Consumer<CommandSource> createBankTransactionsConsumer(CommandSource src, GPClaim claim,
            boolean checkTown, boolean returnToClaimInfo) {
        return settings -> {
            final String name = "Bank Transactions";
            List<String> bankTransactions = new ArrayList<>(
                    claim.getData().getEconomyData().getBankTransactionLog());
            Collections.reverse(bankTransactions);
            List<Text> textList = new ArrayList<>();
            textList.add(
                    Text.builder().append(Text.of(TextColors.WHITE, "\n[", TextColors.AQUA, "Return to bank info",
                            TextColors.WHITE, "]\n")).onClick(TextActions.executeCallback(consumer -> {
                                displayClaimBankInfo(src, claim, checkTown, returnToClaimInfo);
                            })).build());
            Gson gson = new Gson();
            for (String transaction : bankTransactions) {
                GPBankTransaction bankTransaction = gson.fromJson(transaction, GPBankTransaction.class);
                final Duration duration = Duration.between(bankTransaction.timestamp,
                        Instant.now().truncatedTo(ChronoUnit.SECONDS));
                final long s = duration.getSeconds();
                final User user = GriefPreventionPlugin.getOrCreateUser(bankTransaction.source);
                final String timeLeft = String.format("%dh %02dm %02ds", s / 3600, (s % 3600) / 60, (s % 60))
                        + " ago";
                textList.add(Text.of(getTransactionColor(bankTransaction.type), bankTransaction.type.name(),
                        TextColors.BLUE, " | ", TextColors.WHITE, bankTransaction.amount, TextColors.BLUE, " | ",
                        TextColors.GRAY, timeLeft, user == null ? ""
                                : Text.of(TextColors.BLUE, " | ", TextColors.LIGHT_PURPLE, user.getName())));
            }
            textList.add(Text.builder()
                    .append(Text.of(TextColors.WHITE, "\n[", TextColors.AQUA, "Return to bank info",
                            TextColors.WHITE, "]\n"))
                    .onClick(TextActions.executeCallback(CommandHelper.createCommandConsumer(src, "claimbank", "")))
                    .build());
            PaginationService paginationService = Sponge.getServiceManager().provide(PaginationService.class).get();
            PaginationList.Builder paginationBuilder = paginationService.builder()
                    .title(Text.of(TextColors.AQUA, name)).padding(Text.of(TextStyles.STRIKETHROUGH, "-"))
                    .contents(textList);
            paginationBuilder.sendTo(src);
        };
    }

    public static TextColor getTransactionColor(BankTransactionType type) {
        switch (type) {
        case DEPOSIT_SUCCESS:
        case TAX_SUCCESS:
        case WITHDRAW_SUCCESS:
            return TextColors.GREEN;
        case DEPOSIT_FAIL:
        case TAX_FAIL:
        case WITHDRAW_FAIL:
            return TextColors.RED;
        default:
            return TextColors.GREEN;
        }
    }

    private static Comparator<Object[]> rawTextComparator() {
        return (t1, t2) -> Text.of(t1).compareTo(Text.of(t2));
    }

    public static List<Text> stripeText(List<Object[]> texts) {
        Collections.sort(texts, rawTextComparator());

        ImmutableList.Builder<Text> finalTexts = ImmutableList.builder();
        for (int i = 0; i < texts.size(); i++) {
            Object[] text = texts.get(i);
            text[0] = i % 2 == 0 ? TextColors.GREEN : TextColors.AQUA; // Set starting color
            finalTexts.add(Text.of(text));
        }
        return finalTexts.build();
    }

    public static Text getFlagTypeHoverText(FlagType type) {
        Text hoverText = Text.of("");
        if (type == FlagType.DEFAULT) {
            hoverText = Text.of(TextColors.LIGHT_PURPLE, "DEFAULT ", TextColors.WHITE,
                    " : Default is last to be checked. Both claim and override take priority over this.");
        } else if (type == FlagType.CLAIM) {
            hoverText = Text.of(TextColors.GOLD, "CLAIM", TextColors.WHITE,
                    " : Claim is checked before default values. Allows claim owners to specify flag settings in claim only.");
        } else if (type == FlagType.OVERRIDE) {
            hoverText = Text.of(TextColors.RED, "OVERRIDE", TextColors.WHITE,
                    " : Override has highest priority and is checked above both default and claim values. Allows admins to override all basic and admin claims.");
        } else if (type == FlagType.INHERIT) {
            hoverText = Text.of(TextColors.AQUA, "INHERIT", TextColors.WHITE,
                    " : Inherit is an enforced flag set by a parent claim that cannot changed.");
        }
        return hoverText;
    }

    public static Text getBaseFlagOverlayText(String flagPermission) {
        String baseFlag = flagPermission.replace(GPPermissions.FLAG_BASE + ".", "");
        int endIndex = baseFlag.indexOf(".");
        if (endIndex != -1) {
            baseFlag = baseFlag.substring(0, endIndex);
        }
        if (!ClaimFlag.contains(baseFlag)) {
            return Text.of("Not defined.");
        }

        final ClaimFlag claimFlag = ClaimFlag.getEnum(baseFlag);

        switch (claimFlag) {
        case BLOCK_BREAK:
            return Text.of("Controls whether a block can be broken.\n", TextColors.LIGHT_PURPLE, "Example 1",
                    TextColors.WHITE, " : To prevent any source from breaking dirt blocks, enter\n",
                    TextColors.GREEN, "/cf block-break minecraft:dirt false\n", TextColors.AQUA, "Note",
                    TextColors.WHITE, " : ", "minecraft represents the modid and dirt represents the block id.\n",
                    "Specifying no modid will always default to minecraft.\n", TextColors.LIGHT_PURPLE, "Example 2",
                    TextColors.WHITE, " : To prevent players from breaking dirt blocks, enter\n", TextColors.GREEN,
                    "/cf block-break minecraft:player minecraft:dirt false\n");
        case BLOCK_PLACE:
            return Text.of("Controls whether a block can be placed.\n", TextColors.LIGHT_PURPLE, "Example 1",
                    TextColors.WHITE, " : To prevent any source from placing dirt blocks, enter\n",
                    TextColors.GREEN, "/cf block-place minecraft:dirt false\n", TextColors.AQUA, "Note",
                    TextColors.WHITE, " : ", "minecraft represents the modid and dirt represents the block id.\n",
                    "Specifying no modid will always default to minecraft.\n", TextColors.LIGHT_PURPLE, "Example 2",
                    TextColors.WHITE, " : To prevent players from placing dirt blocks, enter\n", TextColors.GREEN,
                    "/cf block-place minecraft:player minecraft:dirt false\n");
        case COMMAND_EXECUTE:
            return Text.of("Controls whether a command can be executed.\n", TextColors.LIGHT_PURPLE, "Example",
                    TextColors.WHITE, " : To prevent pixelmon's command '/shop select' from being run, enter\n",
                    TextColors.GREEN, "/cf command-execute pixelmon:shop[select] false\n", TextColors.AQUA, "Note",
                    TextColors.WHITE, " : ", TextStyles.ITALIC, TextColors.GOLD, "pixelmon", TextStyles.RESET,
                    TextColors.RESET, " represents the modid, ", TextStyles.ITALIC, TextColors.GOLD, "shop",
                    TextStyles.RESET, TextColors.RESET, " represents the base command, and ", TextStyles.ITALIC,
                    TextColors.GOLD, "select", TextStyles.RESET, TextColors.RESET, " represents the argument.\n",
                    "Specifying no modid will always default to minecraft.\n");
        case COMMAND_EXECUTE_PVP:
            return Text.of("Controls whether a command can be executed while engaged in ", TextColors.RED, "PvP.\n",
                    TextColors.LIGHT_PURPLE, "Example", TextColors.WHITE,
                    " : To prevent pixelmon's command '/shop select' from being run, enter\n", TextColors.GREEN,
                    "/cf command-execute pixelmon:shop[select] false\n", TextColors.AQUA, "Note", TextColors.WHITE,
                    " : ", TextStyles.ITALIC, TextColors.GOLD, "pixelmon", TextStyles.RESET, TextColors.RESET,
                    " represents the modid, ", TextStyles.ITALIC, TextColors.GOLD, "shop", TextStyles.RESET,
                    TextColors.RESET, " represents the base command, and ", TextStyles.ITALIC, TextColors.GOLD,
                    "select", TextStyles.RESET, TextColors.RESET, " represents the argument.\n",
                    "Specifying no modid will always default to minecraft.\n");
        case ENTER_CLAIM:
            return Text.of("Controls whether an entity can enter claim.\n", TextColors.AQUA, "Note",
                    TextColors.WHITE, " : If you want to use this for players, it is recommended to use \n",
                    "the '/cfg' command with the group the player is in.");
        case ENTITY_CHUNK_SPAWN:
            return Text.of("Controls whether an entity can be spawned during chunk load.\n", TextColors.AQUA,
                    "Note", TextColors.WHITE,
                    " : This will remove all saved entities within a chunk after it loads.\n",
                    TextColors.LIGHT_PURPLE, "Example", TextColors.WHITE,
                    " : To prevent horses from spawning in chunks enter\n", TextColors.GREEN,
                    "/cf entity-chunk-spawn minecraft:horse false");
        case ENTITY_COLLIDE_BLOCK:
            return Text.of("Controls whether an entity can collide with a block.\n", TextColors.LIGHT_PURPLE,
                    "Example", TextColors.WHITE, " : To prevent entity collisions with dirt blocks, enter\n",
                    TextColors.GREEN, "/cf entity-collide-block minecraft:dirt false");
        case ENTITY_COLLIDE_ENTITY:
            return Text.of("Controls whether an entity can collide with an entity.\n", TextColors.LIGHT_PURPLE,
                    "Example", TextColors.WHITE, " : To prevent entity collisions with item frames, enter\n",
                    TextColors.GREEN, "/cf entity-collide-entity minecraft:itemframe false");
        case ENTITY_DAMAGE:
            return Text.of("Controls whether an entity can be damaged.\n", TextColors.LIGHT_PURPLE, "Example 1",
                    TextColors.WHITE, " : To prevent horses from being damaged, enter\n", TextColors.GREEN,
                    "/cf entity-damage minecraft:horse false\n", TextColors.LIGHT_PURPLE, "Example 2",
                    TextColors.WHITE, " : To prevent all animals from being damaged, enter\n", TextColors.GREEN,
                    "/cf entity-damage minecraft:animals false");
        case ENTITY_RIDING:
            return Text.of("Controls whether an entity can be mounted.\n", TextColors.LIGHT_PURPLE, "Example",
                    TextColors.WHITE, " : To prevent horses from being mounted enter\n", TextColors.GREEN,
                    "/cf entity-riding minecraft:horse false");
        case ENTITY_SPAWN:
            return Text.of("Controls whether an entity can be spawned into the world.\n", TextColors.AQUA, "Note",
                    TextColors.WHITE, " : This does not include entity items. See item-spawn flag.\n",
                    TextColors.LIGHT_PURPLE, "Example", TextColors.WHITE,
                    " : To prevent horses from spawning enter\n", TextColors.GREEN,
                    "/cf entity-spawn minecraft:horse false");
        case ENTITY_TELEPORT_FROM:
            return Text.of("Controls whether an entity can teleport from their current location.\n",
                    TextColors.AQUA, "Note", TextColors.WHITE,
                    " : If you want to use this for players, it is recommended to use \n",
                    "the '/cfg' command with the group the player is in.");
        case ENTITY_TELEPORT_TO:
            return Text.of("Controls whether an entity can teleport to a location.\n", TextColors.LIGHT_PURPLE,
                    "Example", TextColors.WHITE,
                    " : To prevent creepers from traveling and/or teleporting within your claim, enter\n",
                    TextColors.GREEN, "/cf entity-teleport-to minecraft:creeper false\n", TextColors.AQUA, "Note",
                    TextColors.WHITE, " : If you want to use this for players, it is recommended to use \n",
                    "the '/cfg' command with the group the player is in.");
        case EXIT_CLAIM:
            return Text.of("Controls whether an entity can exit claim.\n", TextColors.AQUA, "Note",
                    TextColors.WHITE, " : If you want to use this for players, it is recommended to use \n",
                    "the '/cfg' command with the group the player is in.");
        case EXPLOSION:
            return Text.of("Controls whether an explosion can occur in the world.\n", TextColors.LIGHT_PURPLE,
                    "Example", TextColors.WHITE, " : To prevent any explosion, enter\n", TextColors.GREEN,
                    "/cf explosion any false");
        case EXPLOSION_SURFACE:
            return Text.of("Controls whether an explosion can occur above the surface in a world.\n",
                    TextColors.LIGHT_PURPLE, "Example", TextColors.WHITE,
                    " : To prevent an explosion above surface, enter\n", TextColors.GREEN,
                    "/cf explosion-surface any false");
        case FIRE_SPREAD:
            return Text.of("Controls whether fire can spread in a world.\n", TextColors.AQUA, "Note",
                    TextColors.WHITE, " : This does not prevent the initial fire being placed, only spread.\n",
                    TextColors.LIGHT_PURPLE, "Example", TextColors.WHITE,
                    " : To prevent fire from spreading, enter\n", TextColors.GREEN, "/cf fire-spread any false");
        case INTERACT_BLOCK_PRIMARY:
            return Text.of("Controls whether a player can left-click(attack) a block.\n", TextColors.LIGHT_PURPLE,
                    "Example", TextColors.WHITE, " : To prevent players from left-clicking chests, enter\n",
                    TextColors.GREEN, "/cf interact-block-primary minecraft:chest false");
        case INTERACT_BLOCK_SECONDARY:
            return Text.of("Controls whether a player can right-click a block.\n", TextColors.LIGHT_PURPLE,
                    "Example", TextColors.WHITE,
                    " : To prevent players from right-clicking(opening) chests, enter\n", TextColors.GREEN,
                    "/cf interact-block-secondary minecraft:chest false");
        case INTERACT_ENTITY_PRIMARY:
            return Text.of("Controls whether a player can left-click(attack) an entity.\n", TextColors.LIGHT_PURPLE,
                    "Example", TextColors.WHITE, " : To prevent players from left-clicking horses, enter\n",
                    TextColors.GREEN, "/cf interact-entity-primary minecraft:player minecraft:horse false\n");
        case INTERACT_ENTITY_SECONDARY:
            return Text.of("Controls whether a player can right-click on an entity.\n", TextColors.LIGHT_PURPLE,
                    "Example", TextColors.WHITE, " : To prevent horses from being mounted, enter\n",
                    TextColors.GREEN, "/cf interact-entity-secondary minecraft:horse false\n", TextColors.AQUA,
                    "Note", TextColors.WHITE, " : ",
                    "minecraft represents the modid and horse represents the entity id.\n",
                    "Specifying no modid will always default to minecraft.\n");
        case INTERACT_INVENTORY:
            return Text.of(
                    "Controls whether a player can right-click with a block that contains inventory such as a chest.\n",
                    TextColors.LIGHT_PURPLE, "Example", TextColors.WHITE,
                    " : To prevent players from right-clicking any block that contains inventory, enter\n",
                    TextColors.GREEN, "/cf interact-inventory any false");
        case INTERACT_INVENTORY_CLICK:
            return Text.of("Controls whether a player can click on an inventory slot.\n", TextColors.LIGHT_PURPLE,
                    "Example", TextColors.WHITE,
                    " : To prevent players from clicking an inventory slot that contains diamond, enter\n",
                    TextColors.GREEN, "/cf interact-inventory-click minecraft:diamond false");
        case INTERACT_ITEM_PRIMARY:
            return Text.of("Controls whether a player can left-click(attack) with an item.\n",
                    TextColors.LIGHT_PURPLE, "Example", TextColors.WHITE,
                    " : To prevent players from left-clicking while holding a diamond sword, enter\n",
                    TextColors.GREEN, "/cf interact-item-primary minecraft:diamond_sword false");
        case INTERACT_ITEM_SECONDARY:
            return Text.of("Controls whether a player can right-click with an item.\n", TextColors.LIGHT_PURPLE,
                    "Example", TextColors.WHITE,
                    " : To prevent players from right-clicking while holding a flint and steel, enter\n",
                    TextColors.GREEN, "/cf interact-item-secondary minecraft:flint_and_steel false");
        case ITEM_DROP:
            return Text.of("Controls whether an item can be dropped.\n", TextColors.LIGHT_PURPLE, "Example",
                    TextColors.WHITE, " : To prevent tnt from dropping in the world, enter\n", TextColors.GREEN,
                    "/cf item-drop minecraft:tnt false");
        case ITEM_PICKUP:
            return Text.of("Controls whether an item can be picked up.\n", TextColors.LIGHT_PURPLE, "Example",
                    TextColors.WHITE, " : To prevent tnt from dropping in the world, enter\n", TextColors.GREEN,
                    "/cf item-drop minecraft:tnt false");
        case ITEM_SPAWN:
            return Text.of("Controls whether an item can be spawned into the world up.\n", TextColors.LIGHT_PURPLE,
                    "Example", TextColors.WHITE, " : To prevent feather's from dropping in the world, enter\n",
                    TextColors.GREEN, "/cf item-drop minecraft:feather false");
        case ITEM_USE:
            return Text.of("Controls whether an item can be used.\n", TextColors.LIGHT_PURPLE, "Example",
                    TextColors.WHITE, " : To prevent usage of diamond swords, enter\n", TextColors.GREEN,
                    "/cf item-use minecraft:diamond_sword false");
        case LIQUID_FLOW:
            return Text.of("Controls whether liquid is allowed to flow.\n", TextColors.LIGHT_PURPLE, "Example",
                    TextColors.WHITE, " : To prevent liquid flow, enter\n", TextColors.GREEN,
                    "/cf liquid-flow any false");
        case PORTAL_USE:
            return Text.of("Controls whether a portal can be used.\n", TextColors.LIGHT_PURPLE, "Example 1",
                    TextColors.WHITE, " : To prevent any source from using portals, enter\n", TextColors.GREEN,
                    "/cf portal-use any false\n", TextColors.LIGHT_PURPLE, "Example 2", TextColors.WHITE,
                    " : To prevent only players from using portals, enter\n", TextColors.GREEN,
                    "/cf portal-use minecraft:player any false");
        case PROJECTILE_IMPACT_BLOCK:
            return Text.of("Controls whether a projectile can impact(collide) with a block.\n", TextColors.AQUA,
                    "Note", TextColors.WHITE,
                    " : This involves things such as potions, arrows, throwables, pixelmon pokeballs, etc.\n",
                    TextColors.LIGHT_PURPLE, "Example 1", TextColors.WHITE,
                    " : To prevent any projectile from impacting a block, enter\n", TextColors.GREEN,
                    "/cf projectile-impact-block any false\n", TextColors.LIGHT_PURPLE, "Example 2",
                    TextColors.WHITE, " : To allow pixelmon pokeball's to impact blocks, enter\n", TextColors.GREEN,
                    "/cf projectile-impact-block pixelmon:occupiedpokeball any true");
        case PROJECTILE_IMPACT_ENTITY:
            return Text.of("Controls whether a projectile can impact(collide) with an entity.\n", TextColors.AQUA,
                    "Note", TextColors.WHITE,
                    " : This involves things such as potions, arrows, throwables, pixelmon pokeballs, etc.\n",
                    TextColors.LIGHT_PURPLE, "Example 1", TextColors.WHITE,
                    " : To prevent any projectile from impacting an entity, enter\n", TextColors.GREEN,
                    "/cf projectile-impact-entity any false\n", TextColors.LIGHT_PURPLE, "Example 2",
                    TextColors.WHITE, " : To allow arrows to impact entities, enter\n", TextColors.GREEN,
                    "/cf projectile-impact-entity minecraft:arrow any true");
        default:
            return Text.of("Not defined.");
        }
    }
}