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 8e484b7366..b866a06f42 100644 --- a/Towny/src/main/java/com/palmergames/bukkit/config/ConfigNodes.java +++ b/Towny/src/main/java/com/palmergames/bukkit/config/ConfigNodes.java @@ -2924,6 +2924,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", + "2", + "", + "# 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.", + "# Set to -1 to disable 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 0b1859ce57..a796997bc9 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 07fc0975d3..8d23fa4f3b 100644 --- a/Towny/src/main/java/com/palmergames/bukkit/towny/TownyEconomyHandler.java +++ b/Towny/src/main/java/com/palmergames/bukkit/towny/TownyEconomyHandler.java @@ -1,28 +1,27 @@ package com.palmergames.bukkit.towny; -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 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.Optional; import java.util.UUID; import java.util.concurrent.Executor; @@ -36,7 +35,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 +51,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 +66,32 @@ public static String getServerAccount() { */ @Nullable public static UUID getTownyObjectUUID(String accountName) { + return Optional.ofNullable(getTownyObjectAccount(accountName)).map(Account::getUUID).orElse(null); + } + + @Nullable + public static Account getTownyObjectAccount(String accountName) { - if (accountName.equalsIgnoreCase(getServerAccount())) - return TownyServerAccount.getUUID(); + 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 +101,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 +114,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 +124,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 +131,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 +155,102 @@ public static boolean setupEconomy() { return false; } + @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 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 + 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 + 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 + 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 +258,27 @@ 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 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,18 +301,22 @@ 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 public static boolean hasAccount(String accountName) { - return economy.hasAccount(accountName); + final Account account = getTownyObjectAccount(accountName); + + return account != null && hasAccount(account); } public static boolean isEssentials() { @@ -315,4 +329,15 @@ 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; + } } \ No newline at end of file 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 75a2900bad..d583246083 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,6 +278,12 @@ public class TownyAdminCommand extends BaseCommand implements CommandExecutor { "townyperms", "all" ); + + private static final List adminEcoTabCompletes = Arrays.asList( + "resetbanks", + "depositall", + "convert" + ); public TownyAdminCommand(Towny towny) { this.plugin = towny; @@ -604,6 +615,31 @@ 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()) + convertChoices.add("modern"); + + return NameUtil.filterByStart(convertChoices, args[2]); + } + } + break; default: if (args.length == 1) @@ -643,8 +679,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])) { @@ -2788,9 +2823,18 @@ 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")); + + 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)); + } + } + + private void parseAdminDepositAllCommand(CommandSender sender, String[] split) throws TownyException { checkPermOrThrow(sender, PermissionNodes.TOWNY_COMMAND_TOWNYADMIN_DEPOSITALL.getNode()); if (split.length != 1) { HelpMenu.TA_DEPOSITALL.send(sender); @@ -2808,20 +2852,85 @@ private void parseAdminDepositAllCommand(CommandSender sender, String[] split) t } 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()); 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("No economy provider available"); + + if (args.length == 0) + throw new TownyException("No target economy specified"); + + EconomyAdapter target; + + final String targetName = String.join(" ", args); + if ("modern".equalsIgnoreCase(targetName)) { + if (!provider.isLegacy()) + throw new TownyException("Cannot convert to modern; provider is already in modern mode"); + + // 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("No economy adapter found by name " + targetName + ", available adapters: " + provider.economyAdapters().stream().map(EconomyAdapter::name).collect(Collectors.joining(", "))); + + 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()); + + for (Nation nation : TownyUniverse.getInstance().getNations()) + balances.put(nation.getAccount(), nation.getAccount().getHoldingBalance()); + + for (Resident resident : TownyUniverse.getInstance().getResidents()) + balances.put(resident.getAccount(), resident.getAccount().getHoldingBalance()); + + balances.put(TownyServerAccount.ACCOUNT, TownyServerAccount.ACCOUNT.getHoldingBalance()); + + // And set them to the target economy adapter + for (Map.Entry entry : balances.entrySet()) { + if (!target.hasAccount(entry.getKey())) + target.newAccount(entry.getKey()); + + target.setBalance(entry.getKey(), 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, "Economy conversion successful."); + })).sendTo(sender); + } 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 ae67a02a1a..49a3250515 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 @@ -110,9 +110,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." - : ""; + ecowarn = ""; MoneyUtil.checkLegacyDebtAccounts(); } 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 8b6da4617c..9cef22f4d7 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 fc4e892b20..568befac8e 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 cdbfb6a40a..afd2b90b49 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 cba6581f1e..5afe6b76eb 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 = Account.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 17753445f1..38d09dcd60 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,23 @@ * @see BankAccount * @see EconomyAccount */ -public abstract class Account implements Nameable { - public static final TownyServerAccount SERVER_ACCOUNT = TownyEconomyHandler.initializeTownyServerAccount(); +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; + 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) { + 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 +68,23 @@ 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 public Account(EconomyHandler economyHandler, String name, World world) { - this(economyHandler, name); - this.world = world; + this.name = name; + this.economyHandler = economyHandler; + + TownyWorld townyWorld = TownyAPI.getInstance().getTownyWorld(world); + this.worldSupplier = () -> townyWorld; + } + + @Deprecated + public Account(EconomyHandler economyHandler, String name) { + this.name = name; + this.economyHandler = economyHandler; + this.worldSupplier = () -> TownyUniverse.getInstance().getTownyWorlds().get(0); } // Template methods @@ -118,17 +147,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; } @@ -157,8 +186,14 @@ public synchronized boolean payTo(double amount, Account collector, String reaso * * @return Bukkit world for the object */ + @Nullable public World getBukkitWorld() { - return BukkitTools.getWorlds().get(0); + return getWorld().getBukkitWorld(); + } + + @NotNull + public TownyWorld getWorld() { + return this.worldSupplier.get(); } /** @@ -195,7 +230,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 +243,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 +260,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); } /** @@ -248,13 +284,26 @@ public String getName() { public void setName(String name) { this.name = name; } + + /** + * @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; + } /** * Gets the observers of this account. * * @return A list of account observers. */ - public List getObservers() { + public @Unmodifiable List getObservers() { return Collections.unmodifiableList(observers); } @@ -314,8 +363,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 +413,55 @@ 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; + } + + try { + final Object gameProfile = GAMEPROFILE_CONSTRUCTOR.invoke(this.uuid, this.name); + + return (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 + } + } + + @ApiStatus.Internal + public static UUID modifyNPCUUID(final UUID uuid) { + final int version = TownySettings.getInt(ConfigNodes.ECO_ADVANCED_NPC_UUID_VERSION); + if (version < 0 || version > 15) + return uuid; + + return JavaUtil.changeUUIDVersion(uuid, version); + } } 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 42b7c95623..f4a7d3de66 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, Account.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; @@ -199,9 +198,9 @@ private synchronized boolean removeDebt(double amount) { // 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()); + double bankBalance = getHoldingBalance(); //Set positive balance in regular account - TownyEconomyHandler.setBalance(getName(), bankBalance + netMoney, world); + TownyEconomyHandler.setBalance(this, bankBalance + 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 7dbab58661..f416d2ebce 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 static final TownyServerAccount ACCOUNT = new TownyServerAccount(); - 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 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 247063dc44..0000000000 --- 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 f908649aa1..97903e6b5f 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 5536e85015..ec9ac5a743 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 f828f9c183..bf47855365 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 0000000000..8ec78a9646 --- /dev/null +++ b/Towny/src/main/java/com/palmergames/bukkit/towny/object/economy/provider/EconomyProvider.java @@ -0,0 +1,46 @@ +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; + +public abstract class EconomyProvider { + 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 0000000000..14e200d5be --- /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 class ReserveEconomyProvider extends EconomyProvider { + private final Reserve reserve; + private final Function adapterFunction = api -> !isLegacy() ? new ReserveEconomyAdapter.Legacy(api) : new ReserveEconomyAdapter(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 0000000000..1121d2a097 --- /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 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 fc66862cc1..70b0b139e9 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 95dce01793..e9dea9b2bb 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 @@ -386,6 +386,7 @@ public enum PermissionNodes { 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_CONVERT("towny.command.townyadmin.eco.convert"), 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 e4fc02f695..3fb9fd2d06 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 @@ -7,6 +7,8 @@ import java.math.BigDecimal; import java.math.RoundingMode; +import com.palmergames.bukkit.towny.object.economy.Account; +import com.palmergames.bukkit.towny.object.economy.BankAccount; import org.bukkit.Location; import org.bukkit.entity.Player; @@ -244,10 +246,12 @@ public static void checkLegacyDebtAccounts() { 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())); + final Account debtAccount = new BankAccount(name, town); + + if (TownyEconomyHandler.hasAccount(debtAccount)) { + town.setDebtBalance(TownyEconomyHandler.getBalance(debtAccount)); town.save(); - TownyEconomyHandler.setBalance(name, 0.0, town.getAccount().getBukkitWorld()); + TownyEconomyHandler.setBalance(debtAccount, 0); } } Towny.getPlugin().saveResource("debtAccountsConverted.txt", false); 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 9d372bcc03..f26c76664b 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 @@ -13,6 +13,7 @@ import com.palmergames.bukkit.towny.object.SpawnInformation; import com.palmergames.bukkit.towny.object.Translatable; 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; @@ -100,7 +101,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 @@ -614,7 +615,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 29b43c6848..6bc5ffa119 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 494cab0ac5..1256932cb6 100644 --- a/Towny/src/main/resources/config-migration.json +++ b/Towny/src/main/resources/config-migration.json @@ -865,5 +865,14 @@ "key": "update_farm_blocks" } ] + }, + { + "version": "0.100.2.10", + "changes": [ + { + "type": "RUNNABLE", + "key": "disable_modern_eco" + } + ] } ] \ No newline at end of file diff --git a/Towny/src/main/resources/plugin.yml b/Towny/src/main/resources/plugin.yml index e4d79bc31e..671b3ef215 100644 --- a/Towny/src/main/resources/plugin.yml +++ b/Towny/src/main/resources/plugin.yml @@ -698,6 +698,7 @@ permissions: towny.command.townyadmin.resident.*: true towny.command.townyadmin.depositall: true towny.command.townyadmin.resetbanks: true + towny.command.townyadmin.eco.convert: true towny.command.townyadmin.install: true towny.command.townyadmin.resident.*: 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 0000000000..b1d97ab060 --- /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); + } +}