From 8bd09a41f3b856ee343f3a10fb980d0ea47a15b3 Mon Sep 17 00:00:00 2001 From: Warrior <50800980+Warriorrrr@users.noreply.github.com> Date: Wed, 23 Oct 2024 20:35:31 +0200 Subject: [PATCH] Add OfflinePlayer and UUID support to Economy backend (#7425) * Vault offline player support * Add deprecated since javadocs. * Update config-migration version number. * Flip ternary right way around. * Remove the legacy debt account conversion. * Update admin command permission nodes to follow command structure->node pattern. Made depositall use the economyexecutor too. * Remove old comment that no longer applied. * Make economy provider sealed and fix trailing spaces * Change default npc uuid version * Fix TownyServerAccount initialization throwing NPE. * Add /ta eco info command. Takes town, nation, resident, and serveraccount as subcommands, and outputs the name, UUID, and balance of the EconomyHandler's Account. Fixes an issue with array index out of bounds when /ta eco was run without a sub command. Adds a HelpMenu for /ta eco and /ta eco info. * Add translations for convert command feedback * Set UUID in deprecated methods when available * Use economy executor for new command * Update default npc uuid version again * Re-modify npc uuids when converting * Don't endlessly suggest modern * Add help menu entry * Skip accounts with no balance during conversion * Deprecated unused Account#getBukkitWorld * Add debug messages to eco conversion * Cache constructed offline players * Fix config-migration.json * Add conversion progress messages. * Add online player warning before conversion. --------- Co-authored-by: LlmDl --- .../bukkit/config/ConfigNodes.java | 15 + .../config/migration/RunnableMigrations.java | 45 ++- .../bukkit/towny/TownyEconomyHandler.java | 282 ++++++++++++------ .../bukkit/towny/command/HelpMenu.java | 29 +- .../towny/command/TownyAdminCommand.java | 235 +++++++++++++-- .../towny/hooks/PluginIntegrations.java | 6 +- .../bukkit/towny/object/EconomyAccount.java | 21 +- .../bukkit/towny/object/Government.java | 3 +- .../bukkit/towny/object/Identifiable.java | 3 + .../bukkit/towny/object/Resident.java | 23 +- .../bukkit/towny/object/economy/Account.java | 163 ++++++++-- .../towny/object/economy/BankAccount.java | 23 +- .../object/economy/TownyServerAccount.java | 86 +++--- .../TownyServerAccountEconomyHandler.java | 23 -- .../economy/adapter/EconomyAdapter.java | 38 +-- .../adapter/ReserveEconomyAdapter.java | 79 ++++- .../economy/adapter/VaultEconomyAdapter.java | 82 +++-- .../economy/provider/EconomyProvider.java | 47 +++ .../provider/ReserveEconomyProvider.java | 51 ++++ .../provider/VaultEconomyProvider.java | 53 ++++ .../transaction/TransactionBuilder.java | 5 +- .../towny/permissions/PermissionNodes.java | 7 +- .../bukkit/towny/utils/MoneyUtil.java | 29 -- .../bukkit/towny/utils/SpawnUtil.java | 5 +- .../java/com/palmergames/util/JavaUtil.java | 24 ++ .../src/main/resources/config-migration.json | 9 + .../main/resources/debtAccountsConverted.txt | 6 - Towny/src/main/resources/lang/en-US.yml | 21 +- Towny/src/main/resources/plugin.yml | 12 +- .../com/palmergames/util/JavaUtilTests.java | 24 ++ 30 files changed, 1080 insertions(+), 369 deletions(-) delete mode 100644 Towny/src/main/java/com/palmergames/bukkit/towny/object/economy/TownyServerAccountEconomyHandler.java create mode 100644 Towny/src/main/java/com/palmergames/bukkit/towny/object/economy/provider/EconomyProvider.java create mode 100644 Towny/src/main/java/com/palmergames/bukkit/towny/object/economy/provider/ReserveEconomyProvider.java create mode 100644 Towny/src/main/java/com/palmergames/bukkit/towny/object/economy/provider/VaultEconomyProvider.java delete mode 100644 Towny/src/main/resources/debtAccountsConverted.txt create mode 100644 Towny/src/test/java/com/palmergames/util/JavaUtilTests.java diff --git a/Towny/src/main/java/com/palmergames/bukkit/config/ConfigNodes.java b/Towny/src/main/java/com/palmergames/bukkit/config/ConfigNodes.java index 7d06cff0f93..6b7ed038b73 100644 --- a/Towny/src/main/java/com/palmergames/bukkit/config/ConfigNodes.java +++ b/Towny/src/main/java/com/palmergames/bukkit/config/ConfigNodes.java @@ -2970,6 +2970,21 @@ public enum ConfigNodes { "false", "", "# Does a conquered town which cannot pay the nation tax get deleted?"), + + ECO_ADVANCED_MODERN( + "economy.advanced.modern", + "true", + "", + "# When enabled, Towny will use UUIDs when communicating with your economy plugin.", + "# Most users will never have to touch this, but for existing servers this option will automatically be set to false.", + "# If this option is disabled and you wish to avoid losing data, use the `/townyadmin eco convert modern` command to convert."), + + ECO_ADVANCED_NPC_UUID_VERSION( + "economy.advanced.npc_uuid_version", + "-1", + "", + "# The UUID version to use for non-player accounts. This is used so that economy plugins can more easily differentiate between player and NPC accounts.", + "# The default is -1, which disables modifying npc uuids."), BANKHISTORY( "bank_history", diff --git a/Towny/src/main/java/com/palmergames/bukkit/config/migration/RunnableMigrations.java b/Towny/src/main/java/com/palmergames/bukkit/config/migration/RunnableMigrations.java index 0b1859ce579..a796997bc93 100644 --- a/Towny/src/main/java/com/palmergames/bukkit/config/migration/RunnableMigrations.java +++ b/Towny/src/main/java/com/palmergames/bukkit/config/migration/RunnableMigrations.java @@ -1,6 +1,8 @@ package com.palmergames.bukkit.config.migration; import com.palmergames.bukkit.config.CommentedConfiguration; +import com.palmergames.bukkit.config.ConfigNodes; +import com.palmergames.bukkit.towny.Towny; import com.palmergames.bukkit.towny.TownySettings; import com.palmergames.bukkit.util.BukkitTools; import com.palmergames.util.StringMgmt; @@ -9,6 +11,8 @@ import org.bukkit.entity.EntityType; import org.jetbrains.annotations.Nullable; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -18,17 +22,29 @@ import java.util.Map; import java.util.function.Consumer; import java.util.stream.Collectors; +import java.util.logging.Level; -@SuppressWarnings("FieldCanBeLocal") +@SuppressWarnings({"unused", "unchecked"}) public class RunnableMigrations { private final Map> BY_NAME = new HashMap<>(); public RunnableMigrations() { - BY_NAME.put("migrate_notifications", MIGRATE_NOTIFICATIONS); - BY_NAME.put("add_townblocktype_limits", ADD_TOWNBLOCKTYPE_LIMITS); - BY_NAME.put("convert_entity_class_names", CONVERT_ENTITY_CLASS_NAMES); - BY_NAME.put("add_milkable_animals_to_farm_plot", ADD_MILKABLE_ANIMALS); - BY_NAME.put("update_farm_blocks", UPDATE_FARM_BLOCKS); + try { + for (final Field field : this.getClass().getDeclaredFields()) { + if (!Modifier.isStatic(field.getModifiers())) + continue; + + field.setAccessible(true); + Object value = field.get(null); + + if (!(value instanceof Consumer)) + continue; + + BY_NAME.put(field.getName().toLowerCase(Locale.ROOT), (Consumer) value); + } + } catch (ReflectiveOperationException e) { + Towny.getPlugin().getLogger().log(Level.WARNING, "Exception occurred when getting runnable migrations", e); + } } @Nullable @@ -40,7 +56,7 @@ public boolean addMigration(String name, Consumer migrat return BY_NAME.putIfAbsent(name.toLowerCase(Locale.ROOT), migration) == null; } - private final Consumer MIGRATE_NOTIFICATIONS = config -> { + private static final Consumer MIGRATE_NOTIFICATIONS = config -> { if (Boolean.parseBoolean(config.getString("notification.notifications_appear_in_action_bar", "true"))) config.set("notification.notifications_appear_as", "action_bar"); else if (Boolean.parseBoolean(config.getString("notification.notifications_appear_on_bossbar", "false"))) @@ -49,13 +65,12 @@ else if (Boolean.parseBoolean(config.getString("notification.notifications_appea config.set("notification.notifications_appear_as", "chat"); }; - @SuppressWarnings("unchecked") - private final Consumer ADD_TOWNBLOCKTYPE_LIMITS = config -> { + private static final Consumer ADD_TOWNBLOCKTYPE_LIMITS = config -> { for (Map level : config.getMapList("levels.town_level")) ((Map) level).put("townBlockTypeLimits", new HashMap<>()); }; - private final Consumer CONVERT_ENTITY_CLASS_NAMES = config -> { + private static final Consumer CONVERT_ENTITY_CLASS_NAMES = config -> { List entities = new ArrayList<>(Arrays.asList(config.getString("new_world_settings.plot_management.wild_revert_on_mob_explosion.entities", "").split(","))); ListIterator iterator = entities.listIterator(); @@ -79,8 +94,7 @@ else if (Boolean.parseBoolean(config.getString("notification.notifications_appea config.set("new_world_settings.plot_management.wild_revert_on_mob_explosion.entities", String.join(",", entities)); }; - @SuppressWarnings("unchecked") - private final Consumer ADD_MILKABLE_ANIMALS = config -> { + private static final Consumer ADD_MILKABLE_ANIMALS_TO_FARM_PLOT = config -> { for (Map plotType : config.getMapList("townblocktypes.types")) { if (plotType.get("name").equals("farm")) { String allowedBlocks = (String) plotType.get("allowedBlocks"); @@ -93,8 +107,7 @@ else if (Boolean.parseBoolean(config.getString("notification.notifications_appea * 0.100.2.10 included a change which revamped the ItemLists used to construct the farm blocks, resulting in a more comprehensive list. * This runnable will add any blocks that older configs may have had which were missing from older configs. */ - @SuppressWarnings("unchecked") - private final Consumer UPDATE_FARM_BLOCKS = config -> { + private static final Consumer UPDATE_FARM_BLOCKS = config -> { for (Map plotType : config.getMapList("townblocktypes.types")) { if (!plotType.get("name").equals("farm")) continue; @@ -106,4 +119,8 @@ else if (Boolean.parseBoolean(config.getString("notification.notifications_appea ((Map) plotType).replace("allowedBlocks", rawBlocks + "," + StringMgmt.join(missingBlocks, ",")); } }; + + private static final Consumer DISABLE_MODERN_ECO = config -> { + config.set(ConfigNodes.ECO_ADVANCED_MODERN.getRoot(), "false"); + }; } diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/TownyEconomyHandler.java b/Towny/src/main/java/com/palmergames/bukkit/towny/TownyEconomyHandler.java index 07fc0975d3f..a6e474dc895 100644 --- a/Towny/src/main/java/com/palmergames/bukkit/towny/TownyEconomyHandler.java +++ b/Towny/src/main/java/com/palmergames/bukkit/towny/TownyEconomyHandler.java @@ -2,27 +2,30 @@ import com.palmergames.bukkit.config.ConfigNodes; import com.palmergames.bukkit.towny.event.economy.TownyPreTransactionEvent; -import com.palmergames.bukkit.towny.object.economy.adapter.ReserveEconomyAdapter; +import com.palmergames.bukkit.towny.object.economy.Account; import com.palmergames.bukkit.towny.object.Nation; import com.palmergames.bukkit.towny.object.Resident; import com.palmergames.bukkit.towny.object.Town; -import com.palmergames.bukkit.towny.object.economy.transaction.Transaction; -import com.palmergames.bukkit.towny.object.economy.Account; import com.palmergames.bukkit.towny.object.economy.TownyServerAccount; -import com.palmergames.bukkit.towny.object.economy.TownyServerAccountEconomyHandler; +import com.palmergames.bukkit.towny.object.economy.transaction.Transaction; import com.palmergames.bukkit.towny.object.economy.adapter.EconomyAdapter; -import com.palmergames.bukkit.towny.object.economy.adapter.VaultEconomyAdapter; +import com.palmergames.bukkit.towny.object.economy.provider.EconomyProvider; +import com.palmergames.bukkit.towny.object.economy.provider.ReserveEconomyProvider; +import com.palmergames.bukkit.towny.object.economy.provider.VaultEconomyProvider; import com.palmergames.bukkit.util.BukkitTools; import com.palmergames.bukkit.util.Colors; -import net.milkbowl.vault.economy.Economy; +import com.palmergames.util.JavaUtil; import net.tnemc.core.Reserve; import org.bukkit.World; -import org.bukkit.plugin.Plugin; -import org.bukkit.plugin.RegisteredServiceProvider; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; import java.util.UUID; import java.util.concurrent.Executor; @@ -36,7 +39,7 @@ public class TownyEconomyHandler { private static Towny plugin = null; private static EconomyAdapter economy = null; - private static EcoType Type = EcoType.NONE; + private static EconomyProvider provider = null; private static String version = ""; private static final Executor ECONOMY_EXECUTOR = runnable -> { @@ -52,8 +55,9 @@ public enum EcoType { NONE, VAULT, RESERVE } + @Deprecated public static String getServerAccount() { - return TownySettings.getString(ConfigNodes.ECO_CLOSED_ECONOMY_SERVER_ACCOUNT); + return TownyServerAccount.ACCOUNT.getName(); } /** @@ -66,27 +70,32 @@ public static String getServerAccount() { */ @Nullable public static UUID getTownyObjectUUID(String accountName) { + return Optional.ofNullable(getTownyObjectAccount(accountName)).map(Account::getUUID).orElse(null); + } - if (accountName.equalsIgnoreCase(getServerAccount())) - return TownyServerAccount.getUUID(); + @Nullable + public static Account getTownyObjectAccount(String accountName) { + + if (accountName.equalsIgnoreCase(TownyServerAccount.ACCOUNT.getName())) + return TownyServerAccount.ACCOUNT; String name; if (accountName.startsWith(TownySettings.getNPCPrefix())) { name = accountName.substring(TownySettings.getNPCPrefix().length()); Resident resident = TownyAPI.getInstance().getResident(name); - return resident != null ? resident.getUUID() : null; + return resident != null ? resident.getAccount() : null; } if (accountName.startsWith(TownySettings.getTownAccountPrefix())) { name = accountName.substring(TownySettings.getTownAccountPrefix().length()); Town town = TownyAPI.getInstance().getTown(name); - return town != null ? town.getUUID() : null; + return town != null ? town.getAccount() : null; } if (accountName.startsWith(TownySettings.getNationAccountPrefix())) { name = accountName.substring(TownySettings.getNationAccountPrefix().length()); Nation nation = TownyAPI.getInstance().getNation(name); - return nation != null ? nation.getUUID() : null; + return nation != null ? nation.getAccount() : null; } return null; @@ -96,17 +105,11 @@ public static void initialize(Towny plugin) { TownyEconomyHandler.plugin = plugin; } - public static TownyServerAccount initializeTownyServerAccount() { - TownyServerAccountEconomyHandler economyHandler = new TownyServerAccount(); - TownyServerAccount account = new TownyServerAccount(economyHandler); - return account; - } - /** * @return the economy type we have detected. */ public static EcoType getType() { - return Type; + return provider == null ? EcoType.NONE : provider.economyType(); } /** @@ -115,7 +118,7 @@ public static EcoType getType() { * @return true if we found one. */ public static boolean isActive() { - return (Type != EcoType.NONE && TownySettings.isUsingEconomy()); + return (getType() != EcoType.NONE && TownySettings.isUsingEconomy()); } /** @@ -125,15 +128,6 @@ public static String getVersion() { return version; } - /** - * Internal function to set the version string. - * - * @param version The version of this eco. - */ - private static void setVersion(String version) { - TownyEconomyHandler.version = version; - } - /** * Find and configure a suitable economy provider * @@ -141,37 +135,22 @@ private static void setVersion(String version) { */ public static boolean setupEconomy() { - Plugin economyProvider; - - /* - * Attempt to find Vault for Economy handling - */ - try { - RegisteredServiceProvider vaultEcoProvider = plugin.getServer().getServicesManager().getRegistration(net.milkbowl.vault.economy.Economy.class); - if (vaultEcoProvider != null) { - /* - * Flag as using Vault hooks - */ - economy = new VaultEconomyAdapter(vaultEcoProvider.getProvider()); - setVersion(String.format("%s %s", vaultEcoProvider.getProvider().getName(), "via Vault" )); - Type = EcoType.VAULT; + if (plugin.getServer().getPluginManager().isPluginEnabled("Vault")) + provider = new VaultEconomyProvider(); + else if (plugin.getServer().getPluginManager().isPluginEnabled("Reserve")) + provider = new ReserveEconomyProvider((Reserve) plugin.getServer().getPluginManager().getPlugin("Reserve")); + + if (provider != null) { + economy = provider.mainAdapter(); + + if (economy != null) { + version = economy.name() + " via " + provider.name(); + + if (provider.isLegacy()) + version += " (Legacy)"; + return true; } - } catch (NoClassDefFoundError ignored) { - } - - /* - * Attempt to find Reserve for Economy handling - */ - economyProvider = plugin.getServer().getPluginManager().getPlugin("Reserve"); - if(economyProvider != null && ((Reserve)economyProvider).economyProvided()) { - /* - * Flat as using Reserve Hooks. - */ - economy = new ReserveEconomyAdapter(((Reserve) economyProvider).economy()); - setVersion(String.format("%s %s", ((Reserve) economyProvider).economy().name(), "via Reserve" )); - Type = EcoType.RESERVE; - return true; } /* @@ -180,69 +159,129 @@ public static boolean setupEconomy() { return false; } + /** + * @deprecated since 0.100.4.6, use {@link #removeAccount(Account)} instead. + * @param accountName legacy account name. + */ + @Deprecated + public static void removeAccount(String accountName) { + final Account account = getTownyObjectAccount(accountName); + if (account != null) + removeAccount(account); + } + /** * Attempt to delete the economy account. * - * @param accountName name of the account to delete + * @param account account to delete */ - public static void removeAccount(String accountName) { - economy.deleteAccount(accountName); + public static void removeAccount(Account account) { + economy.deleteAccount(account); } /** * Returns the accounts current balance * - * @param accountName name of the economy account - * @param world name of world to check in (for TNE Reserve) + * @param account The economy account * @return double containing the total in the account */ + public static double getBalance(final @NotNull Account account) { + checkNewAccount(account); + return economy.getBalance(account); + } + + /** + * @deprecated since 0.100.4.6, use {@link #getBalance(Account)} instead. + * @param accountName legacy account name. + * @param world world. + */ + @Deprecated public static double getBalance(String accountName, World world) { - checkNewAccount(accountName); - return economy.getBalance(accountName, world); + final Account account = getTownyObjectAccount(accountName); + + return account == null ? 0 : getBalance(account); + } + + /** + * @deprecated since 0.100.4.6, use {@link #hasEnough(Account, double)} instead. + * @param accountName legacy account name. + * @param amount amount to test for. + * @param world world + */ + @Deprecated + public static boolean hasEnough(String accountName, double amount, World world) { + final Account account = getTownyObjectAccount(accountName); + + return account != null && hasEnough(account, amount); } /** * Returns true if the account has enough money * - * @param accountName name of an economy account + * @param account economy account * @param amount minimum amount to check against (Double) - * @param world name of the world to check in (for TNE Reserve) * @return true if there is enough in the account */ - public static boolean hasEnough(String accountName, double amount, World world) { - return getBalance(accountName, world) >= amount; + public static boolean hasEnough(Account account, double amount) { + return getBalance(account) >= amount; } - private static boolean runPreChecks(Transaction transaction, String accountName) { + private static boolean runPreChecks(Transaction transaction) { TownyPreTransactionEvent preEvent = new TownyPreTransactionEvent(transaction); if (BukkitTools.isEventCancelled(preEvent) && transaction.getSendingPlayer() != null) { TownyMessaging.sendErrorMsg(transaction.getSendingPlayer(), preEvent.getCancelMessage()); return false; } - checkNewAccount(accountName); + if (transaction.hasReceiverAccount()) + checkNewAccount(transaction.getReceivingAccount()); + + if (transaction.hasSenderAccount()) + checkNewAccount(transaction.getSendingAccount()); + return true; } + /** + * @deprecated since 0.100.4.6, use {@link #subtract(Account, double)} instead. + * @param accountName legacy account name. + * @param amount amount to remove. + * @param world world + */ + @Deprecated + public static boolean subtract(String accountName, double amount, World world) { + final Account account = getTownyObjectAccount(accountName); + + return account != null && subtract(account, amount); + } + /** * Attempts to remove an amount from an account * * @param account the Account losing money. * @param amount amount of currency to remove from the account - * @param world name of the world in which to check in (TNE Reserve) * @return true if successful */ - public static boolean subtract(Account account, double amount, World world) { + public static boolean subtract(Account account, double amount) { - if (!runPreChecks(Transaction.subtract(amount).paidBy(account).build(), account.getName())) { + if (!runPreChecks(Transaction.subtract(amount).paidBy(account).build())) { return false; } + + return economy.subtract(account, amount); + } + + /** + * @deprecated since 0.100.4.6, use {@link #add(Account, double)} instead. + * @param accountName legacy account name. + * @param amount amount to add. + * @param world world + */ + @Deprecated + public static boolean add(String accountName, double amount, World world) { + final Account account = getTownyObjectAccount(accountName); - if (economy.subtract(account.getName(), amount, world)) { - return true; - } - - return false; + return account != null && add(account, amount); } /** @@ -250,25 +289,33 @@ public static boolean subtract(Account account, double amount, World world) { * * @param account the Account receiving money. * @param amount amount of currency to add - * @param world name of world (for TNE Reserve) * @return true if successful */ - public static boolean add(Account account, double amount, World world) { + public static boolean add(Account account, double amount) { - if (!runPreChecks(Transaction.add(amount).paidTo(account).build(), account.getName())) { + if (!runPreChecks(Transaction.add(amount).paidTo(account).build())) { return false; } - if (economy.add(account.getName(), amount, world)) { - return true; - } - - return false; + return economy.add(account, amount); } + /** + * @deprecated since 0.100.4.6, use {@link #setBalance(Account, double)} instead. + * @param accountName legacy account name. + * @param amount amount to set as a balance. + * @param world world + */ + @Deprecated public static boolean setBalance(String accountName, double amount, World world) { - checkNewAccount(accountName); - return economy.setBalance(accountName, amount, world); + final Account account = getTownyObjectAccount(accountName); + + return account != null && setBalance(account, amount); + } + + public static boolean setBalance(Account account, double amount) { + checkNewAccount(account); + return economy.setBalance(account, amount); } /** @@ -291,20 +338,29 @@ public static String getFormattedBalance(double balance) { } - private static void checkNewAccount(String accountName) { + private static void checkNewAccount(Account account) { // Check if the account exists, if not create one. - if (!economy.hasAccount(accountName)) { -// if (isEssentials()) { -// plugin.getLogger().info("Vault told Towny that the " + accountName + " economy account does not exist yet. Requesting a new account."); -// } - economy.newAccount(accountName); + if (!economy.hasAccount(account)) { + economy.newAccount(account); } } + public static boolean hasAccount(Account account) { + return economy.hasAccount(account); + } + + /** + * @deprecated since 0.100.4.6, use {@link #hasAccount(Account)} instead. + * @param accountName legacy account name. + */ + @Deprecated public static boolean hasAccount(String accountName) { - return economy.hasAccount(accountName); + final Account account = getTownyObjectAccount(accountName); + + return account != null && hasAccount(account); } + @Deprecated public static boolean isEssentials() { return getVersion().startsWith("EssentialsX Economy") || getVersion().startsWith("Essentials Economy"); } @@ -315,4 +371,32 @@ public static boolean isEssentials() { public static Executor economyExecutor() { return ECONOMY_EXECUTOR; } + + @ApiStatus.Internal + public static EconomyProvider getProvider() { + return provider; + } + + @ApiStatus.Internal + @Nullable + public static EconomyAdapter activeAdapter() { + return economy; + } + + // Names of economy implementations that use v2 uuids to identify NPC accounts + // v4 is used by default by towny so that the uuids of towns/nations in economy plugins line up with the uuid used by towny + private static final Set USE_V2_UUID = JavaUtil.make(new HashSet<>(), set -> set.add("EssentialsX Economy")); + + @ApiStatus.Internal + public static UUID modifyNPCUUID(final UUID uuid) { + if (economy != null && USE_V2_UUID.contains(economy.name())) { + return JavaUtil.changeUUIDVersion(uuid, 2); + } + + final int version = TownySettings.getInt(ConfigNodes.ECO_ADVANCED_NPC_UUID_VERSION); + if (version < 0 || version > 15) + return uuid; + + return JavaUtil.changeUUIDVersion(uuid, version); + } } \ No newline at end of file diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/command/HelpMenu.java b/Towny/src/main/java/com/palmergames/bukkit/towny/command/HelpMenu.java index 24a68a3cde2..c3895c8b9fb 100644 --- a/Towny/src/main/java/com/palmergames/bukkit/towny/command/HelpMenu.java +++ b/Towny/src/main/java/com/palmergames/bukkit/towny/command/HelpMenu.java @@ -417,15 +417,38 @@ protected MenuBuilder load() { .add("all", Translatable.of("ta_reload_help_4")); } }, - + + TA_ECO { + @Override + protected MenuBuilder load() { + return new MenuBuilder("townyadmin eco") + .add("resetbanks {amount}", Translatable.of("ta_eco_resetbanks_help")) + .add("depositall [amount]", Translatable.of("ta_depositall_help_0")) + .add("convert modern", Translatable.of("ta_eco_convert_modern_help")) + .add("convert [economy]", Translatable.of("ta_eco_convert_help")) + .add("info ?", Translatable.of("ta_eco_info_help")); + } + }, + TA_DEPOSITALL { @Override protected MenuBuilder load() { - return new MenuBuilder("townyadmin depositall") + return new MenuBuilder("townyadmin eco depositall") .add("[amount]", Translatable.of("ta_depositall_help_0")); } }, - + + TA_ECO_INFO { + @Override + protected MenuBuilder load() { + return new MenuBuilder("townyadmin eco info") + .add("nation [nationname]", Translatable.of("ta_info_help_0")) + .add("resident [residentname]", Translatable.of("ta_info_help_1")) + .add("serveraccount", Translatable.of("ta_info_help_2")) + .add("town [townname]", Translatable.of("ta_info_help_3")); + } + }, + TOWNYWORLD_HELP { @Override protected MenuBuilder load(MenuBuilder builder) { diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/command/TownyAdminCommand.java b/Towny/src/main/java/com/palmergames/bukkit/towny/command/TownyAdminCommand.java index 21cb1bfe932..12e7a8a5cf0 100644 --- a/Towny/src/main/java/com/palmergames/bukkit/towny/command/TownyAdminCommand.java +++ b/Towny/src/main/java/com/palmergames/bukkit/towny/command/TownyAdminCommand.java @@ -43,7 +43,11 @@ import com.palmergames.bukkit.towny.object.TownyWorld; import com.palmergames.bukkit.towny.object.Translatable; import com.palmergames.bukkit.towny.object.WorldCoord; +import com.palmergames.bukkit.towny.object.economy.TownyServerAccount; import com.palmergames.bukkit.towny.object.economy.transaction.Transaction; +import com.palmergames.bukkit.towny.object.economy.Account; +import com.palmergames.bukkit.towny.object.economy.adapter.EconomyAdapter; +import com.palmergames.bukkit.towny.object.economy.provider.EconomyProvider; import com.palmergames.bukkit.towny.object.jail.UnJailReason; import com.palmergames.bukkit.towny.object.metadata.CustomDataField; import com.palmergames.bukkit.towny.permissions.PermissionNodes; @@ -88,8 +92,10 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.logging.Level; @@ -126,9 +132,8 @@ public class TownyAdminCommand extends BaseCommand implements CommandExecutor { "tpplot", "database", "townyperms", - "depositall", - "resetbanks", - "install" + "install", + "eco" ); private static final List adminTownTabCompletes = Arrays.asList( @@ -273,7 +278,21 @@ public class TownyAdminCommand extends BaseCommand implements CommandExecutor { "townyperms", "all" ); + + private static final List adminEcoTabCompletes = Arrays.asList( + "convert", + "depositall", + "info", + "resetbanks" + ); + private static final List adminEcoInfoTabCompletes = Arrays.asList( + "nation", + "resident", + "serveraccount", + "town" + ); + public TownyAdminCommand(Towny towny) { this.plugin = towny; } @@ -606,6 +625,43 @@ else if (args.length > 3 && TownyCommandAddonAPI.hasCommand(CommandType.TOWNYADM return Collections.emptyList(); } } + break; + case "eco": + if (args.length == 2) + return NameUtil.filterByStart(adminEcoTabCompletes, args[1]); + + if (args.length > 2) { + if ("convert".equalsIgnoreCase(args[1])) { + List convertChoices = new ArrayList<>(); + if (!TownyEconomyHandler.isActive()) + return convertChoices; + + for (EconomyAdapter adapter : TownyEconomyHandler.getProvider().economyAdapters()) + convertChoices.add(adapter.name()); + + EconomyAdapter active = TownyEconomyHandler.activeAdapter(); + if (active != null) + convertChoices.remove(active.name()); + + if (TownyEconomyHandler.getProvider().isLegacy() && args.length < 4) + convertChoices.add("modern"); + + return NameUtil.filterByStart(convertChoices, args[2]); + } + if ("info".equalsIgnoreCase(args[1])) { + if (args.length == 3) + return NameUtil.filterByStart(adminEcoInfoTabCompletes, args[2]); + if (args.length == 4) { + switch (args[2].toLowerCase(Locale.ROOT)) { + case "nation": return BaseCommand.getTownyStartingWith(args[3], "n"); + case "resident": return BaseCommand.getTownyStartingWith(args[3], "r"); + case "town": return BaseCommand.getTownyStartingWith(args[3], "t"); + default: return Collections.emptyList(); + } + } + } + } + break; default: if (args.length == 1) @@ -645,8 +701,7 @@ public void parseTownyAdminCommand(CommandSender sender, String[] split) throws case "checkoutposts" -> parseAdminCheckOutpostsCommand(sender, null); case "townyperms" -> parseAdminTownyPermsCommand(sender, StringMgmt.remFirstArg(split)); case "tpplot" -> parseAdminTpPlotCommand(catchConsole(sender), StringMgmt.remFirstArg(split)); - case "depositall" -> parseAdminDepositAllCommand(sender, StringMgmt.remFirstArg(split)); - case "resetbanks" -> parseAdminResetBanksCommand(sender, StringMgmt.remFirstArg(split)); + case "eco" -> parseAdminEcoCommand(sender, StringMgmt.remFirstArg(split)); case "install" -> parseAdminInstall(sender); default -> { if (TownyCommandAddonAPI.hasCommand(CommandType.TOWNYADMIN, split[0])) { @@ -2800,40 +2855,178 @@ private static void handlePlotMetaRemove(Player player, TownBlock townBlock, fin TownyMessaging.sendMsg(player, Translatable.of("msg_data_successfully_deleted")); } - private void parseAdminDepositAllCommand(CommandSender sender, String[] split) throws TownyException { + private void parseAdminEcoCommand(CommandSender sender, String[] split) throws TownyException { if (!TownyEconomyHandler.isActive()) throw new TownyException(Translatable.of("msg_err_no_economy")); - checkPermOrThrow(sender, PermissionNodes.TOWNY_COMMAND_TOWNYADMIN_DEPOSITALL.getNode()); + + if (split.length < 1) { + HelpMenu.TA_ECO.send(sender); + return; + } + + switch (split[0].toLowerCase(Locale.ROOT)) { + case "depositall" -> parseAdminDepositAllCommand(sender, StringMgmt.remFirstArg(split)); + case "resetbanks" -> parseAdminResetBanksCommand(sender, StringMgmt.remFirstArg(split)); + case "convert" -> parseAdminConvertEconomyCommand(sender, StringMgmt.remFirstArg(split)); + case "info" -> parseAdminEcoInfoCommand(sender, StringMgmt.remFirstArg(split)); + } + } + + private void parseAdminDepositAllCommand(CommandSender sender, String[] split) throws TownyException { + checkPermOrThrow(sender, PermissionNodes.TOWNY_COMMAND_TOWNYADMIN_ECO_DEPOSITALL.getNode()); if (split.length != 1) { HelpMenu.TA_DEPOSITALL.send(sender); return; } String reason = "townyadmin depositall"; double amount = MoneyUtil.getMoneyAboveZeroOrThrow(split[0]); - for (Nation nation : TownyUniverse.getInstance().getNations()) - nation.getAccount().deposit(amount, reason); - - for (Town town : TownyUniverse.getInstance().getTowns()) - town.getAccount().deposit(amount, reason); - + TownyEconomyHandler.economyExecutor().execute(() -> { + for (Nation nation : TownyUniverse.getInstance().getNations()) + nation.getAccount().deposit(amount, reason); + + for (Town town : TownyUniverse.getInstance().getTowns()) + town.getAccount().deposit(amount, reason); + }); TownyMessaging.sendMsg(sender, Translatable.of("msg_ta_deposit_all_success", TownyEconomyHandler.getFormattedBalance(amount))); } private void parseAdminResetBanksCommand(CommandSender sender, String[] args) throws TownyException { - if (!TownyEconomyHandler.isActive()) - throw new TownyException(Translatable.of("msg_err_no_economy")); - checkPermOrThrow(sender, PermissionNodes.TOWNY_COMMAND_TOWNYADMIN_RESETBANKS.getNode()); + checkPermOrThrow(sender, PermissionNodes.TOWNY_COMMAND_TOWNYADMIN_ECO_RESETBANKS.getNode()); final double amount = args.length == 1 ? MathUtil.getIntOrThrow(args[0]) : 0.0; Confirmation.runOnAccept(() -> { - for (Town town : TownyUniverse.getInstance().getTowns()) - town.getAccount().setBalance(amount, "Admin used /ta resetbanks"); - - for (Nation nation : TownyUniverse.getInstance().getNations()) - nation.getAccount().setBalance(amount, "Admin used /ta resetbanks"); + TownyEconomyHandler.economyExecutor().execute(() -> { + for (Town town : TownyUniverse.getInstance().getTowns()) + town.getAccount().setBalance(amount, "Admin used /ta resetbanks"); + + for (Nation nation : TownyUniverse.getInstance().getNations()) + nation.getAccount().setBalance(amount, "Admin used /ta resetbanks"); + }); }) .setTitle(Translatable.of("confirmation_are_you_sure_you_want_to_reset_all_banks", TownyEconomyHandler.getFormattedBalance(amount))) .sendTo(sender); } + + private void parseAdminConvertEconomyCommand(CommandSender sender, String[] args) throws TownyException { + checkPermOrThrow(sender, PermissionNodes.TOWNY_COMMAND_TOWNYADMIN_ECO_CONVERT.getNode()); + + EconomyProvider provider = TownyEconomyHandler.getProvider(); + if (provider == null) + throw new TownyException(Translatable.of("msg_admin_eco_convert_no_providers")); + + if (args.length == 0) + throw new TownyException(Translatable.of("msg_admin_eco_convert_no_target_specified")); + + EconomyAdapter target; + + final String targetName = String.join(" ", args); + if ("modern".equalsIgnoreCase(targetName)) { + if (!provider.isLegacy()) + throw new TownyException(Translatable.of("msg_admin_eco_convert_already_modern")); + + // Temporarily set to modern to create a non-legacy adapter as the target + provider.setLegacy(false); + try { + target = provider.mainAdapter(); + } finally { + provider.setLegacy(true); + } + } else { + target = provider.getEconomyAdapter(targetName); + } + + if (target == null) + throw new TownyException(Translatable.of("msg_admin_eco_convert_target_not_found", targetName, provider.economyAdapters().stream().map(EconomyAdapter::name).collect(Collectors.joining(", ")))); + + if (Bukkit.getOnlinePlayers().size() > 0) + TownyMessaging.sendMsg(sender, Translatable.of("msg_admin_eco_convert_online_players_warning")); + + Confirmation.runOnAccept(() -> TownyEconomyHandler.economyExecutor().execute(() -> { + Map balances = new HashMap<>(); + + // Gather up all the balances + for (Town town : TownyUniverse.getInstance().getTowns()) { + balances.put(town.getAccount(), town.getAccount().getHoldingBalance()); + + town.getAccount().setUUID(TownyEconomyHandler.modifyNPCUUID(town.getUUID())); + } + + for (Nation nation : TownyUniverse.getInstance().getNations()) { + balances.put(nation.getAccount(), nation.getAccount().getHoldingBalance()); + + nation.getAccount().setUUID(TownyEconomyHandler.modifyNPCUUID(nation.getUUID())); + } + + for (Resident resident : TownyUniverse.getInstance().getResidents()) { + balances.put(resident.getAccount(), resident.getAccount().getHoldingBalance()); + + if (resident.isNPC()) + resident.getAccount().setUUID(TownyEconomyHandler.modifyNPCUUID(resident.getUUID())); + } + + balances.put(TownyServerAccount.ACCOUNT, TownyServerAccount.ACCOUNT.getHoldingBalance()); + + TownyMessaging.sendMessage(sender, Translatable.of("msg_admin_eco_convert_x_accounts_found", balances.size())); + HashMap progressMap = new HashMap<>(); + int tenPercent = balances.size() / 10; + for (int i = 1; i < 10; i++) + progressMap.put(tenPercent * i, i + "0%"); + + int i = 0; + // And set them to the target economy adapter + for (Map.Entry entry : balances.entrySet()) { + i++; + if (progressMap.containsKey(i)) + TownyMessaging.sendMessage(sender, Translatable.of("msg_admin_eco_convert_conversion_progress", progressMap.get(i))); + + // Skip accounts with no balance + if (entry.getValue() == 0) + continue; + + if (!target.hasAccount(entry.getKey())) { + TownyMessaging.sendDebugMsg("Creating new account for " + entry.getKey().getUUID()); + target.newAccount(entry.getKey()); + } + + target.setBalance(entry.getKey(), entry.getValue()); + TownyMessaging.sendDebugMsg("Setting balance of account " + entry.getKey().getUUID() + " to " + entry.getValue()); + } + + // Change config to modern & setup economy again + if ("modern".equalsIgnoreCase(targetName)) { + TownySettings.setProperty(ConfigNodes.ECO_ADVANCED_MODERN.getRoot(), true); + TownySettings.saveConfig(); + TownyEconomyHandler.setupEconomy(); + } + + TownyMessaging.sendMsg(sender, Translatable.of("msg_admin_eco_convert_success")); + })).sendTo(sender); + } + + private void parseAdminEcoInfoCommand(CommandSender sender, String[] args) throws TownyException { + checkPermOrThrow(sender, PermissionNodes.TOWNY_COMMAND_TOWNYADMIN_ECO_INFO.getNode()); + if (args.length == 0 || (!args[0].equalsIgnoreCase("serveraccount") && args.length < 2)) { + HelpMenu.TA_ECO_INFO.send(sender); + return; + } + + Account account = switch (args[0].toLowerCase(Locale.ROOT)) { + case "serveraccount" -> TownyServerAccount.ACCOUNT; + case "nation" -> getNationOrThrow(args[1]).getAccount(); + case "resident" -> getResidentOrThrow(args[1]).getAccount(); + case "town" -> getTownOrThrow(args[1]).getAccount(); + default -> null; + }; + + if (account == null) + throw new TownyException("Account not found."); + + TownyEconomyHandler.economyExecutor().execute(() -> { + TownyMessaging.sendMessage(sender, ChatTools.formatTitle("Account Info")); + TownyMessaging.sendMessage(sender, "Name: " + account.getName()); + TownyMessaging.sendMessage(sender, "UUID: " + account.getUUID()); + TownyMessaging.sendMessage(sender, "Balance: " + account.getHoldingBalance()); + }); + } private void parseAdminInstall(CommandSender sender) throws NoPermissionException { checkPermOrThrow(sender, PermissionNodes.TOWNY_COMMAND_TOWNYADMIN_INSTALL.getNode()); diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/hooks/PluginIntegrations.java b/Towny/src/main/java/com/palmergames/bukkit/towny/hooks/PluginIntegrations.java index ae67a02a1a3..10ad1a831aa 100644 --- a/Towny/src/main/java/com/palmergames/bukkit/towny/hooks/PluginIntegrations.java +++ b/Towny/src/main/java/com/palmergames/bukkit/towny/hooks/PluginIntegrations.java @@ -26,7 +26,6 @@ import com.palmergames.bukkit.towny.permissions.BukkitPermSource; import com.palmergames.bukkit.towny.permissions.GroupManagerSource; import com.palmergames.bukkit.towny.permissions.VaultPermSource; -import com.palmergames.bukkit.towny.utils.MoneyUtil; import com.palmergames.bukkit.util.Colors; import com.palmergames.bukkit.util.Version; import com.palmergames.util.JavaUtil; @@ -110,10 +109,7 @@ public void setupAndPrintEconomy(boolean configSetForEconomy) { // Check if the economy is enabled in the config and attempt to set it up. if (configSetForEconomy && TownyEconomyHandler.setupEconomy()) { - ecowarn = TownyEconomyHandler.isEssentials() - ? "EssentialsX Economy has been known to reset town and nation bank accounts on rare occasions." - : ""; - MoneyUtil.checkLegacyDebtAccounts(); + ecowarn = ""; // Used to be useful, hopefully we don't need to use it again. } if (TownyEconomyHandler.isActive()) diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/object/EconomyAccount.java b/Towny/src/main/java/com/palmergames/bukkit/towny/object/EconomyAccount.java index 8b6da4617ca..9cef22f4d78 100644 --- a/Towny/src/main/java/com/palmergames/bukkit/towny/object/EconomyAccount.java +++ b/Towny/src/main/java/com/palmergames/bukkit/towny/object/EconomyAccount.java @@ -3,7 +3,9 @@ import com.palmergames.bukkit.towny.TownyEconomyHandler; import com.palmergames.bukkit.towny.object.economy.Account; import com.palmergames.bukkit.towny.object.economy.BankAccount; -import org.bukkit.World; + +import java.util.UUID; +import java.util.function.Supplier; /** * An Account object representing a Player's account. In contrast to the @@ -15,27 +17,18 @@ * @author LlmDl */ public class EconomyAccount extends Account { - private World world; - - protected EconomyAccount(Resident resident, String name, World world) { - super(resident, name); - this.world = world; + protected EconomyAccount(Resident resident, String name, UUID uuid, Supplier worldSupplier) { + super(resident, name, uuid, worldSupplier); } @Override protected synchronized boolean addMoney(double amount) { - - return TownyEconomyHandler.add(this, amount, world); + return TownyEconomyHandler.add(this, amount); } @Override protected synchronized boolean subtractMoney(double amount) { - return TownyEconomyHandler.subtract(this, amount, world); + return TownyEconomyHandler.subtract(this, amount); } - - public World getWorld() { - return world; - } - } diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/object/Government.java b/Towny/src/main/java/com/palmergames/bukkit/towny/object/Government.java index fc4e892b20d..568befac8e9 100644 --- a/Towny/src/main/java/com/palmergames/bukkit/towny/object/Government.java +++ b/Towny/src/main/java/com/palmergames/bukkit/towny/object/Government.java @@ -275,8 +275,7 @@ public final synchronized void depositToBank(Resident resident, int amount) thro public BankAccount getAccount() { if (account == null) { String accountName = StringMgmt.trimMaxLength(getBankAccountPrefix() + getName(), 32); - World world = getWorld(); - account = new BankAccount(accountName, world, this); + account = new BankAccount(accountName, this); account.setAuditor(accountAuditor); } diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/object/Identifiable.java b/Towny/src/main/java/com/palmergames/bukkit/towny/object/Identifiable.java index cdbfb6a40a9..afd2b90b492 100644 --- a/Towny/src/main/java/com/palmergames/bukkit/towny/object/Identifiable.java +++ b/Towny/src/main/java/com/palmergames/bukkit/towny/object/Identifiable.java @@ -1,5 +1,7 @@ package com.palmergames.bukkit.towny.object; +import org.jetbrains.annotations.ApiStatus; + import java.util.UUID; /** @@ -14,6 +16,7 @@ public interface Identifiable { * This should only be used by internal loading methods! * @param uuid the UUID to set. */ + @ApiStatus.Internal void setUUID(UUID uuid); } diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/object/Resident.java b/Towny/src/main/java/com/palmergames/bukkit/towny/object/Resident.java index cba6581f1eb..e53cdc9e33c 100644 --- a/Towny/src/main/java/com/palmergames/bukkit/towny/object/Resident.java +++ b/Towny/src/main/java/com/palmergames/bukkit/towny/object/Resident.java @@ -2,6 +2,7 @@ import com.google.common.base.Preconditions; import com.palmergames.bukkit.towny.Towny; +import com.palmergames.bukkit.towny.TownyAPI; import com.palmergames.bukkit.towny.TownyEconomyHandler; import com.palmergames.bukkit.towny.TownyMessaging; import com.palmergames.bukkit.towny.TownySettings; @@ -40,7 +41,6 @@ import net.kyori.adventure.audience.Audience; import net.kyori.adventure.audience.ForwardingAudience; import org.bukkit.Location; -import org.bukkit.World; import org.bukkit.entity.Player; import org.bukkit.inventory.Inventory; import org.jetbrains.annotations.ApiStatus; @@ -808,18 +808,19 @@ public void removeMetaData(@NotNull CustomDataField md) { @Override public Account getAccount() { if (account == null) { - String accountName = StringMgmt.trimMaxLength(getName(), 32); - World world; - - Player player = getPlayer(); - if (player != null) { - world = player.getWorld(); - } else { - world = BukkitTools.getWorlds().get(0); - } - account = new EconomyAccount(this, accountName, world); + UUID uuid = this.uuid; + if (this.isNPC()) + uuid = TownyEconomyHandler.modifyNPCUUID(uuid); + + account = new EconomyAccount(this, accountName, uuid, () -> { + final Player player = getPlayer(); + if (player != null) + return TownyAPI.getInstance().getTownyWorld(player.getWorld()); + else + return TownyUniverse.getInstance().getTownyWorlds().get(0); + }); } return account; diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/object/economy/Account.java b/Towny/src/main/java/com/palmergames/bukkit/towny/object/economy/Account.java index 6ac08db355d..513a9a099f8 100644 --- a/Towny/src/main/java/com/palmergames/bukkit/towny/object/economy/Account.java +++ b/Towny/src/main/java/com/palmergames/bukkit/towny/object/economy/Account.java @@ -2,18 +2,36 @@ import com.palmergames.bukkit.config.ConfigNodes; import com.palmergames.bukkit.towny.Towny; +import com.palmergames.bukkit.towny.TownyAPI; import com.palmergames.bukkit.towny.TownyEconomyHandler; import com.palmergames.bukkit.towny.TownySettings; +import com.palmergames.bukkit.towny.TownyUniverse; import com.palmergames.bukkit.towny.object.EconomyAccount; import com.palmergames.bukkit.towny.object.EconomyHandler; +import com.palmergames.bukkit.towny.object.Identifiable; import com.palmergames.bukkit.towny.object.Nameable; import com.palmergames.bukkit.towny.object.economy.transaction.Transaction; +import com.palmergames.bukkit.towny.object.TownyWorld; import com.palmergames.bukkit.util.BukkitTools; +import com.palmergames.util.JavaUtil; +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; import org.bukkit.World; - +import org.bukkit.entity.Player; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.Unmodifiable; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.reflect.Constructor; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.UUID; +import java.util.function.Supplier; import java.util.logging.Level; /** @@ -24,21 +42,25 @@ * @see BankAccount * @see EconomyAccount */ -public abstract class Account implements Nameable { +public abstract class Account implements Nameable, Identifiable { private static final long CACHE_TIMEOUT = TownySettings.getCachedBankTimeout(); private static final AccountObserver GLOBAL_OBSERVER = new GlobalAccountObserver(); private final List observers = new ArrayList<>(); private final EconomyHandler economyHandler; private AccountAuditor auditor; - protected CachedBalance cachedBalance = null; - public static final TownyServerAccount SERVER_ACCOUNT = TownyEconomyHandler.initializeTownyServerAccount(); + CachedBalance cachedBalance = new CachedBalance(0); - String name; - World world; + private String name; + private UUID uuid; + private final Supplier worldSupplier; - public Account(EconomyHandler economyHandler, String name) { + private OfflinePlayer cachedOfflinePlayer; + + public Account(final EconomyHandler owner, final @NotNull String name, final @NotNull UUID uuid, final @Nullable Supplier worldSupplier) { + this.economyHandler = owner; this.name = name; - this.economyHandler = economyHandler; + this.uuid = uuid; + this.worldSupplier = worldSupplier; // ALL account transactions will route auditing data through this // central auditor. @@ -48,14 +70,36 @@ public Account(EconomyHandler economyHandler, String name) { this.cachedBalance = new CachedBalance(getHoldingBalance(false)); } catch (Exception e) { Towny.getPlugin().getLogger().log(Level.WARNING, String.format("An exception occurred when initializing cached balance for an account (name: %s), see the below error for more details.", name), e); - - this.cachedBalance = new CachedBalance(0); } } + /** + * @deprecated since 0.100.4.6 use {@link #Account(EconomyHandler, String, UUID, Supplier)} instead. + * @param economyHandler economyHandler that owns this account. + * @param name name of the account owner. + * @param world world in which the account would exist. + */ + @Deprecated public Account(EconomyHandler economyHandler, String name, World world) { - this(economyHandler, name); - this.world = world; + this.name = name; + this.uuid = economyHandler instanceof Identifiable identifiable ? identifiable.getUUID() : null; + this.economyHandler = economyHandler; + + TownyWorld townyWorld = TownyAPI.getInstance().getTownyWorld(world); + this.worldSupplier = () -> townyWorld; + } + + /** + * @deprecated since 0.100.4.6 use {@link #Account(EconomyHandler, String, UUID, Supplier)} instead. + * @param economyHandler economyHandler that owns this account. + * @param name name of the account owner. + */ + @Deprecated + public Account(EconomyHandler economyHandler, String name) { + this.name = name; + this.uuid = economyHandler instanceof Identifiable identifiable ? identifiable.getUUID() : null; + this.economyHandler = economyHandler; + this.worldSupplier = () -> TownyUniverse.getInstance().getTownyWorlds().get(0); } // Template methods @@ -118,17 +162,17 @@ public synchronized boolean payTo(double amount, EconomyHandler collector, Strin protected synchronized boolean payToServer(double amount, String reason) { // Put it back into the server. - boolean success = Account.SERVER_ACCOUNT.addToServer(this, amount, getBukkitWorld()); + boolean success = TownyServerAccount.addToServer(this, amount, getWorld()); if (success) - notifyObserversDeposit(Account.SERVER_ACCOUNT, amount, reason); + notifyObserversDeposit(TownyServerAccount.ACCOUNT, amount, reason); return success; } protected synchronized boolean payFromServer(double amount, String reason) { // Remove it from the server economy. - boolean success = Account.SERVER_ACCOUNT.subtractFromServer(this, amount, getBukkitWorld()); + boolean success = TownyServerAccount.subtractFromServer(this, amount, getWorld()); if (success) - notifyObserversWithdraw(Account.SERVER_ACCOUNT, amount, reason); + notifyObserversWithdraw(TownyServerAccount.ACCOUNT, amount, reason); return success; } @@ -155,10 +199,18 @@ public synchronized boolean payTo(double amount, Account collector, String reaso /** * Fetch the current world for this object * + * @deprecated since 0.100.4.6 use {@link #getWorld().getBukkitWorld()} instead. * @return Bukkit world for the object */ + @Deprecated + @Nullable public World getBukkitWorld() { - return BukkitTools.getWorlds().get(0); + return getWorld().getBukkitWorld(); + } + + @NotNull + public TownyWorld getWorld() { + return this.worldSupplier.get(); } /** @@ -195,7 +247,7 @@ public synchronized double getHoldingBalance() { * @return The amount in this account. */ public synchronized double getHoldingBalance(boolean setCache) { - double balance = TownyEconomyHandler.getBalance(getName(), getBukkitWorld()); + double balance = TownyEconomyHandler.getBalance(this); if (setCache) cachedBalance.setBalance(balance); return balance; @@ -208,7 +260,7 @@ public synchronized double getHoldingBalance(boolean setCache) { * @return true if there is enough. */ public synchronized boolean canPayFromHoldings(double amount) { - return TownyEconomyHandler.hasEnough(getName(), amount, getBukkitWorld()); + return TownyEconomyHandler.hasEnough(this, amount); } /** @@ -225,11 +277,12 @@ public String getHoldingFormattedBalance() { */ public void removeAccount() { if (TownySettings.isEcoClosedEconomyEnabled()) { - double balance = TownyEconomyHandler.getBalance(getName(), getBukkitWorld()); + double balance = TownyEconomyHandler.getBalance(this); if (balance > 0) - Account.SERVER_ACCOUNT.addToServer(this, balance, getBukkitWorld()); + TownyServerAccount.addToServer(this, balance, getWorld()); } - TownyEconomyHandler.removeAccount(getName()); + + TownyEconomyHandler.removeAccount(this); } /** @@ -247,6 +300,21 @@ public String getName() { public void setName(String name) { this.name = name; + this.cachedOfflinePlayer = null; + } + + /** + * @apiNote The returned uuid's version may differ from the object this represents in the case of NPC accounts. + */ + @Override + public @NotNull UUID getUUID() { + return this.uuid; + } + + @Override + public void setUUID(final @NotNull UUID uuid) { + this.uuid = uuid; + this.cachedOfflinePlayer = null; } /** @@ -254,7 +322,7 @@ public void setName(String name) { * * @return A list of account observers. */ - public List getObservers() { + public @Unmodifiable List getObservers() { return Collections.unmodifiableList(observers); } @@ -314,8 +382,8 @@ class CachedBalance { private double balance = 0; private long time; - CachedBalance(double _balance) { - balance = _balance; + CachedBalance(double balance) { + this.balance = balance; time = System.currentTimeMillis(); } @@ -364,4 +432,49 @@ public synchronized double getCachedBalance(boolean refreshIfStale) { return cachedBalance.getBalance(); } + + private static final MethodHandle GAMEPROFILE_CONSTRUCTOR = JavaUtil.make(() -> { + try { + return MethodHandles.publicLookup().findConstructor(Class.forName("com.mojang.authlib.GameProfile"), MethodType.methodType(void.class, UUID.class, String.class)); + } catch (Throwable throwable) { + Towny.getPlugin().getLogger().log(Level.WARNING, "Could not find game profile constructor", throwable); + return null; + } + }); + + private static final MethodHandle OFFLINEPLAYER_CONSTRUCTOR = JavaUtil.make(() -> { + try { + final String cbPackagePath = Bukkit.getServer().getClass().getPackage().getName(); + Class offlinePlayer = Class.forName(cbPackagePath + ".CraftOfflinePlayer"); + Constructor constructor = offlinePlayer.getDeclaredConstructor(Class.forName(cbPackagePath + ".CraftServer"), Class.forName("com.mojang.authlib.GameProfile")); + constructor.setAccessible(true); + + return MethodHandles.lookup().unreflectConstructor(constructor); + } catch (Throwable throwable) { + Towny.getPlugin().getLogger().log(Level.WARNING, "Could not find craft offline player constructor", throwable); + return null; + } + }); + + @ApiStatus.Internal + public @NotNull OfflinePlayer asOfflinePlayer() { + // This account could belong to an online player + if (this instanceof EconomyAccount && this.uuid.version() != 2) { + final Player player = Bukkit.getServer().getPlayer(this.uuid); + if (player != null) + return player; + } + + if (this.cachedOfflinePlayer != null) + return this.cachedOfflinePlayer; + + try { + final Object gameProfile = GAMEPROFILE_CONSTRUCTOR.invoke(this.uuid, this.name); + + return (this.cachedOfflinePlayer = (OfflinePlayer) OFFLINEPLAYER_CONSTRUCTOR.invoke(Bukkit.getServer(), gameProfile)); + } catch (Throwable throwable) { + Towny.getPlugin().getLogger().log(Level.WARNING, "An exception occurred when creating offline player for account " + this.getUUID(), throwable); + return Bukkit.getServer().getOfflinePlayer(this.uuid); // panic + } + } } diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/object/economy/BankAccount.java b/Towny/src/main/java/com/palmergames/bukkit/towny/object/economy/BankAccount.java index 99bc7c4530d..7dfd78addbd 100644 --- a/Towny/src/main/java/com/palmergames/bukkit/towny/object/economy/BankAccount.java +++ b/Towny/src/main/java/com/palmergames/bukkit/towny/object/economy/BankAccount.java @@ -1,5 +1,6 @@ package com.palmergames.bukkit.towny.object.economy; +import com.palmergames.bukkit.towny.TownyAPI; import com.palmergames.bukkit.towny.TownyEconomyHandler; import com.palmergames.bukkit.towny.TownyMessaging; import com.palmergames.bukkit.towny.TownySettings; @@ -12,7 +13,6 @@ import com.palmergames.bukkit.towny.object.Translatable; import com.palmergames.bukkit.util.BukkitTools; -import org.bukkit.World; import org.jetbrains.annotations.Nullable; /** @@ -22,17 +22,16 @@ public class BankAccount extends Account { private double debtCap; - private Government government; + private final Government government; /** * Constructor for a {@link Government} BankAccount. Governments can be Towns or Nations. * * @param name Name of the {@link EconomyAccount} that will be used, ie: town-townname. - * @param world World that will be associated with this BankAccount. * @param government Town or Nation that is getting a BankAccount. */ - public BankAccount(String name, World world, Government government) { - super(government, name, world); + public BankAccount(String name, Government government) { + super(government, name, TownyEconomyHandler.modifyNPCUUID(government.getUUID()), () -> TownyAPI.getInstance().getTownyWorld(government.getWorld())); this.government = government; } @@ -57,7 +56,7 @@ protected synchronized boolean subtractMoney(double amount) { if (newDebt <= getDebtCap()) { // Empty out account. - boolean success = TownyEconomyHandler.setBalance(getName(), 0, world); + boolean success = TownyEconomyHandler.setBalance(this, 0); success &= addDebt(newDebt); // Fire an event if the Town will be allowed to take on this new debt. @@ -71,7 +70,7 @@ protected synchronized boolean subtractMoney(double amount) { } // Otherwise continue like normal. - return TownyEconomyHandler.subtract(this, amount, world); + return TownyEconomyHandler.subtract(this, amount); } @Override @@ -84,7 +83,7 @@ protected synchronized boolean addMoney(double amount) { return removeDebt(amount); // Otherwise continue like normal. - return TownyEconomyHandler.add(this, amount, world); + return TownyEconomyHandler.add(this, amount); } @Override @@ -97,7 +96,7 @@ public synchronized boolean canPayFromHoldings(double amount) { @Override public synchronized double getHoldingBalance(boolean setCache) { - double balance = isBankrupt() ? balance = getTownDebt() * -1 : TownyEconomyHandler.getBalance(getName(), getBukkitWorld()); + double balance = isBankrupt() ? getTownDebt() * -1 : TownyEconomyHandler.getBalance(this); if (setCache) this.cachedBalance.setBalance(balance); return balance; @@ -196,12 +195,8 @@ private synchronized boolean removeDebt(double amount) { double netMoney = amount - getTownDebt(); //Clear debt account setTownDebt(0.0); - // Sometimes there's money in the bank account - // (from a player manually putting money in via - // eco plugin, maybe.) - double bankBalance = TownyEconomyHandler.getBalance(getName(), getBukkitWorld()); //Set positive balance in regular account - TownyEconomyHandler.setBalance(getName(), bankBalance + netMoney, world); + TownyEconomyHandler.setBalance(this, getHoldingBalance() + netMoney); return true; } else { setTownDebt(getTownDebt() - amount); diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/object/economy/TownyServerAccount.java b/Towny/src/main/java/com/palmergames/bukkit/towny/object/economy/TownyServerAccount.java index 7dbab586614..c1cb2fb9cb1 100644 --- a/Towny/src/main/java/com/palmergames/bukkit/towny/object/economy/TownyServerAccount.java +++ b/Towny/src/main/java/com/palmergames/bukkit/towny/object/economy/TownyServerAccount.java @@ -2,45 +2,41 @@ import java.util.UUID; -import org.bukkit.World; - import com.palmergames.bukkit.config.ConfigNodes; import com.palmergames.bukkit.towny.TownyEconomyHandler; import com.palmergames.bukkit.towny.TownySettings; +import com.palmergames.bukkit.towny.object.EconomyHandler; import com.palmergames.bukkit.towny.object.economy.transaction.Transaction; import com.palmergames.bukkit.util.BukkitTools; +import com.palmergames.bukkit.towny.TownyUniverse; +import com.palmergames.bukkit.towny.object.TownyWorld; +import org.jetbrains.annotations.ApiStatus; /** * For internal use only. */ -public class TownyServerAccount extends Account implements TownyServerAccountEconomyHandler { - - private final static UUID uuid = UUID.fromString("a73f39b0-1b7c-4930-b4a3-ce101812d926"); - private final static String name = TownySettings.getString(ConfigNodes.ECO_CLOSED_ECONOMY_SERVER_ACCOUNT); +@ApiStatus.Internal +public final class TownyServerAccount extends Account { - public TownyServerAccount() { - super(null, name); - } + private static final UUID uuid = UUID.fromString("a73f39b0-1b7c-2930-b4a3-ce101812d926"); + private static final String name = TownySettings.getString(ConfigNodes.ECO_CLOSED_ECONOMY_SERVER_ACCOUNT); + private static final ThreadLocal worldLocal = ThreadLocal.withInitial(() -> TownyUniverse.getInstance().getTownyWorlds().get(0)); + public static final TownyServerAccount ACCOUNT = new TownyServerAccount(); - public TownyServerAccount(TownyServerAccountEconomyHandler economyHandler) { - super(economyHandler, name); - } - - public static UUID getUUID() { - return uuid; + private TownyServerAccount() { + super(new EconomyHandlerHolder(), name, uuid, worldLocal::get); } @Override protected synchronized boolean addMoney(double amount) { - return TownyEconomyHandler.add(this, amount, world); + return addToServer(null, amount, this.getWorld()); } @Override protected synchronized boolean subtractMoney(double amount) { - return TownyEconomyHandler.subtract(this, amount, world); + return subtractFromServer(null, amount, this.getWorld()); } - /** * Adds money to the server account (used for towny closed economy.) * @@ -49,13 +45,18 @@ protected synchronized boolean subtractMoney(double amount) { * @param world The world of the deposit. * @return A boolean indicating success. */ - @Override - public boolean addToServer(Account account, double amount, World world) { - boolean success = TownyEconomyHandler.add(this, amount, world); - if (success) - BukkitTools.fireEvent(Transaction.add(amount).paidBy(account).paidToServer().asTownyTransactionEvent()); - - return success; + public static boolean addToServer(Account account, double amount, TownyWorld world) { + worldLocal.set(world); + + try { + boolean success = TownyEconomyHandler.add(ACCOUNT, amount); + if (success) + BukkitTools.fireEvent(Transaction.add(amount).paidBy(account).paidToServer().asTownyTransactionEvent()); + + return success; + } finally { + worldLocal.remove(); + } } /** @@ -65,18 +66,29 @@ public boolean addToServer(Account account, double amount, World world) { * @param world The world of the withdraw. * @return A boolean indicating success. */ - @Override - public boolean subtractFromServer(Account account, double amount, World world) { - boolean success = TownyEconomyHandler.subtract(this, amount, world); - if (success) - BukkitTools.fireEvent(Transaction.subtract(amount).paidByServer().paidTo(account).asTownyTransactionEvent()); - - return success; + public static boolean subtractFromServer(Account account, double amount, TownyWorld world) { + worldLocal.set(world); + + try { + boolean success = TownyEconomyHandler.subtract(ACCOUNT, amount); + if (success) + BukkitTools.fireEvent(Transaction.subtract(amount).paidByServer().paidTo(account).asTownyTransactionEvent()); + + return success; + } finally { + worldLocal.remove(); + } } - - @Override - public Account getAccount() { - return this; + + private static final class EconomyHandlerHolder implements EconomyHandler { + @Override + public Account getAccount() { + return ACCOUNT; + } + + @Override + public String getName() { + return name; + } } - } diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/object/economy/TownyServerAccountEconomyHandler.java b/Towny/src/main/java/com/palmergames/bukkit/towny/object/economy/TownyServerAccountEconomyHandler.java deleted file mode 100644 index 247063dc447..00000000000 --- a/Towny/src/main/java/com/palmergames/bukkit/towny/object/economy/TownyServerAccountEconomyHandler.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.palmergames.bukkit.towny.object.economy; - -import org.bukkit.World; - -import com.palmergames.bukkit.towny.object.EconomyHandler; - -/** - * Defines methods necessary for the TownyServerAccount, used mainly in the - * closed economy feature. - */ -public interface TownyServerAccountEconomyHandler extends EconomyHandler { - /** - * Gets the account associated with the TownyServerAccount - * - * @return The TownyServerAccount - */ - @Override - Account getAccount(); // Covariant return type of Account from superinterface - - public boolean addToServer(Account account, double amount, World world); - - public boolean subtractFromServer(Account account, double amount, World world); -} diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/object/economy/adapter/EconomyAdapter.java b/Towny/src/main/java/com/palmergames/bukkit/towny/object/economy/adapter/EconomyAdapter.java index f908649aa16..97903e6b5fd 100644 --- a/Towny/src/main/java/com/palmergames/bukkit/towny/object/economy/adapter/EconomyAdapter.java +++ b/Towny/src/main/java/com/palmergames/bukkit/towny/object/economy/adapter/EconomyAdapter.java @@ -1,72 +1,72 @@ package com.palmergames.bukkit.towny.object.economy.adapter; -import org.bukkit.World; +import com.palmergames.bukkit.towny.object.economy.Account; +import org.jetbrains.annotations.ApiStatus; /** * An adapter that is used to adapt multiple * economy implementations. */ +@ApiStatus.Internal public interface EconomyAdapter { + String name(); + /** * Attempts to add money to an account. * - * @param accountName The name of the account. + * @param account The account. * @param amount The amount to add. - * @param world The world this account is in. * @return A boolean indicating success. */ - boolean add(String accountName, double amount, World world); + boolean add(Account account, double amount); /** * Attempts to subtract money from an account. * - * @param accountName The name of the account. + * @param account The account. * @param amount The amount to add. - * @param world The world this account is in. * @return A boolean indicating success. */ - boolean subtract(String accountName, double amount, World world); + boolean subtract(Account account, double amount); /** * Checks whether the given account exists. * - * @param accountName The name of the account. + * @param account The account. * @return A boolean indicating success. */ - boolean hasAccount(String accountName); + boolean hasAccount(Account account); /** * Gets the balance of the account. * - * @param accountName The name of the account. - * @param world The world this account is in. + * @param account The account. * @return A boolean indicating success. */ - double getBalance(String accountName, World world); + double getBalance(Account account); /** * Attempts to create an account. * - * @param accountName The name of the new account. + * @param account The name of the new account. */ - void newAccount(String accountName); + void newAccount(Account account); /** * Removes an account. * - * @param accountName The name of the account to remove. + * @param account The name of the account to remove. */ - void deleteAccount(String accountName); + void deleteAccount(Account account); /** * Sets the balance of the account. * - * @param accountName The name of the account. + * @param account The account. * @param amount The amount to add. - * @param world The world this account is in. * @return A boolean indicating success. */ - boolean setBalance(String accountName, double amount, World world); + boolean setBalance(Account account, double amount); /** * Get's the proper formatting for a given balance. diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/object/economy/adapter/ReserveEconomyAdapter.java b/Towny/src/main/java/com/palmergames/bukkit/towny/object/economy/adapter/ReserveEconomyAdapter.java index 5536e850156..ec9ac5a7437 100644 --- a/Towny/src/main/java/com/palmergames/bukkit/towny/object/economy/adapter/ReserveEconomyAdapter.java +++ b/Towny/src/main/java/com/palmergames/bukkit/towny/object/economy/adapter/ReserveEconomyAdapter.java @@ -1,7 +1,7 @@ package com.palmergames.bukkit.towny.object.economy.adapter; +import com.palmergames.bukkit.towny.object.economy.Account; import net.tnemc.core.economy.EconomyAPI; -import org.bukkit.World; import java.math.BigDecimal; @@ -12,43 +12,48 @@ public class ReserveEconomyAdapter implements EconomyAdapter { public ReserveEconomyAdapter(EconomyAPI economy) { this.economy = economy; } + + @Override + public String name() { + return economy.name(); + } @Override - public boolean add(String accountName, double amount, World world) { + public boolean add(Account account, double amount) { BigDecimal bd = BigDecimal.valueOf(amount); - return economy.addHoldingsDetail(accountName, bd, world.getName()).success(); + return economy.addHoldingsDetail(account.getUUID(), bd, account.getWorld().getName()).success(); } @Override - public boolean subtract(String accountName, double amount, World world) { + public boolean subtract(Account account, double amount) { BigDecimal bd = BigDecimal.valueOf(amount); - return economy.removeHoldingsDetail(accountName, bd, world.getName()).success(); + return economy.removeHoldingsDetail(account.getUUID(), bd, account.getWorld().getName()).success(); } @Override - public boolean hasAccount(String accountName) { - return economy.hasAccountDetail(accountName).success(); + public boolean hasAccount(Account account) { + return economy.hasAccountDetail(account.getUUID()).success(); } @Override - public double getBalance(String accountName, World world) { - return economy.getHoldings(accountName, world.getName()).doubleValue(); + public double getBalance(Account account) { + return economy.getHoldings(account.getUUID(), account.getWorld().getName()).doubleValue(); } @Override - public void newAccount(String accountName) { - economy.createAccountDetail(accountName).success(); + public void newAccount(Account account) { + economy.createAccountDetail(account.getUUID()).success(); } @Override - public void deleteAccount(String accountName) { - economy.deleteAccountDetail(accountName); + public void deleteAccount(Account account) { + economy.deleteAccountDetail(account.getUUID()); } @Override - public boolean setBalance(String accountName, double amount, World world) { + public boolean setBalance(Account account, double amount) { BigDecimal bd = BigDecimal.valueOf(amount); - return economy.setHoldingsDetail(accountName, bd, world.getName()).success(); + return economy.setHoldingsDetail(account.getUUID(), bd, account.getWorld().getName()).success(); } @Override @@ -56,4 +61,48 @@ public String getFormattedBalance(double balance) { BigDecimal bd = BigDecimal.valueOf(balance); return economy.format(bd); } + + public static class Legacy extends ReserveEconomyAdapter { + public Legacy(EconomyAPI economy) { + super(economy); + } + + @Override + public boolean add(Account account, double amount) { + BigDecimal bd = BigDecimal.valueOf(amount); + return economy.addHoldingsDetail(account.getName(), bd, account.getWorld().getName()).success(); + } + + @Override + public boolean subtract(Account account, double amount) { + BigDecimal bd = BigDecimal.valueOf(amount); + return economy.removeHoldingsDetail(account.getName(), bd, account.getWorld().getName()).success(); + } + + @Override + public boolean hasAccount(Account account) { + return economy.hasAccountDetail(account.getName()).success(); + } + + @Override + public double getBalance(Account account) { + return economy.getHoldings(account.getName(), account.getWorld().getName()).doubleValue(); + } + + @Override + public void newAccount(Account account) { + economy.createAccountDetail(account.getName()).success(); + } + + @Override + public void deleteAccount(Account account) { + economy.deleteAccountDetail(account.getName()); + } + + @Override + public boolean setBalance(Account account, double amount) { + BigDecimal bd = BigDecimal.valueOf(amount); + return economy.setHoldingsDetail(account.getName(), bd, account.getWorld().getName()).success(); + } + } } diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/object/economy/adapter/VaultEconomyAdapter.java b/Towny/src/main/java/com/palmergames/bukkit/towny/object/economy/adapter/VaultEconomyAdapter.java index f828f9c183c..bf478553653 100644 --- a/Towny/src/main/java/com/palmergames/bukkit/towny/object/economy/adapter/VaultEconomyAdapter.java +++ b/Towny/src/main/java/com/palmergames/bukkit/towny/object/economy/adapter/VaultEconomyAdapter.java @@ -1,64 +1,68 @@ package com.palmergames.bukkit.towny.object.economy.adapter; +import com.palmergames.bukkit.towny.object.economy.Account; import net.milkbowl.vault.economy.Economy; import net.milkbowl.vault.economy.EconomyResponse; -import org.bukkit.World; -@SuppressWarnings("deprecation") public class VaultEconomyAdapter implements EconomyAdapter { - private final Economy economy; + protected final Economy economy; public VaultEconomyAdapter(Economy economy) { this.economy = economy; } + + @Override + public String name() { + return economy.getName(); + } @Override - public boolean add(String accountName, double amount, World world) { - return economy.depositPlayer(accountName, amount).type == EconomyResponse.ResponseType.SUCCESS; + public boolean add(Account account, double amount) { + return economy.depositPlayer(account.asOfflinePlayer(), account.getWorld().getName(), amount).type == EconomyResponse.ResponseType.SUCCESS; } @Override - public boolean subtract(String accountName, double amount, World world) { - return economy.withdrawPlayer(accountName, amount).type == EconomyResponse.ResponseType.SUCCESS; + public boolean subtract(Account account, double amount) { + return economy.withdrawPlayer(account.asOfflinePlayer(), account.getWorld().getName(), amount).type == EconomyResponse.ResponseType.SUCCESS; } @Override - public boolean hasAccount(String accountName) { - return economy.hasAccount(accountName); + public boolean hasAccount(Account account) { + return economy.hasAccount(account.asOfflinePlayer(), account.getWorld().getName()); } @Override - public double getBalance(String accountName, World world) { - return economy.getBalance(accountName); + public double getBalance(Account account) { + return economy.getBalance(account.asOfflinePlayer(), account.getWorld().getName()); } @Override - public void newAccount(String accountName) { - economy.createPlayerAccount(accountName); + public void newAccount(Account account) { + economy.createPlayerAccount(account.asOfflinePlayer(), account.getWorld().getName()); } @Override - public void deleteAccount(String accountName) { + public void deleteAccount(Account account) { // Attempt to zero the account as Vault provides no delete method. - if (!economy.hasAccount(accountName)) { + if (!hasAccount(account)) { return; } - - economy.withdrawPlayer(accountName, (economy.getBalance(accountName))); + + subtract(account, getBalance(account)); } @Override - public boolean setBalance(String accountName, double amount, World world) { - double currentBalance = getBalance(accountName, world); + public boolean setBalance(Account account, double amount) { + double currentBalance = getBalance(account); double diff = Math.abs(amount - currentBalance); - + if (amount > currentBalance) { - return add(accountName, diff, world); + return add(account, diff); }else if (amount < currentBalance) { - return subtract(accountName, diff, world); + return subtract(account, diff); } - + // If we get here, the balances are equal. return true; } @@ -67,4 +71,36 @@ public boolean setBalance(String accountName, double amount, World world) { public String getFormattedBalance(double balance) { return economy.format(balance); } + + @SuppressWarnings("deprecation") + public static class Legacy extends VaultEconomyAdapter { + public Legacy(Economy economy) { + super(economy); + } + + @Override + public boolean add(Account account, double amount) { + return economy.depositPlayer(account.getName(), account.getWorld().getName(), amount).type == EconomyResponse.ResponseType.SUCCESS; + } + + @Override + public boolean subtract(Account account, double amount) { + return economy.withdrawPlayer(account.getName(), account.getWorld().getName(), amount).type == EconomyResponse.ResponseType.SUCCESS; + } + + @Override + public boolean hasAccount(Account account) { + return economy.hasAccount(account.getName(), account.getWorld().getName()); + } + + @Override + public double getBalance(Account account) { + return economy.getBalance(account.getName(), account.getWorld().getName()); + } + + @Override + public void newAccount(Account account) { + economy.createPlayerAccount(account.getName(), account.getWorld().getName()); + } + } } diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/object/economy/provider/EconomyProvider.java b/Towny/src/main/java/com/palmergames/bukkit/towny/object/economy/provider/EconomyProvider.java new file mode 100644 index 00000000000..82f9d4b5472 --- /dev/null +++ b/Towny/src/main/java/com/palmergames/bukkit/towny/object/economy/provider/EconomyProvider.java @@ -0,0 +1,47 @@ +package com.palmergames.bukkit.towny.object.economy.provider; + +import com.palmergames.bukkit.config.ConfigNodes; +import com.palmergames.bukkit.towny.TownyEconomyHandler; +import com.palmergames.bukkit.towny.TownySettings; +import com.palmergames.bukkit.towny.object.economy.adapter.EconomyAdapter; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; + +@ApiStatus.Internal +public sealed abstract class EconomyProvider permits VaultEconomyProvider, ReserveEconomyProvider { + private boolean isLegacy = !TownySettings.getBoolean(ConfigNodes.ECO_ADVANCED_MODERN); + + /** + * @return The name of the plugin that provides the economy API in use, e.g. "Vault". + */ + public abstract String name(); + + public abstract TownyEconomyHandler.EcoType economyType(); + + /** + * @return The main economy adapter that should be used for all transactions. + */ + @Nullable + public abstract EconomyAdapter mainAdapter(); + + /** + * @return All existing registered adapters + */ + public abstract Collection economyAdapters(); + + @Nullable + public abstract EconomyAdapter getEconomyAdapter(final @NotNull String name); + + @ApiStatus.Internal + public boolean isLegacy() { + return this.isLegacy; + } + + @ApiStatus.Internal + public void setLegacy(final boolean legacy) { + this.isLegacy = legacy; + } +} diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/object/economy/provider/ReserveEconomyProvider.java b/Towny/src/main/java/com/palmergames/bukkit/towny/object/economy/provider/ReserveEconomyProvider.java new file mode 100644 index 00000000000..48c68260189 --- /dev/null +++ b/Towny/src/main/java/com/palmergames/bukkit/towny/object/economy/provider/ReserveEconomyProvider.java @@ -0,0 +1,51 @@ +package com.palmergames.bukkit.towny.object.economy.provider; + +import com.palmergames.bukkit.towny.TownyEconomyHandler; +import com.palmergames.bukkit.towny.object.economy.adapter.EconomyAdapter; +import com.palmergames.bukkit.towny.object.economy.adapter.ReserveEconomyAdapter; +import net.tnemc.core.Reserve; +import net.tnemc.core.economy.EconomyAPI; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Collectors; + +public final class ReserveEconomyProvider extends EconomyProvider { + private final Reserve reserve; + private final Function adapterFunction = api -> !isLegacy() ? new ReserveEconomyAdapter(api) : new ReserveEconomyAdapter.Legacy(api); + + public ReserveEconomyProvider(Reserve reserve) { + this.reserve = reserve; + } + + @Override + public String name() { + return "Reserve"; + } + + @Override + public TownyEconomyHandler.EcoType economyType() { + return TownyEconomyHandler.EcoType.RESERVE; + } + + @Override + public @Nullable EconomyAdapter mainAdapter() { + if (reserve.economy() == null) + return null; + + return adapterFunction.apply(reserve.economy()); + } + + @Override + public Collection economyAdapters() { + return reserve.getRegisteredEconomies().values().stream().map(adapterFunction).collect(Collectors.toSet()); + } + + @Override + public @Nullable EconomyAdapter getEconomyAdapter(@NotNull String name) { + return Optional.ofNullable(reserve.getRegisteredEconomies().get(name)).map(adapterFunction).orElse(null); + } +} diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/object/economy/provider/VaultEconomyProvider.java b/Towny/src/main/java/com/palmergames/bukkit/towny/object/economy/provider/VaultEconomyProvider.java new file mode 100644 index 00000000000..5f29c7403dd --- /dev/null +++ b/Towny/src/main/java/com/palmergames/bukkit/towny/object/economy/provider/VaultEconomyProvider.java @@ -0,0 +1,53 @@ +package com.palmergames.bukkit.towny.object.economy.provider; + +import com.palmergames.bukkit.towny.TownyEconomyHandler; +import com.palmergames.bukkit.towny.object.economy.adapter.EconomyAdapter; +import com.palmergames.bukkit.towny.object.economy.adapter.VaultEconomyAdapter; +import net.milkbowl.vault.economy.Economy; +import org.bukkit.Bukkit; +import org.bukkit.plugin.RegisteredServiceProvider; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; +import java.util.Map; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Collectors; + +public final class VaultEconomyProvider extends EconomyProvider { + private final Function, EconomyAdapter> adapterFunction = registration -> !isLegacy() ? new VaultEconomyAdapter(registration.getProvider()) : new VaultEconomyAdapter.Legacy(registration.getProvider()); + + @Override + public String name() { + return "Vault"; + } + + @Override + public TownyEconomyHandler.EcoType economyType() { + return TownyEconomyHandler.EcoType.VAULT; + } + + @Override + public EconomyAdapter mainAdapter() { + RegisteredServiceProvider registration = Bukkit.getServer().getServicesManager().getRegistration(Economy.class); + if (registration == null) + return null; + + return adapterFunction.apply(registration); + } + + @Override + public Collection economyAdapters() { + return getEconomyRegistrations().values().stream().map(adapterFunction).collect(Collectors.toList()); + } + + @Override + public @Nullable EconomyAdapter getEconomyAdapter(@NotNull String name) { + return Optional.ofNullable(getEconomyRegistrations().get(name)).map(adapterFunction).orElse(null); + } + + private Map> getEconomyRegistrations() { + return Bukkit.getServer().getServicesManager().getRegistrations(Economy.class).stream().collect(Collectors.toMap(registration -> registration.getProvider().getName(), registration -> registration)); + } +} diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/object/economy/transaction/TransactionBuilder.java b/Towny/src/main/java/com/palmergames/bukkit/towny/object/economy/transaction/TransactionBuilder.java index fc66862cc1f..70b0b139e93 100644 --- a/Towny/src/main/java/com/palmergames/bukkit/towny/object/economy/transaction/TransactionBuilder.java +++ b/Towny/src/main/java/com/palmergames/bukkit/towny/object/economy/transaction/TransactionBuilder.java @@ -3,6 +3,7 @@ import com.palmergames.bukkit.towny.event.economy.TownyTransactionEvent; import com.palmergames.bukkit.towny.object.EconomyHandler; import com.palmergames.bukkit.towny.object.economy.Account; +import com.palmergames.bukkit.towny.object.economy.TownyServerAccount; public class TransactionBuilder { TransactionType type; @@ -26,7 +27,7 @@ public TransactionBuilder paidTo(EconomyHandler handler) { } public TransactionBuilder paidToServer() { - this.receivingAccount = Account.SERVER_ACCOUNT; + this.receivingAccount = TownyServerAccount.ACCOUNT; return this; } @@ -41,7 +42,7 @@ public TransactionBuilder paidBy(EconomyHandler handler) { } public TransactionBuilder paidByServer() { - this.sendingAccount = Account.SERVER_ACCOUNT; + this.sendingAccount = TownyServerAccount.ACCOUNT; return this; } diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/permissions/PermissionNodes.java b/Towny/src/main/java/com/palmergames/bukkit/towny/permissions/PermissionNodes.java index 8e2e67cb972..24350373957 100644 --- a/Towny/src/main/java/com/palmergames/bukkit/towny/permissions/PermissionNodes.java +++ b/Towny/src/main/java/com/palmergames/bukkit/towny/permissions/PermissionNodes.java @@ -385,8 +385,11 @@ public enum PermissionNodes { TOWNY_COMMAND_TOWNYADMIN_CHECKOUTPOSTS("towny.command.townyadmin.checkoutposts"), TOWNY_COMMAND_TOWNYADMIN_UNCLAIM("towny.command.townyadmin.unclaim"), TOWNY_COMMAND_TOWNYADMIN_RESIDNET_DELETE("towny.command.townyadmin.resident.delete"), - TOWNY_COMMAND_TOWNYADMIN_DEPOSITALL("towny.command.townyadmin.depositall"), - TOWNY_COMMAND_TOWNYADMIN_RESETBANKS("towny.command.townyadmin.resetbanks"), + TOWNY_COMMAND_TOWNYADMIN_ECO("towny.command.townyadmin.eco.*"), + TOWNY_COMMAND_TOWNYADMIN_ECO_DEPOSITALL("towny.command.townyadmin.eco.depositall"), + TOWNY_COMMAND_TOWNYADMIN_ECO_RESETBANKS("towny.command.townyadmin.eco.resetbanks"), + TOWNY_COMMAND_TOWNYADMIN_ECO_CONVERT("towny.command.townyadmin.eco.convert"), + TOWNY_COMMAND_TOWNYADMIN_ECO_INFO("towny.command.townyadmin.eco.info"), TOWNY_COMMAND_TOWNYADMIN_TOWNYPERMS("towny.command.townyadmin.townyperms"), TOWNY_COMMAND_TOWNYADMIN_TPPLOT("towny.command.townyadmin.tpplot"), TOWNY_COMMAND_TOWNYADMIN_INSTALL("towny.command.townyadmin.install"), diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/utils/MoneyUtil.java b/Towny/src/main/java/com/palmergames/bukkit/towny/utils/MoneyUtil.java index e4fc02f695f..24b42ec9cdc 100644 --- a/Towny/src/main/java/com/palmergames/bukkit/towny/utils/MoneyUtil.java +++ b/Towny/src/main/java/com/palmergames/bukkit/towny/utils/MoneyUtil.java @@ -3,20 +3,17 @@ import com.palmergames.bukkit.towny.object.Translatable; import com.palmergames.bukkit.towny.object.Translator; -import java.io.File; import java.math.BigDecimal; import java.math.RoundingMode; import org.bukkit.Location; import org.bukkit.entity.Player; -import com.palmergames.bukkit.towny.Towny; import com.palmergames.bukkit.towny.TownyAPI; import com.palmergames.bukkit.towny.TownyEconomyHandler; import com.palmergames.bukkit.towny.TownyFormatter; import com.palmergames.bukkit.towny.TownyMessaging; import com.palmergames.bukkit.towny.TownySettings; -import com.palmergames.bukkit.towny.TownyUniverse; import com.palmergames.bukkit.towny.event.economy.NationPreTransactionEvent; import com.palmergames.bukkit.towny.event.economy.NationTransactionEvent; import com.palmergames.bukkit.towny.event.economy.TownPreTransactionEvent; @@ -227,32 +224,6 @@ private static boolean isNotInOwnTown(Town town, Location loc) { return TownyAPI.getInstance().isWilderness(loc) || !town.equals(TownyAPI.getInstance().getTown(loc)); } - /** - * For a short time Towny stored debt accounts in the server's economy plugin. - * This practice had to end, being replaced with the debtBalance which is stored - * in the Town object. - */ - public static void checkLegacyDebtAccounts() { - File f = new File(TownyUniverse.getInstance().getRootFolder(), "debtAccountsConverted.txt"); - if (!f.exists()) - Towny.getPlugin().getScheduler().runAsyncLater(() -> TownyEconomyHandler.economyExecutor().execute(MoneyUtil::convertLegacyDebtAccounts), 600L); - } - - /** - * Will attempt to set a town's debtBalance if their old DebtAccount is above 0 and exists. - */ - private static void convertLegacyDebtAccounts() { - for (Town town : TownyUniverse.getInstance().getTowns()) { - final String name = "[DEBT]-" + town.getName(); - if (TownyEconomyHandler.hasAccount(name)) { - town.setDebtBalance(TownyEconomyHandler.getBalance(name, town.getAccount().getBukkitWorld())); - town.save(); - TownyEconomyHandler.setBalance(name, 0.0, town.getAccount().getBukkitWorld()); - } - } - Towny.getPlugin().saveResource("debtAccountsConverted.txt", false); - } - public static double getMoneyAboveZeroOrThrow(String input) throws TownyException { double amount; try { diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/utils/SpawnUtil.java b/Towny/src/main/java/com/palmergames/bukkit/towny/utils/SpawnUtil.java index c0cc8b0016f..26b7861685d 100644 --- a/Towny/src/main/java/com/palmergames/bukkit/towny/utils/SpawnUtil.java +++ b/Towny/src/main/java/com/palmergames/bukkit/towny/utils/SpawnUtil.java @@ -16,6 +16,7 @@ import com.palmergames.bukkit.towny.object.Translatable; import com.palmergames.bukkit.towny.object.WorldCoord; import com.palmergames.bukkit.towny.object.economy.Account; +import com.palmergames.bukkit.towny.object.economy.TownyServerAccount; import com.palmergames.bukkit.towny.object.spawnlevel.NationSpawnLevel; import com.palmergames.bukkit.towny.object.spawnlevel.TownSpawnLevel; @@ -108,7 +109,7 @@ public static void sendToTownySpawn(Player player, String[] split, TownyObject t if (spawnInfo.travelCost > 0) { // Get paymentMsg for the money.csv and the Account being paid. final String paymentMsg = getPaymentMsg(spawnInfo.townSpawnLevel, spawnInfo.nationSpawnLevel, spawnType); - final Account payee = TownySettings.isTownSpawnPaidToTown() ? getPayee(town, nation, spawnType) : Account.SERVER_ACCOUNT; + final Account payee = TownySettings.isTownSpawnPaidToTown() ? getPayee(town, nation, spawnType) : TownyServerAccount.ACCOUNT; initiateCostedSpawn(player, resident, spawnLoc, spawnInfo.travelCost, payee, paymentMsg, ignoreWarn, spawnInfo.cooldown); // No Cost so skip confirmation system. } else @@ -738,7 +739,7 @@ private static String getPaymentMsg(TownSpawnLevel townSpawnLevel, NationSpawnLe */ private static Account getPayee(Town town, Nation nation, SpawnType spawnType) { return switch(spawnType) { - case RESIDENT -> town == null ? Account.SERVER_ACCOUNT : town.getAccount(); + case RESIDENT -> town == null ? TownyServerAccount.ACCOUNT : town.getAccount(); case TOWN -> town.getAccount(); case NATION -> nation.getAccount(); }; diff --git a/Towny/src/main/java/com/palmergames/util/JavaUtil.java b/Towny/src/main/java/com/palmergames/util/JavaUtil.java index 29b43c6848f..6bc5ffa1191 100644 --- a/Towny/src/main/java/com/palmergames/util/JavaUtil.java +++ b/Towny/src/main/java/com/palmergames/util/JavaUtil.java @@ -1,5 +1,6 @@ package com.palmergames.util; +import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -12,11 +13,13 @@ import java.lang.invoke.MethodHandles; import java.lang.reflect.Field; import java.lang.reflect.Method; +import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.nio.file.CopyOption; import java.nio.file.Files; import java.nio.file.Path; import java.util.List; +import java.util.UUID; import java.util.function.Consumer; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -122,4 +125,25 @@ public static T make(T initial, Consumer initializer) { return null; } } + + @NotNull + @Contract(pure = true) + public static UUID changeUUIDVersion(final @NotNull UUID uuid, final int version) { + if (uuid.version() == version) + return uuid; + + final ByteBuffer buffer = ByteBuffer.wrap(new byte[16]); + buffer.putLong(uuid.getMostSignificantBits()); + buffer.putLong(uuid.getLeastSignificantBits()); + + final byte[] bytes = buffer.array(); + + bytes[6] = (byte) (version << 4); + + final ByteBuffer rewrapped = ByteBuffer.wrap(bytes); + final long mostSig = rewrapped.getLong(); + final long leastSig = rewrapped.getLong(); + + return new UUID(mostSig, leastSig); + } } diff --git a/Towny/src/main/resources/config-migration.json b/Towny/src/main/resources/config-migration.json index a13086590e2..86b2c8e5803 100644 --- a/Towny/src/main/resources/config-migration.json +++ b/Towny/src/main/resources/config-migration.json @@ -875,5 +875,14 @@ "value": "0" } ] + }, + { + "version": "0.100.4.6", + "changes": [ + { + "type": "RUNNABLE", + "key": "disable_modern_eco" + } + ] } ] \ No newline at end of file diff --git a/Towny/src/main/resources/debtAccountsConverted.txt b/Towny/src/main/resources/debtAccountsConverted.txt deleted file mode 100644 index 86eee3b8268..00000000000 --- a/Towny/src/main/resources/debtAccountsConverted.txt +++ /dev/null @@ -1,6 +0,0 @@ -If this file doesn't exist (you delete it), -Towny will automatically attempt to convert -old debt accounts used in Towny 0.96.3.0 to -0.96.5.0. - -99% of servers can leave this file alone. \ No newline at end of file diff --git a/Towny/src/main/resources/lang/en-US.yml b/Towny/src/main/resources/lang/en-US.yml index 0361ec7290a..7bb6bc5afa8 100644 --- a/Towny/src/main/resources/lang/en-US.yml +++ b/Towny/src/main/resources/lang/en-US.yml @@ -426,8 +426,17 @@ ta_reload_help_2: "Reloads language files." ta_reload_help_3: "Reloads Towny permissions." ta_reload_help_4: "Reloads all components of Towny." +ta_eco_resetbanks_help: "Resets town and nation banks to the given amount or 0." +ta_eco_convert_modern_help: "Converts your Towny accounts into our modern variety, able to use Vault's OfflinePlayer." +ta_eco_convert_help: "Converts your Towny accounts to the target economy implementation, useful for transferring data between economy plugins." +ta_eco_info_help: "Gives information about economy accounts on the server." ta_depositall_help_0: "Deposit the given amount into all town and nation banks." +ta_info_help_0: "View eco info for the given nation." +ta_info_help_1: "View eco info for the given resident." +ta_info_help_2: "View eco info for the towny server account." +ta_info_help_3: "View eco info for the given town." + plot_help_0: "Turns on the plot info HUD." plot_help_1: "Removes the owner of the plot." plot_help_2: "Deletes a list of blocks from the plot." @@ -2590,4 +2599,14 @@ msg_your_nation_has_pending_ally_invites: "Your nation has invitations become an msg_your_town_has_pending_nation_invites: "Your town has invitations to join one or more nations. Use '/t invite received' to view your invitations." -msg_you_have_pending_town_invites: "You have invitations to join one or more towns. Use '/invite list' to view your invitations, or use '/invite deny all' to decline all of these invites." \ No newline at end of file +msg_you_have_pending_town_invites: "You have invitations to join one or more towns. Use '/invite list' to view your invitations, or use '/invite deny all' to decline all of these invites." + +msg_admin_eco_convert_no_providers: "No economy providers available" +msg_admin_eco_convert_no_provider_specified: "No economy provider specified" +msg_admin_eco_convert_no_target_specified: "No target economy specified" +msg_admin_eco_convert_target_not_found: "No economy adapter found by name '%s', available adapters: [%s]" +msg_admin_eco_convert_already_modern: "Cannot convert to modern; provider is already in modern mode" +msg_admin_eco_convert_online_players_warning: "Warning: It is recommended to convert while there are no players on the server." +msg_admin_eco_convert_x_accounts_found: "Found %s accounts to convert. Beginning conversion now." +msg_admin_eco_convert_conversion_progress: "Account conversion in progress, %s complete..." +msg_admin_eco_convert_success: "Economy conversion successful" diff --git a/Towny/src/main/resources/plugin.yml b/Towny/src/main/resources/plugin.yml index 03f683aca1b..8cb8a224fe7 100644 --- a/Towny/src/main/resources/plugin.yml +++ b/Towny/src/main/resources/plugin.yml @@ -696,8 +696,7 @@ permissions: towny.command.townyadmin.plot.*: true towny.command.townyadmin.tpplot: true towny.command.townyadmin.resident.*: true - towny.command.townyadmin.depositall: true - towny.command.townyadmin.resetbanks: true + towny.command.townyadmin.eco.*: true towny.command.townyadmin.install: true towny.command.townyadmin.resident.*: @@ -710,6 +709,15 @@ permissions: towny.command.townyadmin.resident.friend: true towny.command.townyadmin.resident.unjail: true + towny.command.townyadmin.eco.*: + description: User can access the admin eco commands. + default: false + children: + towny.command.townyadmin.eco.depositall: true + towny.command.townyadmin.eco.resetbanks: true + towny.command.townyadmin.eco.convert: true + towny.command.townyadmin.eco.info: true + towny.command.townyadmin.town.spawn.*: description: User can access town spawns default: false diff --git a/Towny/src/test/java/com/palmergames/util/JavaUtilTests.java b/Towny/src/test/java/com/palmergames/util/JavaUtilTests.java new file mode 100644 index 00000000000..b1d97ab060b --- /dev/null +++ b/Towny/src/test/java/com/palmergames/util/JavaUtilTests.java @@ -0,0 +1,24 @@ +package com.palmergames.util; + +import org.junit.jupiter.api.Test; + +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.*; + +public class JavaUtilTests { + @Test + void testSetUUIDVersion() { + final UUID uuid = UUID.randomUUID(); + final UUID startUUID = new UUID(uuid.getMostSignificantBits(), uuid.getLeastSignificantBits()); + + assertEquals(4, uuid.version()); + + for (int i = 0; i < 16; i++) { + assertEquals(i, JavaUtil.changeUUIDVersion(uuid, i).version()); + } + + JavaUtil.changeUUIDVersion(uuid, 4); + assertEquals(startUUID, uuid); + } +}