From 3b92ad7113fe02e3e826363b5d9a7b1b3a0b6977 Mon Sep 17 00:00:00 2001 From: William Date: Sun, 25 Jun 2023 19:59:44 +0100 Subject: [PATCH] Add support for customising flags (#273) * Add ability to customize flags from config * Update Config-Files.md with new flags.yml * Add API methods for flags, tweak method names * Add API methods for flags, tweak method names * docs: Add note about flags.yml to Legacy Migration --- .../william278/husktowns/BukkitHuskTowns.java | 12 + .../husktowns/hook/LuckPermsHook.java | 6 +- .../net/william278/husktowns/HuskTowns.java | 8 +- .../husktowns/api/IHuskTownsAPI.java | 48 ++- .../net/william278/husktowns/claim/Flag.java | 284 ++++++++++++++---- .../net/william278/husktowns/claim/Rules.java | 57 +++- .../husktowns/command/TownCommand.java | 6 +- .../william278/husktowns/config/Flags.java | 90 ++++++ .../william278/husktowns/config/Presets.java | 127 ++++---- .../husktowns/listener/Operation.java | 11 + .../husktowns/listener/OperationHandler.java | 6 +- .../husktowns/manager/ClaimsManager.java | 4 +- .../husktowns/manager/TownsManager.java | 4 +- .../husktowns/menu/RulesConfig.java | 9 +- .../husktowns/migrator/LegacyMigrator.java | 20 +- .../net/william278/husktowns/town/Town.java | 10 +- docs/Config-Files.md | 58 ++++ docs/Legacy-Migration.md | 1 + gradle.properties | 2 +- 19 files changed, 583 insertions(+), 180 deletions(-) create mode 100644 common/src/main/java/net/william278/husktowns/config/Flags.java diff --git a/bukkit/src/main/java/net/william278/husktowns/BukkitHuskTowns.java b/bukkit/src/main/java/net/william278/husktowns/BukkitHuskTowns.java index 61157ff8..b4bc5684 100644 --- a/bukkit/src/main/java/net/william278/husktowns/BukkitHuskTowns.java +++ b/bukkit/src/main/java/net/william278/husktowns/BukkitHuskTowns.java @@ -72,6 +72,7 @@ public class BukkitHuskTowns extends JavaPlugin implements HuskTowns, PluginMess private Locales locales; private Roles roles; private Presets presets; + private Flags flags; private Levels levels; private Server server; private Database database; @@ -239,6 +240,17 @@ public void setRulePresets(@NotNull Presets presets) { this.presets = presets; } + @Override + @NotNull + public Flags getFlags() { + return flags; + } + + @Override + public void setFlags(@NotNull Flags flags) { + this.flags = flags; + } + @Override @NotNull public Levels getLevels() { diff --git a/bukkit/src/main/java/net/william278/husktowns/hook/LuckPermsHook.java b/bukkit/src/main/java/net/william278/husktowns/hook/LuckPermsHook.java index b93507d7..091ad388 100644 --- a/bukkit/src/main/java/net/william278/husktowns/hook/LuckPermsHook.java +++ b/bukkit/src/main/java/net/william278/husktowns/hook/LuckPermsHook.java @@ -136,11 +136,11 @@ public ContextSet estimatePotentialContexts() { private void setContextsFromRules(@NotNull ContextConsumer consumer, Rules wilderness) { consumer.accept(ContextKey.CAN_PLAYER_BUILD.getKey(plugin), wilderness - .cancelOperation(Operation.Type.BLOCK_BREAK) ? "false" : "true"); + .cancelOperation(Operation.Type.BLOCK_BREAK, plugin().getFlags()) ? "false" : "true"); consumer.accept(ContextKey.CAN_PLAYER_OPEN_CONTAINERS.getKey(plugin), wilderness - .cancelOperation(Operation.Type.CONTAINER_OPEN) ? "false" : "true"); + .cancelOperation(Operation.Type.CONTAINER_OPEN, plugin().getFlags()) ? "false" : "true"); consumer.accept(ContextKey.CAN_PLAYER_INTERACT.getKey(plugin), wilderness - .cancelOperation(Operation.Type.BLOCK_INTERACT) ? "false" : "true"); + .cancelOperation(Operation.Type.BLOCK_INTERACT, plugin().getFlags()) ? "false" : "true"); } } diff --git a/common/src/main/java/net/william278/husktowns/HuskTowns.java b/common/src/main/java/net/william278/husktowns/HuskTowns.java index 2a1eb947..490c8926 100644 --- a/common/src/main/java/net/william278/husktowns/HuskTowns.java +++ b/common/src/main/java/net/william278/husktowns/HuskTowns.java @@ -89,6 +89,11 @@ public interface HuskTowns extends TaskRunner, EventDispatcher, AdvancementTrack void setRulePresets(@NotNull Presets presets); + @NotNull + Flags getFlags(); + + void setFlags(@NotNull Flags flags); + @NotNull Levels getLevels(); @@ -194,7 +199,7 @@ default Optional getUserTown(@NotNull User user) throws IllegalStateExce final int weight = town.getMembers().get(user.getUuid()); return Optional.of(getRoles().fromWeight(weight) .map(role -> new Member(user, town, role)) - .orElseThrow(() -> new IllegalStateException("No role found for weight " + weight))); + .orElseThrow(() -> new IllegalStateException("No role found for weight \"" + weight + "\""))); }); } @@ -319,6 +324,7 @@ default void loadConfig() throws RuntimeException { setSettings(Annotaml.create(new File(getDataFolder(), "config.yml"), Settings.class).get()); setRoles(Annotaml.create(new File(getDataFolder(), "roles.yml"), Roles.class).get()); setRulePresets(Annotaml.create(new File(getDataFolder(), "rules.yml"), Presets.class).get()); + setFlags(Annotaml.create(new File(getDataFolder(), "flags.yml"), Flags.class).get()); setLevels(Annotaml.create(new File(getDataFolder(), "levels.yml"), new Levels()).get()); setLocales(Annotaml.create(new File(getDataFolder(), "messages-" + getSettings().getLanguage() + ".yml"), Annotaml.create(Locales.class, getResource("locales/" + getSettings().getLanguage() + ".yml")).get()).get()); diff --git a/common/src/main/java/net/william278/husktowns/api/IHuskTownsAPI.java b/common/src/main/java/net/william278/husktowns/api/IHuskTownsAPI.java index d88ded69..cc617084 100644 --- a/common/src/main/java/net/william278/husktowns/api/IHuskTownsAPI.java +++ b/common/src/main/java/net/william278/husktowns/api/IHuskTownsAPI.java @@ -26,12 +26,11 @@ import net.william278.husktowns.user.SavedUser; import net.william278.husktowns.user.User; import net.william278.husktowns.util.Validator; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Unmodifiable; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.UUID; +import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.function.Consumer; @@ -49,6 +48,7 @@ public interface IHuskTownsAPI { * * @return The {@link HuskTowns} instance */ + @ApiStatus.Internal @NotNull HuskTowns getPlugin(); @@ -83,11 +83,47 @@ default void setAdvancements(@NotNull Advancement advancements) throws IllegalSt * Get the Town {@link Advancement Advancements} used by the plugin. * * @return The root {@link Advancement} used by the plugin, if advancements are enabled. + * @since 2.4 */ default Optional getAdvancements() { return getPlugin().getAdvancements(); } + /** + * Get the set of {@link Flag}s in use by the plugin. + * + * @return The set of {@link Flag}s currently in use + * @since 2.5 + */ + @Unmodifiable + default Set getFlagSet() { + return getPlugin().getFlags().getFlagSet(); + } + + /** + * Get the {@link Flag} with the given name. + * + * @param name The name of the {@link Flag} to get + * @return The {@link Flag} with the given name, if it exists + * @since 2.5 + */ + default Optional getFlag(@NotNull String name) { + return getPlugin().getFlags().getFlag(name); + } + + /** + * Register one or more {@link Flag}s to be used by the plugin. + * + * @param flags The {@link Flag}s to register + * @since 2.5 + */ + default void registerFlags(@NotNull Flag... flags) { + final Set flagSet = new LinkedHashSet<>(getFlagSet()); + flagSet.addAll(Arrays.asList(flags)); + getPlugin().getFlags().setFlags(flagSet); + } + + /** * Get a list of {@link ClaimWorld}s on this server. If you want to get the {@link ClaimWorld}s on every server, * see {@link #getAllClaimWorlds()}. @@ -304,7 +340,7 @@ default void createAdminClaimAt(@NotNull OnlineUser actor, @NotNull Position pos */ default void deleteClaimAt(@NotNull OnlineUser actor, @NotNull Chunk chunk, @NotNull World world) throws IllegalArgumentException { final TownClaim townClaim = getClaimAt(chunk, world) - .orElseThrow(() -> new IllegalArgumentException("No claim exists at " + chunk)); + .orElseThrow(() -> new IllegalArgumentException("No claim exists at: " + chunk)); getPlugin().runAsync(() -> getPlugin().getManager().claims().deleteClaimData(actor, townClaim, world)); } @@ -330,7 +366,7 @@ default void deleteClaimAt(@NotNull OnlineUser actor, @NotNull Position position */ default void updateClaim(@NotNull TownClaim claim, @NotNull World world) throws IllegalArgumentException { final ClaimWorld claimWorld = getClaimWorld(world) - .orElseThrow(() -> new IllegalArgumentException(world + " is not claimable")); + .orElseThrow(() -> new IllegalArgumentException("World \"" + world.getName() + "\" is not claimable")); getPlugin().runAsync(() -> { if (claim.isAdminClaim(getPlugin())) { return; diff --git a/common/src/main/java/net/william278/husktowns/claim/Flag.java b/common/src/main/java/net/william278/husktowns/claim/Flag.java index da04be0f..49c6b9b0 100644 --- a/common/src/main/java/net/william278/husktowns/claim/Flag.java +++ b/common/src/main/java/net/william278/husktowns/claim/Flag.java @@ -17,77 +17,237 @@ import org.jetbrains.annotations.NotNull; import java.util.Arrays; +import java.util.Locale; import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; /** - * Various flag types + * Represents flags, which can be set on claims to allow or deny certain groups of operations */ -public enum Flag { - EXPLOSION_DAMAGE( - Operation.Type.EXPLOSION_DAMAGE_TERRAIN, - Operation.Type.EXPLOSION_DAMAGE_ENTITY - ), - FIRE_DAMAGE( - Operation.Type.FIRE_SPREAD, - Operation.Type.FIRE_BURN - ), - MOB_GRIEFING( - Operation.Type.MONSTER_DAMAGE_TERRAIN - ), - MONSTER_SPAWNING( - Operation.Type.MONSTER_SPAWN, - Operation.Type.PLAYER_DAMAGE_MONSTER - ), - PUBLIC_BUILD_ACCESS( - Operation.Type.BLOCK_BREAK, - Operation.Type.BLOCK_PLACE, - Operation.Type.CONTAINER_OPEN, - Operation.Type.FARM_BLOCK_PLACE, - Operation.Type.FARM_BLOCK_PLACE, - Operation.Type.FARM_BLOCK_INTERACT, - Operation.Type.FILL_BUCKET, - Operation.Type.EMPTY_BUCKET, - Operation.Type.BREAK_HANGING_ENTITY, - Operation.Type.PLACE_HANGING_ENTITY, - Operation.Type.BLOCK_INTERACT, - Operation.Type.ENTITY_INTERACT, - Operation.Type.REDSTONE_INTERACT, - Operation.Type.USE_SPAWN_EGG, - Operation.Type.PLAYER_DAMAGE_MONSTER, - Operation.Type.PLAYER_DAMAGE_PERSISTENT_ENTITY, - Operation.Type.PLAYER_DAMAGE_ENTITY - ), - PUBLIC_CONTAINER_ACCESS( - Operation.Type.CONTAINER_OPEN - ), - PUBLIC_FARM_ACCESS( - Operation.Type.BLOCK_INTERACT, - Operation.Type.FARM_BLOCK_BREAK, - Operation.Type.FARM_BLOCK_PLACE, - Operation.Type.FARM_BLOCK_INTERACT, - Operation.Type.PLAYER_DAMAGE_ENTITY - ), - PUBLIC_INTERACT_ACCESS( - Operation.Type.BLOCK_INTERACT, - Operation.Type.ENTITY_INTERACT, - Operation.Type.REDSTONE_INTERACT - ), - PVP( - Operation.Type.PLAYER_DAMAGE_PLAYER - ); - - - private final Operation.Type[] allowedOperations; - - Flag(@NotNull Operation.Type... allowedOperations) { - this.allowedOperations = allowedOperations; +public class Flag implements Comparable { + + @Deprecated(since = "2.5") + public static final Flag EXPLOSION_DAMAGE = Defaults.EXPLOSION_DAMAGE.getFlag(); + @Deprecated(since = "2.5") + public static final Flag FIRE_DAMAGE = Defaults.FIRE_DAMAGE.getFlag(); + @Deprecated(since = "2.5") + public static final Flag MOB_GRIEFING = Defaults.MOB_GRIEFING.getFlag(); + @Deprecated(since = "2.5") + public static final Flag MONSTER_SPAWNING = Defaults.MONSTER_SPAWNING.getFlag(); + @Deprecated(since = "2.5") + public static final Flag PUBLIC_BUILD_ACCESS = Defaults.PUBLIC_BUILD_ACCESS.getFlag(); + @Deprecated(since = "2.5") + public static final Flag PUBLIC_CONTAINER_ACCESS = Defaults.PUBLIC_CONTAINER_ACCESS.getFlag(); + @Deprecated(since = "2.5") + public static final Flag PUBLIC_FARM_ACCESS = Defaults.PUBLIC_FARM_ACCESS.getFlag(); + @Deprecated(since = "2.5") + public static final Flag PUBLIC_INTERACT_ACCESS = Defaults.PUBLIC_INTERACT_ACCESS.getFlag(); + @Deprecated(since = "2.5") + public static final Flag PVP = Defaults.PVP.getFlag(); + + private final String name; + private final Set allowedOperations; + + private Flag(@NotNull String name, @NotNull Operation.Type... allowedOperations) { + this.name = name; + this.allowedOperations = Set.of(allowedOperations); + } + + /** + * Create a new flag with the given name and allowed operations + * + * @param name The ID name of the flag + * @param allowedOperations The operations allowed by this flag + * @return The flag + * @throws IllegalArgumentException If the name is empty or contains whitespace + */ + @NotNull + public static Flag of(@NotNull String name, @NotNull Set allowedOperations) throws IllegalArgumentException { + if (name.isEmpty() || name.contains(" ")) { + throw new IllegalArgumentException("Flag name cannot be empty or contain whitespace"); + } + return new Flag(name.toLowerCase(Locale.ENGLISH), allowedOperations.toArray(new Operation.Type[0])); + } + + /** + * Get the operations allowed by this flag + * + * @return The operations allowed by this flag + */ + @NotNull + public Set getAllowedOperations() { + return allowedOperations; } + /** + * Get whether this flag allows the given operation + * + * @param type The operation type to check + * @return Whether the operation is allowed + */ public boolean isOperationAllowed(@NotNull Operation.Type type) { - return Arrays.asList(allowedOperations).contains(type); + return getAllowedOperations().contains(type); } + /** + * Get the name of the flag + * + * @return The name of the flag + */ + @NotNull + public String getName() { + return name; + } + + /** + * Get the name of the flag + * + * @return The name of the flag + * @deprecated Use {@link #getName()} instead + */ + @Deprecated(since = "2.5") + public String name() { + return getName(); + } + + /** + * Get a default flag from the given name + * + * @param id The name of the flag + * @return The flag, or an empty optional if not found + * @deprecated Use {@link Defaults#fromId(String)} instead + */ + @Deprecated(since = "2.5") public static Optional fromId(@NotNull String id) { - return Arrays.stream(values()).filter(flag -> flag.name().equalsIgnoreCase(id)).findFirst(); + return Defaults.fromId(id); + } + + /** + * Get the default set of flags + * + * @return The default set of flags + */ + @NotNull + public static Set getDefaults() { + return Arrays.stream(Defaults.values()).map(Defaults::getFlag).collect(Collectors.toSet()); + } + + /** + * Get the default set of flags + * + * @return The default set of flags + * @deprecated Use {@link #getDefaults()} instead + */ + @Deprecated(since = "2.5") + public static Flag[] values() { + return getDefaults().toArray(new Flag[0]); + } + + /** + * Compare this flag to another + * + * @param other the object to be compared. + * @return A string comparison integer-compare of the flag names + */ + @Override + public int compareTo(@NotNull Flag other) { + return getName().compareTo(other.getName()); + } + + /** + * The default set of flag IDs to allowed operations + */ + public enum Defaults { + EXPLOSION_DAMAGE( + Operation.Type.EXPLOSION_DAMAGE_TERRAIN, + Operation.Type.EXPLOSION_DAMAGE_ENTITY + ), + FIRE_DAMAGE( + Operation.Type.FIRE_SPREAD, + Operation.Type.FIRE_BURN + ), + MOB_GRIEFING( + Operation.Type.MONSTER_DAMAGE_TERRAIN + ), + MONSTER_SPAWNING( + Operation.Type.MONSTER_SPAWN, + Operation.Type.PLAYER_DAMAGE_MONSTER + ), + PUBLIC_BUILD_ACCESS( + Operation.Type.BLOCK_BREAK, + Operation.Type.BLOCK_PLACE, + Operation.Type.CONTAINER_OPEN, + Operation.Type.FARM_BLOCK_PLACE, + Operation.Type.FARM_BLOCK_INTERACT, + Operation.Type.FILL_BUCKET, + Operation.Type.EMPTY_BUCKET, + Operation.Type.BREAK_HANGING_ENTITY, + Operation.Type.PLACE_HANGING_ENTITY, + Operation.Type.BLOCK_INTERACT, + Operation.Type.ENTITY_INTERACT, + Operation.Type.REDSTONE_INTERACT, + Operation.Type.USE_SPAWN_EGG, + Operation.Type.PLAYER_DAMAGE_MONSTER, + Operation.Type.PLAYER_DAMAGE_PERSISTENT_ENTITY, + Operation.Type.PLAYER_DAMAGE_ENTITY + ), + PUBLIC_CONTAINER_ACCESS( + Operation.Type.CONTAINER_OPEN + ), + PUBLIC_FARM_ACCESS( + Operation.Type.BLOCK_INTERACT, + Operation.Type.FARM_BLOCK_BREAK, + Operation.Type.FARM_BLOCK_PLACE, + Operation.Type.FARM_BLOCK_INTERACT, + Operation.Type.PLAYER_DAMAGE_ENTITY + ), + PUBLIC_INTERACT_ACCESS( + Operation.Type.BLOCK_INTERACT, + Operation.Type.ENTITY_INTERACT, + Operation.Type.REDSTONE_INTERACT + ), + PVP( + Operation.Type.PLAYER_DAMAGE_PLAYER + ); + + private final Operation.Type[] allowedOperations; + + Defaults(@NotNull Operation.Type... allowedOperations) { + this.allowedOperations = allowedOperations; + } + + /** + * Get the flag for this default + * + * @return The flag + */ + @NotNull + public Flag getFlag() { + return Flag.of(name().toLowerCase(), Set.of(allowedOperations)); + } + + /** + * Get a default flag from the given name + * + * @param id The name of the flag + * @return The flag, or an empty optional if not found + */ + public static Optional fromId(@NotNull String id) { + return Arrays.stream(values()) + .filter(flag -> flag.name().equalsIgnoreCase(id)) + .map(Defaults::getFlag).findFirst(); + } + + /** + * Get the name of this default flag (lowercase) + * + * @return The name of this default flag + */ + @NotNull + public String getName() { + return name().toLowerCase(Locale.ENGLISH); + } + } } diff --git a/common/src/main/java/net/william278/husktowns/claim/Rules.java b/common/src/main/java/net/william278/husktowns/claim/Rules.java index 45016b19..c6e2e3f0 100644 --- a/common/src/main/java/net/william278/husktowns/claim/Rules.java +++ b/common/src/main/java/net/william278/husktowns/claim/Rules.java @@ -14,10 +14,14 @@ package net.william278.husktowns.claim; import com.google.gson.annotations.Expose; +import net.william278.husktowns.config.Flags; import net.william278.husktowns.listener.Operation; import org.jetbrains.annotations.NotNull; +import java.util.LinkedHashMap; +import java.util.Locale; import java.util.Map; +import java.util.stream.Collectors; /** * Claim rules, defining what players can do in a claim @@ -25,12 +29,24 @@ public class Rules { @Expose - private Map flags; + private Map flags; - private Rules(@NotNull Map flags) { + private Rules(@NotNull Map flags) { this.flags = flags; } + /** + * Create a new Rules instance from a map of flag names to their respective values + * * + * + * @param rules the map of flag IDs to create from + * @return the new Rules instance + */ + @NotNull + public static Rules from(@NotNull Map rules) { + return new Rules(rules); + } + /** * Create a new Rules instance from a {@link Flag}-value map * @@ -39,7 +55,13 @@ private Rules(@NotNull Map flags) { */ @NotNull public static Rules of(@NotNull Map rules) { - return new Rules(rules); + return new Rules(rules.entrySet().stream() + .collect(Collectors.toMap( + f -> f.getKey().getName().toLowerCase(Locale.ENGLISH), + Map.Entry::getValue, + (a, b) -> b, + LinkedHashMap::new + ))); } @SuppressWarnings("unused") @@ -53,34 +75,45 @@ private Rules() { * @param value the value to set the flag to */ public void setFlag(@NotNull Flag flag, boolean value) { - if (flags.containsKey(flag)) { - flags.replace(flag, value); + if (flags.containsKey(flag.getName())) { + flags.replace(flag.getName(), value); } else { - flags.put(flag, value); + flags.put(flag.getName(), value); } } /** * Get the map of {@link Flag}s to their respective values + *

+ * Any flags set in the town's rules that aren't defined on this server will be ignored. * * @return the map of flags to their respective values */ @NotNull - public Map getFlagMap() { - return flags; + public Map getFlagMap(@NotNull Flags flagConfig) { + return flags.entrySet().stream() + .filter(f -> flagConfig.getFlag(f.getKey()).isPresent()) + .collect(Collectors.toMap( + f -> flagConfig.getFlag(f.getKey()).orElseThrow(), + Map.Entry::getValue, + (a, b) -> b, + LinkedHashMap::new + )); } /** * Whether, for the given operation, the flag rules set indicate it should be cancelled * - * @param type the operation type that is being performed in a region governed by these rules - * @return Whether the operation should be cancelled: + * @param type the operation type that is being performed in a region governed by these rules + * @param flagConfig the flag configuration to use + * @return Whether the operation should be canceled: *

* {@code true} if no flags have been set to {@code true} that permit the operation; {@code false} otherwise */ - public boolean cancelOperation(@NotNull Operation.Type type) { - return flags.entrySet().stream() + public boolean cancelOperation(@NotNull Operation.Type type, @NotNull Flags flagConfig) { + return getFlagMap(flagConfig).entrySet().stream() .filter(Map.Entry::getValue) .noneMatch(entry -> entry.getKey().isOperationAllowed(type)); } + } diff --git a/common/src/main/java/net/william278/husktowns/command/TownCommand.java b/common/src/main/java/net/william278/husktowns/command/TownCommand.java index 5b2457aa..ffb4d739 100644 --- a/common/src/main/java/net/william278/husktowns/command/TownCommand.java +++ b/common/src/main/java/net/william278/husktowns/command/TownCommand.java @@ -618,7 +618,7 @@ protected RulesCommand(@NotNull Command parent, @NotNull HuskTowns plugin) { @Override public void execute(@NotNull CommandUser executor, @NotNull String[] args) { final OnlineUser user = (OnlineUser) executor; - final Optional flag = parseStringArg(args, 0).flatMap(Flag::fromId); + final Optional flag = parseStringArg(args, 0).flatMap(id -> plugin.getFlags().getFlag(id)); final Optional claimType = parseStringArg(args, 1).flatMap(Claim.Type::fromId); final Optional value = parseStringArg(args, 2).map(Boolean::parseBoolean); final boolean showMenu = parseStringArg(args, 3).map(arg -> arg.equals("-m")).orElse(false); @@ -633,8 +633,8 @@ public void execute(@NotNull CommandUser executor, @NotNull String[] args) { @Nullable public List suggest(@NotNull CommandUser user, @NotNull String[] args) { return switch (args.length) { - case 0, 1 -> filter(Arrays.stream(Flag.values()) - .map(Flag::name) + case 0, 1 -> filter(plugin.getFlags().getFlagSet().stream() + .map(Flag::getName) .map(String::toLowerCase) .collect(Collectors.toList()), args); case 2 -> filter(Arrays.stream(Claim.Type.values()) diff --git a/common/src/main/java/net/william278/husktowns/config/Flags.java b/common/src/main/java/net/william278/husktowns/config/Flags.java new file mode 100644 index 00000000..f3baeaa5 --- /dev/null +++ b/common/src/main/java/net/william278/husktowns/config/Flags.java @@ -0,0 +1,90 @@ +/* + * This file is part of HuskTowns by William278. Do not redistribute! + * + * Copyright (c) William278 + * All rights reserved. + * + * This source code is provided as reference to licensed individuals that have purchased the HuskTowns + * plugin once from any of the official sources it is provided. The availability of this code does + * not grant you the rights to modify, re-distribute, compile or redistribute this source code or + * "plugin" outside this intended purpose. This license does not cover libraries developed by third + * parties that are utilised in the plugin. + */ + +package net.william278.husktowns.config; + +import net.william278.annotaml.YamlComment; +import net.william278.annotaml.YamlFile; +import net.william278.annotaml.YamlKey; +import net.william278.husktowns.claim.Flag; +import net.william278.husktowns.listener.Operation; +import org.jetbrains.annotations.NotNull; + +import java.util.*; +import java.util.stream.Collectors; + +@YamlFile(header = """ + ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ + ┃ HuskTowns Flags Config ┃ + ┃ Developed by William278 ┃ + ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ + ┣╸ This file is for configuring flags. Flag IDs map to a list of permitted operations. + ┗╸ Config Help: https://william278.net/docs/husktowns/config-files""") +public class Flags { + + @YamlComment("A map of flag IDs to operations that flag permits." + + "Display names of flags correspond to a \"town_rule_name_\" locale in your messages file.") + @YamlKey("flags") + public Map> flags = new LinkedHashMap<>( + Flag.getDefaults().stream().collect(Collectors.toMap( + Flag::getName, + flag -> flag.getAllowedOperations().stream().map(Enum::name).collect(Collectors.toList()) + )) + ); + + private Flags() { + } + + /** + * Get the set of {@link Flag flags} being used by the plugin + * + * @return the set of flags + */ + @NotNull + public Set getFlagSet() { + final Set flagSet = new LinkedHashSet<>(); + for (Map.Entry> entry : flags.entrySet()) { + flagSet.add(Flag.of( + entry.getKey(), + entry.getValue().stream() + .map(a -> Operation.Type.fromId(a).orElseThrow( + () -> new IllegalArgumentException("Invalid operation type in flags config: " + a))) + .collect(Collectors.toUnmodifiableSet()) + )); + } + return flagSet; + } + + /** + * Set the set of {@link Flag flags} being used by the plugin + * + * @param flags the set of flags to use + */ + public void setFlags(@NotNull Set flags) { + this.flags = flags.stream().collect(Collectors.toMap( + Flag::getName, + flag -> flag.getAllowedOperations().stream().map(Enum::name).collect(Collectors.toList()) + )); + } + + /** + * Lookup a {@link Flag} by its ID + * + * @param flagId the ID of the flag to lookup + * @return the flag, if found + */ + public Optional getFlag(@NotNull String flagId) { + return getFlagSet().stream().filter(flag -> flag.getName().equalsIgnoreCase(flagId)).findFirst(); + } + +} diff --git a/common/src/main/java/net/william278/husktowns/config/Presets.java b/common/src/main/java/net/william278/husktowns/config/Presets.java index ef7b5ccf..04b2e9af 100644 --- a/common/src/main/java/net/william278/husktowns/config/Presets.java +++ b/common/src/main/java/net/william278/husktowns/config/Presets.java @@ -37,85 +37,85 @@ public class Presets { @YamlComment("Rules for the wilderness (claimable chunks outside of towns)") @YamlKey("wilderness_rules") private Map wildernessRules = Map.of( - Flag.EXPLOSION_DAMAGE.name().toLowerCase(), true, - Flag.FIRE_DAMAGE.name().toLowerCase(), true, - Flag.MOB_GRIEFING.name().toLowerCase(), true, - Flag.MONSTER_SPAWNING.name().toLowerCase(), true, - Flag.PUBLIC_BUILD_ACCESS.name().toLowerCase(), true, - Flag.PUBLIC_CONTAINER_ACCESS.name().toLowerCase(), true, - Flag.PUBLIC_FARM_ACCESS.name().toLowerCase(), true, - Flag.PUBLIC_INTERACT_ACCESS.name().toLowerCase(), true, - Flag.PVP.name().toLowerCase(), true + Flag.Defaults.EXPLOSION_DAMAGE.getName(), true, + Flag.Defaults.FIRE_DAMAGE.getName(), true, + Flag.Defaults.MOB_GRIEFING.getName(), true, + Flag.Defaults.MONSTER_SPAWNING.getName(), true, + Flag.Defaults.PUBLIC_BUILD_ACCESS.getName(), true, + Flag.Defaults.PUBLIC_CONTAINER_ACCESS.getName(), true, + Flag.Defaults.PUBLIC_FARM_ACCESS.getName(), true, + Flag.Defaults.PUBLIC_INTERACT_ACCESS.getName(), true, + Flag.Defaults.PVP.getName(), true ); @YamlComment("Rules for admin claims (created with /admintown claim)") @YamlKey("admin_claim_rules") private Map adminClaimRules = Map.of( - Flag.EXPLOSION_DAMAGE.name().toLowerCase(), false, - Flag.FIRE_DAMAGE.name().toLowerCase(), false, - Flag.MOB_GRIEFING.name().toLowerCase(), false, - Flag.MONSTER_SPAWNING.name().toLowerCase(), false, - Flag.PUBLIC_BUILD_ACCESS.name().toLowerCase(), false, - Flag.PUBLIC_CONTAINER_ACCESS.name().toLowerCase(), false, - Flag.PUBLIC_FARM_ACCESS.name().toLowerCase(), true, - Flag.PUBLIC_INTERACT_ACCESS.name().toLowerCase(), true, - Flag.PVP.name().toLowerCase(), false + Flag.Defaults.EXPLOSION_DAMAGE.getName(), false, + Flag.Defaults.FIRE_DAMAGE.getName(), false, + Flag.Defaults.MOB_GRIEFING.getName(), false, + Flag.Defaults.MONSTER_SPAWNING.getName(), false, + Flag.Defaults.PUBLIC_BUILD_ACCESS.getName(), false, + Flag.Defaults.PUBLIC_CONTAINER_ACCESS.getName(), false, + Flag.Defaults.PUBLIC_FARM_ACCESS.getName(), true, + Flag.Defaults.PUBLIC_INTERACT_ACCESS.getName(), true, + Flag.Defaults.PVP.getName(), false ); @YamlComment("Rules for worlds where claims cannot be created (as defined in unclaimable_worlds)") @YamlKey("unclaimable_world_rules") private Map unclaimableWorldRules = Map.of( - Flag.EXPLOSION_DAMAGE.name().toLowerCase(), true, - Flag.FIRE_DAMAGE.name().toLowerCase(), true, - Flag.MOB_GRIEFING.name().toLowerCase(), true, - Flag.MONSTER_SPAWNING.name().toLowerCase(), true, - Flag.PUBLIC_BUILD_ACCESS.name().toLowerCase(), true, - Flag.PUBLIC_CONTAINER_ACCESS.name().toLowerCase(), true, - Flag.PUBLIC_FARM_ACCESS.name().toLowerCase(), true, - Flag.PUBLIC_INTERACT_ACCESS.name().toLowerCase(), true, - Flag.PVP.name().toLowerCase(), true + Flag.Defaults.EXPLOSION_DAMAGE.getName(), true, + Flag.Defaults.FIRE_DAMAGE.getName(), true, + Flag.Defaults.MOB_GRIEFING.getName(), true, + Flag.Defaults.MONSTER_SPAWNING.getName(), true, + Flag.Defaults.PUBLIC_BUILD_ACCESS.getName(), true, + Flag.Defaults.PUBLIC_CONTAINER_ACCESS.getName(), true, + Flag.Defaults.PUBLIC_FARM_ACCESS.getName(), true, + Flag.Defaults.PUBLIC_INTERACT_ACCESS.getName(), true, + Flag.Defaults.PVP.getName(), true ); @YamlComment("Default rules for normal claims") @YamlKey("default_rules.claims") private Map claimRules = Map.of( - Flag.EXPLOSION_DAMAGE.name().toLowerCase(), false, - Flag.FIRE_DAMAGE.name().toLowerCase(), false, - Flag.MOB_GRIEFING.name().toLowerCase(), false, - Flag.MONSTER_SPAWNING.name().toLowerCase(), true, - Flag.PUBLIC_BUILD_ACCESS.name().toLowerCase(), false, - Flag.PUBLIC_CONTAINER_ACCESS.name().toLowerCase(), false, - Flag.PUBLIC_FARM_ACCESS.name().toLowerCase(), false, - Flag.PUBLIC_INTERACT_ACCESS.name().toLowerCase(), false, - Flag.PVP.name().toLowerCase(), false + Flag.Defaults.EXPLOSION_DAMAGE.getName(), false, + Flag.Defaults.FIRE_DAMAGE.getName(), false, + Flag.Defaults.MOB_GRIEFING.getName(), false, + Flag.Defaults.MONSTER_SPAWNING.getName(), true, + Flag.Defaults.PUBLIC_BUILD_ACCESS.getName(), false, + Flag.Defaults.PUBLIC_CONTAINER_ACCESS.getName(), false, + Flag.Defaults.PUBLIC_FARM_ACCESS.getName(), false, + Flag.Defaults.PUBLIC_INTERACT_ACCESS.getName(), false, + Flag.Defaults.PVP.getName(), false ); @YamlComment("Default rules for farm claims") @YamlKey("default_rules.farms") private Map farmRules = Map.of( - Flag.EXPLOSION_DAMAGE.name().toLowerCase(), false, - Flag.FIRE_DAMAGE.name().toLowerCase(), false, - Flag.MOB_GRIEFING.name().toLowerCase(), false, - Flag.MONSTER_SPAWNING.name().toLowerCase(), true, - Flag.PUBLIC_BUILD_ACCESS.name().toLowerCase(), false, - Flag.PUBLIC_CONTAINER_ACCESS.name().toLowerCase(), false, - Flag.PUBLIC_FARM_ACCESS.name().toLowerCase(), true, - Flag.PUBLIC_INTERACT_ACCESS.name().toLowerCase(), false, - Flag.PVP.name().toLowerCase(), false + Flag.Defaults.EXPLOSION_DAMAGE.getName(), false, + Flag.Defaults.FIRE_DAMAGE.getName(), false, + Flag.Defaults.MOB_GRIEFING.getName(), false, + Flag.Defaults.MONSTER_SPAWNING.getName(), true, + Flag.Defaults.PUBLIC_BUILD_ACCESS.getName(), false, + Flag.Defaults.PUBLIC_CONTAINER_ACCESS.getName(), false, + Flag.Defaults.PUBLIC_FARM_ACCESS.getName(), true, + Flag.Defaults.PUBLIC_INTERACT_ACCESS.getName(), false, + Flag.Defaults.PVP.getName(), false ); @YamlComment("Default rules for plot claims") @YamlKey("default_rules.plots") private Map plotRules = Map.of( - Flag.EXPLOSION_DAMAGE.name().toLowerCase(), false, - Flag.FIRE_DAMAGE.name().toLowerCase(), false, - Flag.MOB_GRIEFING.name().toLowerCase(), false, - Flag.MONSTER_SPAWNING.name().toLowerCase(), false, - Flag.PUBLIC_BUILD_ACCESS.name().toLowerCase(), false, - Flag.PUBLIC_CONTAINER_ACCESS.name().toLowerCase(), false, - Flag.PUBLIC_FARM_ACCESS.name().toLowerCase(), false, - Flag.PUBLIC_INTERACT_ACCESS.name().toLowerCase(), false, - Flag.PVP.name().toLowerCase(), false + Flag.Defaults.EXPLOSION_DAMAGE.getName(), false, + Flag.Defaults.FIRE_DAMAGE.getName(), false, + Flag.Defaults.MOB_GRIEFING.getName(), false, + Flag.Defaults.MONSTER_SPAWNING.getName(), false, + Flag.Defaults.PUBLIC_BUILD_ACCESS.getName(), false, + Flag.Defaults.PUBLIC_CONTAINER_ACCESS.getName(), false, + Flag.Defaults.PUBLIC_FARM_ACCESS.getName(), false, + Flag.Defaults.PUBLIC_INTERACT_ACCESS.getName(), false, + Flag.Defaults.PVP.getName(), false ); @SuppressWarnings("unused") @@ -128,31 +128,26 @@ public Map getDefaultClaimRules() { defaultRules.put(Claim.Type.CLAIM, claimRules); defaultRules.put(Claim.Type.FARM, farmRules); defaultRules.put(Claim.Type.PLOT, plotRules); - return defaultRules.entrySet().stream().collect( - Collectors.toMap(Map.Entry::getKey, entry -> Rules.of(entry.getValue().entrySet().stream() - .collect(Collectors.toMap( - flagEntry -> Flag.fromId(flagEntry.getKey()).orElseThrow(), - Map.Entry::getValue)) - ) + return defaultRules.entrySet().stream() + .collect(Collectors.toMap( + Map.Entry::getKey, + entry -> Rules.from(entry.getValue()) )); } @NotNull public Rules getUnclaimableWorldRules() { - return Rules.of(unclaimableWorldRules.entrySet().stream() - .collect(Collectors.toMap(e -> Flag.fromId(e.getKey()).orElseThrow(), Map.Entry::getValue))); + return Rules.from(unclaimableWorldRules); } @NotNull public Rules getWildernessRules() { - return Rules.of(wildernessRules.entrySet().stream() - .collect(Collectors.toMap(e -> Flag.fromId(e.getKey()).orElseThrow(), Map.Entry::getValue))); + return Rules.from(wildernessRules); } @NotNull public Rules getAdminClaimRules() { - return Rules.of(adminClaimRules.entrySet().stream() - .collect(Collectors.toMap(e -> Flag.fromId(e.getKey()).orElseThrow(), Map.Entry::getValue))); + return Rules.from(adminClaimRules); } } diff --git a/common/src/main/java/net/william278/husktowns/listener/Operation.java b/common/src/main/java/net/william278/husktowns/listener/Operation.java index 017412d2..3cabcb76 100644 --- a/common/src/main/java/net/william278/husktowns/listener/Operation.java +++ b/common/src/main/java/net/william278/husktowns/listener/Operation.java @@ -18,6 +18,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.Arrays; import java.util.Optional; /** @@ -266,6 +267,16 @@ private boolean isSilent() { return this.silent; } + /** + * Get an operation type from the given name + * + * @param id The name of the operation type + * @return The operation type, or an empty optional if not found + */ + public static Optional fromId(@NotNull String id) { + return Arrays.stream(values()).filter(flag -> flag.name().equalsIgnoreCase(id)).findFirst(); + } + } } diff --git a/common/src/main/java/net/william278/husktowns/listener/OperationHandler.java b/common/src/main/java/net/william278/husktowns/listener/OperationHandler.java index 8c4d9613..4162cd97 100644 --- a/common/src/main/java/net/william278/husktowns/listener/OperationHandler.java +++ b/common/src/main/java/net/william278/husktowns/listener/OperationHandler.java @@ -52,7 +52,7 @@ public boolean cancelOperation(@NotNull Operation operation) { } final Optional world = plugin.getClaimWorld(operation.getPosition().getWorld()); if (world.isEmpty()) { - if (plugin.getRulePresets().getUnclaimableWorldRules().cancelOperation(operation.getType())) { + if (plugin.getRulePresets().getUnclaimableWorldRules().cancelOperation(operation.getType(), plugin.getFlags())) { if (operation.isVerbose() && operation.getUser().isPresent()) { plugin.getLocales().getLocale("operation_cancelled") .ifPresent(operation.getUser().get()::sendMessage); @@ -61,7 +61,7 @@ public boolean cancelOperation(@NotNull Operation operation) { } return false; } - if (plugin.getRulePresets().getWildernessRules().cancelOperation(operation.getType())) { + if (plugin.getRulePresets().getWildernessRules().cancelOperation(operation.getType(), plugin.getFlags())) { if (operation.isVerbose() && operation.getUser().isPresent()) { plugin.getLocales().getLocale("operation_cancelled") .ifPresent(operation.getUser().get()::sendMessage); @@ -84,7 +84,7 @@ private boolean cancelOperation(@NotNull Operation operation, @NotNull TownClaim final Claim claim = townClaim.claim(); // If the operation is not allowed by the claim flags - if (town.getRules().get(claim.getType()).cancelOperation(operation.getType())) { + if (town.getRules().get(claim.getType()).cancelOperation(operation.getType(), plugin.getFlags())) { if (optionalUser.isEmpty()) { return true; } diff --git a/common/src/main/java/net/william278/husktowns/manager/ClaimsManager.java b/common/src/main/java/net/william278/husktowns/manager/ClaimsManager.java index 51e2c87f..7b218e5f 100644 --- a/common/src/main/java/net/william278/husktowns/manager/ClaimsManager.java +++ b/common/src/main/java/net/william278/husktowns/manager/ClaimsManager.java @@ -112,7 +112,7 @@ public void createClaim(@NotNull OnlineUser user, @NotNull World world, @NotNull public void createClaimData(@NotNull OnlineUser user, @NotNull TownClaim claim, @NotNull World world) throws IllegalArgumentException { final ClaimWorld claimWorld = plugin.getClaimWorld(world) - .orElseThrow(() -> new IllegalArgumentException("World is not claimable")); + .orElseThrow(() -> new IllegalArgumentException("World \"" + world.getName() + "\" is not claimable")); if (claim.isAdminClaim(plugin)) { claimWorld.addAdminClaim(claim.claim()); } else { @@ -234,7 +234,7 @@ public void deleteAllClaims(@NotNull OnlineUser user, @NotNull Town town) { public void deleteClaimData(@NotNull OnlineUser user, @NotNull TownClaim claim, @NotNull World world) throws IllegalArgumentException { final ClaimWorld claimWorld = plugin.getClaimWorld(world) - .orElseThrow(() -> new IllegalArgumentException("World is not claimable")); + .orElseThrow(() -> new IllegalArgumentException("World \"" + world.getName() + "\" is not claimable")); if (claim.isAdminClaim(plugin)) { claimWorld.removeAdminClaim(claim.claim().getChunk()); } else { diff --git a/common/src/main/java/net/william278/husktowns/manager/TownsManager.java b/common/src/main/java/net/william278/husktowns/manager/TownsManager.java index 8031c5f7..ec589153 100644 --- a/common/src/main/java/net/william278/husktowns/manager/TownsManager.java +++ b/common/src/main/java/net/william278/husktowns/manager/TownsManager.java @@ -835,8 +835,8 @@ public void setFlagRule(@NotNull OnlineUser user, @NotNull Flag flag, @NotNull C plugin.getManager().memberEditTown(user, Privilege.SET_RULES, (member -> { final Town town = member.town(); town.getRules().get(type).setFlag(flag, value); - town.getLog().log(Action.of(user, Action.Type.SET_FLAG_RULE, flag.name().toLowerCase() + ": " + value)); - plugin.getLocales().getLocale("town_flag_set", flag.name().toLowerCase(), Boolean.toString(value), + town.getLog().log(Action.of(user, Action.Type.SET_FLAG_RULE, flag.getName().toLowerCase() + ": " + value)); + plugin.getLocales().getLocale("town_flag_set", flag.getName().toLowerCase(), Boolean.toString(value), type.name().toLowerCase()).ifPresent(user::sendMessage); if (showMenu) { showRulesConfig(user); diff --git a/common/src/main/java/net/william278/husktowns/menu/RulesConfig.java b/common/src/main/java/net/william278/husktowns/menu/RulesConfig.java index b9f7c9d3..6382b9d6 100644 --- a/common/src/main/java/net/william278/husktowns/menu/RulesConfig.java +++ b/common/src/main/java/net/william278/husktowns/menu/RulesConfig.java @@ -65,7 +65,7 @@ private Component getTitle() { private Component getRules() { final Map> rules = new TreeMap<>(); for (Map.Entry entry : town.getRules().entrySet()) { - for (Map.Entry flagEntry : entry.getValue().getFlagMap().entrySet()) { + for (Map.Entry flagEntry : entry.getValue().getFlagMap(plugin.getFlags()).entrySet()) { if (!rules.containsKey(flagEntry.getKey())) { rules.put(flagEntry.getKey(), new TreeMap<>()); } @@ -87,9 +87,10 @@ private Component getRuleLine(@NotNull Map.Entry> } } - final String flagName = entry.getKey().name().toLowerCase(); + final String flagName = entry.getKey().getName().toLowerCase(); return line.append(plugin.getLocales().getLocale("town_rules_config_flag_name", - plugin.getLocales().getRawLocale(("town_rule_name_" + flagName)).orElse(flagName)) + plugin.getLocales().getRawLocale(("town_rule_name_" + flagName)) + .orElse(flagName.replaceAll("_", " "))) .map(MineDown::toComponent).orElse(Component.empty())); } @@ -99,7 +100,7 @@ private Component getRuleFlag(@NotNull Flag flag, @NotNull Claim.Type type, bool .map(MineDown::toComponent).orElse(Component.empty()) .hoverEvent(plugin.getLocales().getLocale("town_rules_config_flag_hover", type.name().toLowerCase()).map(MineDown::toComponent).orElse(Component.empty())) - .clickEvent(ClickEvent.runCommand("/husktowns:town rules " + flag.name().toLowerCase() + " " + .clickEvent(ClickEvent.runCommand("/husktowns:town rules " + flag.getName().toLowerCase() + " " + type.name().toLowerCase() + " " + !value + " -m")); } diff --git a/common/src/main/java/net/william278/husktowns/migrator/LegacyMigrator.java b/common/src/main/java/net/william278/husktowns/migrator/LegacyMigrator.java index a3805b2b..0e5f432d 100644 --- a/common/src/main/java/net/william278/husktowns/migrator/LegacyMigrator.java +++ b/common/src/main/java/net/william278/husktowns/migrator/LegacyMigrator.java @@ -179,16 +179,16 @@ worldName, determineEnvironment(worldName)), final int townId = resultSet.getInt("town_id"); final Town town = towns.stream().filter(t -> t.getId() == townId).findFirst().orElseThrow(); final Claim.Type type = Claim.Type.values()[resultSet.getInt("chunk_type")]; - town.getRules().put(type, Rules.of(Map.of( - Flag.EXPLOSION_DAMAGE, resultSet.getBoolean("explosion_damage"), - Flag.FIRE_DAMAGE, resultSet.getBoolean("fire_damage"), - Flag.MOB_GRIEFING, resultSet.getBoolean("mob_griefing"), - Flag.MONSTER_SPAWNING, resultSet.getBoolean("monster_spawning"), - Flag.PVP, resultSet.getBoolean("pvp"), - Flag.PUBLIC_INTERACT_ACCESS, resultSet.getBoolean("public_interact_access"), - Flag.PUBLIC_CONTAINER_ACCESS, resultSet.getBoolean("public_container_access"), - Flag.PUBLIC_BUILD_ACCESS, resultSet.getBoolean("public_build_access"), - Flag.PUBLIC_FARM_ACCESS, resultSet.getBoolean("public_farm_access")))); + town.getRules().put(type, Rules.from(Map.of( + Flag.Defaults.EXPLOSION_DAMAGE.getName(), resultSet.getBoolean("explosion_damage"), + Flag.Defaults.FIRE_DAMAGE.getName(), resultSet.getBoolean("fire_damage"), + Flag.Defaults.MOB_GRIEFING.getName(), resultSet.getBoolean("mob_griefing"), + Flag.Defaults.MONSTER_SPAWNING.getName(), resultSet.getBoolean("monster_spawning"), + Flag.Defaults.PVP.getName(), resultSet.getBoolean("pvp"), + Flag.Defaults.PUBLIC_INTERACT_ACCESS.getName(), resultSet.getBoolean("public_interact_access"), + Flag.Defaults.PUBLIC_CONTAINER_ACCESS.getName(), resultSet.getBoolean("public_container_access"), + Flag.Defaults.PUBLIC_BUILD_ACCESS.getName(), resultSet.getBoolean("public_build_access"), + Flag.Defaults.PUBLIC_FARM_ACCESS.getName(), resultSet.getBoolean("public_farm_access")))); } } } diff --git a/common/src/main/java/net/william278/husktowns/town/Town.java b/common/src/main/java/net/william278/husktowns/town/Town.java index 58dc5751..b7e9257c 100644 --- a/common/src/main/java/net/william278/husktowns/town/Town.java +++ b/common/src/main/java/net/william278/husktowns/town/Town.java @@ -142,8 +142,8 @@ public static Town of(int id, @NotNull String name, @Nullable String bio, @Nulla @NotNull public static Town create(@NotNull String name, @NotNull User creator, @NotNull HuskTowns plugin) { return of(0, name, null, null, null, new HashMap<>(), - plugin.getRulePresets().getDefaultClaimRules(), 0, BigDecimal.ZERO, 1, null, - Log.newTownLog(creator), Town.getRandomColor(name), new HashMap<>(), new HashMap<>()); + plugin.getRulePresets().getDefaultClaimRules(), 0, BigDecimal.ZERO, 1, + null, Log.newTownLog(creator), Town.getRandomColor(name), new HashMap<>(), new HashMap<>()); } /** @@ -295,7 +295,7 @@ public UUID getMayor() { return members.entrySet().stream() .max(Map.Entry.comparingByValue()) .map(Map.Entry::getKey) - .orElseThrow(() -> new IllegalStateException("Town has no mayor")); + .orElseThrow(() -> new IllegalStateException("Town \"" + getName() + "\" has no mayor")); } /** @@ -316,7 +316,7 @@ public void addMember(@NotNull UUID uuid, @NotNull Role role) { */ public void removeMember(@NotNull UUID uuid) throws IllegalArgumentException { if (getMayor().equals(uuid)) { - throw new IllegalArgumentException("Cannot remove the mayor of the town"); + throw new IllegalArgumentException("Cannot remove the mayor of the town \"" + getName() + "\""); } this.members.remove(uuid); } @@ -595,7 +595,7 @@ public Map getMetadataTags() { return metadata.entrySet().stream().collect(Collectors .toMap(entry -> Key.key(entry.getKey()), Map.Entry::getValue)); } catch (InvalidKeyException e) { - throw new IllegalStateException("Invalid key in town metadata", e); + throw new IllegalStateException("Invalid key in town \"" + getName() + "\" metadata", e); } } diff --git a/docs/Config-Files.md b/docs/Config-Files.md index 675c8754..cd6316a0 100644 --- a/docs/Config-Files.md +++ b/docs/Config-Files.md @@ -3,6 +3,7 @@ This page contains the configuration structure for HuskTowns. ## Configuration structure 📁 `plugins/HuskTowns/` - 📄 `config.yml`: General plugin configuration + - 📄 `flags.yml`: Flag definition configuration - 📄 `levels.yml`: Town level requirements and limits - 📄 [`roles.yml`](town-roles): Town role hierarchy (see [[Town Roles]]) - 📄 `rules.yml`: Default town/wilderness claim/flag rules @@ -139,6 +140,63 @@ towns: ``` +

+flags.yml + +```yaml +# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +# ┃ HuskTowns Flags Config ┃ +# ┃ Developed by William278 ┃ +# ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ +# ┣╸ This file is for configuring flags. Flag IDs map to a list of permitted operations. +# ┗╸ Config Help: https://william278.net/docs/husktowns/config-files +# A map of flag IDs to allowed operations +flags: + public_container_access: + - CONTAINER_OPEN + fire_damage: + - FIRE_SPREAD + - FIRE_BURN + public_farm_access: + - BLOCK_INTERACT + - FARM_BLOCK_PLACE + - FARM_BLOCK_INTERACT + - FARM_BLOCK_BREAK + - PLAYER_DAMAGE_ENTITY + public_build_access: + - CONTAINER_OPEN + - REDSTONE_INTERACT + - PLAYER_DAMAGE_ENTITY + - BLOCK_BREAK + - PLAYER_DAMAGE_MONSTER + - USE_SPAWN_EGG + - ENTITY_INTERACT + - BLOCK_PLACE + - PLAYER_DAMAGE_PERSISTENT_ENTITY + - BLOCK_INTERACT + - PLACE_HANGING_ENTITY + - FARM_BLOCK_PLACE + - FARM_BLOCK_INTERACT + - BREAK_HANGING_ENTITY + - EMPTY_BUCKET + - FILL_BUCKET + mob_griefing: + - MONSTER_DAMAGE_TERRAIN + explosion_damage: + - EXPLOSION_DAMAGE_TERRAIN + - EXPLOSION_DAMAGE_ENTITY + pvp: + - PLAYER_DAMAGE_PLAYER + monster_spawning: + - MONSTER_SPAWN + - PLAYER_DAMAGE_MONSTER + public_interact_access: + - BLOCK_INTERACT + - REDSTONE_INTERACT + - ENTITY_INTERACT +``` +
+
levels.yml diff --git a/docs/Legacy-Migration.md b/docs/Legacy-Migration.md index 70adfc5e..acd9d424 100644 --- a/docs/Legacy-Migration.md +++ b/docs/Legacy-Migration.md @@ -26,6 +26,7 @@ Please follow the steps below to upgrade from HuskTowns v1.8.2. If you're runnin 2. Under "roles" in the new file, add the list of privileges for each weight. If you don't wish to assign any privileges at a role level, instead of the list, enter `[]` after the colon 4. *If you customised the town flag defaults previously:* Open the newly generated `rules.yml` and your old v1 `config.yml` you copied over earlier side-by-side. 1. Fill in the default flag rules for each claim type, the wilderness, admin claims and unclaimable worlds as it is set up in your old config + 2. You can also edit `flags.yml` to customize the actions permitted for each flag 5. *If you are using cross-server mode*: Create a new file called `server.yml` and open your old v1 `config.yml` you copied earlier side-by-side 1. At the top of the file, type `name: ''`, replacing `` with the ID *name of this server* as it is defined in your old config. 6. Update your [`messages-xx-xx.yml`](config-files) file to your liking. Note you can't use your old file as the interfaces and system messages have been completely rewritten. diff --git a/gradle.properties b/gradle.properties index d9fe0525..28d1d904 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,7 +3,7 @@ javaVersion=16 org.gradle.jvmargs='-Dfile.encoding=UTF-8' org.gradle.daemon=true -plugin_version=2.4 +plugin_version=2.5 plugin_archive=husktowns plugin_description=Simple and elegant proxy-compatible Towny-style protection