diff --git a/CHANGELOG.md b/CHANGELOG.md index d6c53ce..80c8b86 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,39 @@ ## Unreleased +## Version 1.0.2 + +### New Features + +#### Bank Menu + ++ Deposit and Withdraw now open a menu of amounts instead of going straight to a typing dialog: the preset amounts the + server offers, an Everything button, and Custom Amount for typing your own figure. ++ Each amount shows whether it will actually work. An amount you cannot afford, or one outside the minimum and maximum + Towny allows, says so instead of offering a click, so a button that would only earn an error is never offered. ++ The amount menu shows the bank balance, your own balance, and Towny's deposit or withdrawal limits when the server + sets any. + +### Improvements + +#### Bank Menu + ++ The Deposit, Withdraw, and Bank History buttons now have a blank row above and below them instead of being wedged + between the bank icon and the bottom row, so the bank details, the actions, and the navigation read as three + separate blocks. The menu is one row taller for it, and the amount menu is laid out the same way. + +#### Admin Menus + ++ TownyMenu Settings can edit the bank menu's preset amounts. + +### Technical Details + +#### Misc + ++ New `bank-amounts` setting in `config.yml` lists the preset amounts the deposit and withdraw menus offer. Amounts + that are zero or negative are dropped, duplicates removed, and the rest sorted; up to nine fit in the menu row. An + empty list leaves players with only Everything and Custom Amount. + ## Version 1.0.1 ### Improvements diff --git a/README.md b/README.md index 9e21846..d95a546 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,7 @@ to copy them and start the server. Before the first start, download a Paper 26.2 jar from [papermc.io](https://papermc.io/downloads/paper) into `run/`, then accept the EULA in `run/eula.txt` after the first launch. -Prebuilt jars are attached to every [GitHub release](https://github.com/Trilleo/TownyMenu/releases); see the +Prebuilt jars are attached to every [GitHub release](https://github.com/TriBinary/TownyMenu/releases); see the [change log](CHANGELOG.md) for what changed in each one. ## Commands @@ -102,6 +102,7 @@ All commands are sub-commands of `/townymenu` (alias `/tm`). | `tutorial-join-hint` | `true` | Suggest the tutorial to players without a town when they join | | `live-menu-refresh` | `true` | Update open menus when Towny changes what they show | | `towny-alerts` | `true` | Announce invites, bankruptcy, and ruin in chat, linked to the right menu | +| `bank-amounts` | `10, 100, 1000, 10000` | Preset amounts the deposit and withdraw menus offer, alongside Everything and a custom amount | ## Translations diff --git a/docs/DEVELOPER_GUIDE.md b/docs/DEVELOPER_GUIDE.md index 3b7dfe9..ac37c3b 100644 --- a/docs/DEVELOPER_GUIDE.md +++ b/docs/DEVELOPER_GUIDE.md @@ -362,6 +362,7 @@ override fun entries(): List = | `ToggleMenu` | Pages of `Toggle`s, 21 per page (material, name, description, node, command, value reader). A `null` node leaves the toggle unguarded, for modes Towny checks no node for. | | `PermissionMenu` | 4×4 build/destroy/switch/item-use grid for any `set perm` command. Pass `overrides` to add a Player Overrides button (used for plots). | | `BankMenu` | Deposit, withdraw, and bank history for a town or nation. | +| `BankAmountMenu` | Picks how much to deposit or withdraw: the `bank-amounts` presets, everything, or a typed figure. Amounts Towny would refuse say why instead of offering a click. | | `RankMenu` | Grants or revokes town or nation ranks, checking the per-rank permission node. | | `Pickers` | Selection menus for online residents, towns, nations, and fixed options. `option` takes a `lines` lambda for per-option lore, such as what picking that option costs. | @@ -1156,11 +1157,15 @@ sneak-swap-hand-shortcut: true ### Properties -| Property | Key | Description | -|:------------------------|:---------------------------|:-----------------------------------------------| -| `messagePrefix` | `message-prefix` | MiniMessage prefix used by `MessageUtil` | -| `language` | `language` | `auto` or the language id given to `Lang.load` | -| `sneakSwapHandShortcut` | `sneak-swap-hand-shortcut` | Whether sneak + swap-hand opens the main menu | +| Property | Key | Description | +|:------------------------|:---------------------------|:-------------------------------------------------------------------| +| `messagePrefix` | `message-prefix` | MiniMessage prefix used by `MessageUtil` | +| `language` | `language` | `auto` or the language id given to `Lang.load` | +| `sneakSwapHandShortcut` | `sneak-swap-hand-shortcut` | Whether sneak + swap-hand opens the main menu | +| `tutorialJoinHint` | `tutorial-join-hint` | Whether players without a town are pointed to the tutorial on join | +| `liveMenuRefresh` | `live-menu-refresh` | Whether open menus re-render when Towny changes what they show | +| `townyAlerts` | `towny-alerts` | Whether invitations, bankruptcy, and ruin are announced in chat | +| `bankAmounts` | `bank-amounts` | Preset amounts `BankAmountMenu` offers, cleaned and capped at nine | To add a setting, add the key to `config.yml` and a matching property to `PluginConfig`: diff --git a/gradle.properties b/gradle.properties index d2496dc..0ef051f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,7 +1,7 @@ kotlin.code.style=official # Plugin Properties -plugin_version=1.0.1 +plugin_version=1.0.2 # Dependency Versions towny_version=0.103.2.7 diff --git a/src/main/kotlin/net/trilleo/mc/plugins/townymenu/config/PluginConfig.kt b/src/main/kotlin/net/trilleo/mc/plugins/townymenu/config/PluginConfig.kt index e9ddcf2..414fb5f 100644 --- a/src/main/kotlin/net/trilleo/mc/plugins/townymenu/config/PluginConfig.kt +++ b/src/main/kotlin/net/trilleo/mc/plugins/townymenu/config/PluginConfig.kt @@ -56,8 +56,27 @@ class PluginConfig(private val plugin: JavaPlugin) { get() = plugin.config.getBoolean("towny-alerts", true) set(value) = save("towny-alerts", value) + /** + * The preset amounts the deposit and withdraw menus offer (`bank-amounts`). + * + * Towny's bank commands take whole numbers, and only nine buttons fit in the menu row, + * so the list is cleaned up on the way in and out: non-positive amounts are dropped, + * duplicates removed, and the rest sorted and capped at nine. An empty list is a choice — + * the menus then offer only "All" and a custom amount — so only a missing key falls back. + */ + var bankAmounts: List + get() = if (plugin.config.isSet("bank-amounts")) clean(plugin.config.getIntegerList("bank-amounts")) + else DEFAULT_BANK_AMOUNTS + set(value) = save("bank-amounts", clean(value)) + private fun save(path: String, value: Any) { plugin.config.set(path, value) plugin.saveConfig() } + + private fun clean(amounts: List): List = amounts.filter { it > 0 }.distinct().sorted().take(9) + + companion object { + private val DEFAULT_BANK_AMOUNTS = listOf(10, 100, 1000, 10000) + } } diff --git a/src/main/kotlin/net/trilleo/mc/plugins/townymenu/guis/admin/PluginSettingsMenu.kt b/src/main/kotlin/net/trilleo/mc/plugins/townymenu/guis/admin/PluginSettingsMenu.kt index f657ed0..48652b0 100644 --- a/src/main/kotlin/net/trilleo/mc/plugins/townymenu/guis/admin/PluginSettingsMenu.kt +++ b/src/main/kotlin/net/trilleo/mc/plugins/townymenu/guis/admin/PluginSettingsMenu.kt @@ -93,6 +93,22 @@ class PluginSettingsMenu(player: Player, back: Menu) : Menu(player, player.tr("a config.townyAlerts = !config.townyAlerts apply() } + row.add( + Icons.icon( + Material.GOLD_NUGGET, tr("admin-plugin.bank-amounts"), tr("admin-plugin.bank-amounts-description"), + tr("common.current", "value" to config.bankAmounts.joinToString(", ").ifEmpty { tr("common.none") }), + actions = listOf(tr("common.click-change")) + ) + ) { + prompt( + tr("admin-plugin.bank-amounts"), + tr("admin-plugin.bank-amounts-label"), + initial = config.bankAmounts.joinToString(", ") + ) { input -> + config.bankAmounts = input.split(',', ' ').mapNotNull { it.trim().toIntOrNull() } + apply() + } + } backButton(31) } diff --git a/src/main/kotlin/net/trilleo/mc/plugins/townymenu/guis/common/BankAmountMenu.kt b/src/main/kotlin/net/trilleo/mc/plugins/townymenu/guis/common/BankAmountMenu.kt new file mode 100644 index 0000000..ee29135 --- /dev/null +++ b/src/main/kotlin/net/trilleo/mc/plugins/townymenu/guis/common/BankAmountMenu.kt @@ -0,0 +1,151 @@ +package net.trilleo.mc.plugins.townymenu.guis.common + +import com.palmergames.bukkit.towny.TownySettings +import com.palmergames.bukkit.towny.`object`.Government +import com.palmergames.bukkit.towny.`object`.Nation +import net.trilleo.mc.plugins.townymenu.Main +import net.trilleo.mc.plugins.townymenu.guis.framework.Icons +import net.trilleo.mc.plugins.townymenu.guis.framework.Menu +import net.trilleo.mc.plugins.townymenu.utils.TownyUtil +import net.trilleo.mc.plugins.townymenu.utils.tr +import org.bukkit.Material +import org.bukkit.entity.Player +import kotlin.math.floor + +/** + * Picks how much to move in or out of a bank: one of the server's preset amounts + * (`bank-amounts` in `config.yml`), everything the source holds, or a typed figure. + * + * Presets the transaction would fail on — too little money, or outside Towny's own + * minimum and maximum — are shown with the reason instead of a click hint, so a + * button that cannot work is never offered. + */ +class BankAmountMenu( + player: Player, + private val government: Government, + private val withdraw: Boolean, + private val bank: Menu, +) : Menu( + player, + player.tr( + if (withdraw) "bank.withdraw-menu-title" else "bank.deposit-menu-title", + "name" to TownyUtil.name(government.name) + ), + 5, + bank, +) { + + private val isNation = government is Nation + private val command = if (isNation) "towny:nation" else "towny:town" + + /** + * The balance the money comes out of: the bank when withdrawing, the player's own when depositing. + * Read once per render, because every amount is measured against it and the economy plugin answers each query. + */ + private var available = 0.0 + + override fun build() { + val own = resident?.account?.holdingBalance ?: 0.0 + available = if (withdraw) balance() else own + + button( + 4, Icons.icon( + if (withdraw) Material.REDSTONE else Material.EMERALD, + tr(if (withdraw) "bank.withdraw" else "bank.deposit"), + tr(if (withdraw) "bank.withdraw-description" else "bank.deposit-description"), + *infoLines(own).toTypedArray() + ) + ) + + val amounts = Main.instance.pluginConfig.bankAmounts + val row = layout(*centered(2, amounts.size + 2)) + amounts.forEach { amount -> + add(row, Material.GOLD_NUGGET, tr("bank.amount", "amount" to TownyUtil.money(amount.toDouble())), amount) + } + val everything = floor(available).toInt() + add( + row, Material.GOLD_BLOCK, tr("bank.all"), everything, "all", + tr("bank.amount-line", "amount" to TownyUtil.money(everything.toDouble())) + ) + row.add( + Icons.icon( + Material.NAME_TAG, tr("bank.custom"), tr("bank.custom-description"), + actions = listOf(tr("common.click-change")) + ) + ) { + prompt( + tr(if (withdraw) "bank.withdraw-title" else "bank.deposit-title"), + tr("common.amount"), + tr("bank.prompt-balance", "balance" to TownyUtil.balance(government)) + ) { amount -> + transact(TownyUtil.argument(amount)) + } + } + + backButton(40) + } + + /** Adds an amount button, or, when Towny would refuse [amount], an unclickable icon saying why. */ + private fun add( + row: Layout, + material: Material, + name: String, + amount: Int, + argument: String = "$amount", + line: String? = null, + ) { + val problem = problem(amount) + val icon = Icons.icon( + material, name, null, + *listOfNotNull(line, problem).toTypedArray(), + actions = if (problem == null) listOf(tr(if (withdraw) "bank.click-withdraw" else "bank.click-deposit")) + else emptyList() + ) + if (problem == null) row.add(icon) { transact(argument) } else row.add(icon) + } + + /** Why Towny would refuse [amount], or `null` when it would go through. */ + private fun problem(amount: Int): String? = when { + amount <= 0 -> tr("bank.nothing-to-move") + amount > available -> tr("bank.not-enough") + amount < minimum -> tr("bank.below-minimum", "amount" to TownyUtil.money(minimum.toDouble())) + maximum > -1 && amount > maximum -> tr("bank.above-maximum", "amount" to TownyUtil.money(maximum.toDouble())) + else -> null + } + + private fun transact(argument: String) { + run("$command ${if (withdraw) "withdraw" else "deposit"} $argument", ::balance, returnTo = bank) + } + + private fun infoLines(own: Double): List = buildList { + add(tr("bank.balance", "balance" to TownyUtil.balance(government))) + add(tr("bank.your-balance", "balance" to TownyUtil.money(own))) + if (minimum > 0) add(tr("bank.minimum", "amount" to TownyUtil.money(minimum.toDouble()))) + if (maximum > -1) add(tr("bank.maximum", "amount" to TownyUtil.money(maximum.toDouble()))) + } + + private val minimum: Int + get() = when { + isNation && withdraw -> TownySettings.getNationMinWithdraw() + isNation -> TownySettings.getNationMinDeposit() + withdraw -> TownySettings.getTownMinWithdraw() + else -> TownySettings.getTownMinDeposit() + } + + private val maximum: Int + get() = when { + isNation && withdraw -> TownySettings.getNationMaxWithdraw() + isNation -> TownySettings.getNationMaxDeposit() + withdraw -> TownySettings.getTownMaxWithdraw() + else -> TownySettings.getTownMaxDeposit() + } + + private fun balance(): Double = government.account.holdingBalance + + /** Up to nine slots in [row], centred, so a short list of presets sits under the icon above it. */ + private fun centered(row: Int, count: Int): IntArray { + val size = count.coerceIn(1, 9) + val start = row * 9 + (9 - size) / 2 + return IntArray(size) { start + it } + } +} diff --git a/src/main/kotlin/net/trilleo/mc/plugins/townymenu/guis/common/BankMenu.kt b/src/main/kotlin/net/trilleo/mc/plugins/townymenu/guis/common/BankMenu.kt index 1058c13..1a04e29 100644 --- a/src/main/kotlin/net/trilleo/mc/plugins/townymenu/guis/common/BankMenu.kt +++ b/src/main/kotlin/net/trilleo/mc/plugins/townymenu/guis/common/BankMenu.kt @@ -17,7 +17,7 @@ class BankMenu( player: Player, private val government: Government, back: Menu, -) : Menu(player, player.tr("bank.title", "name" to TownyUtil.name(government.name)), 4, back) { +) : Menu(player, player.tr("bank.title", "name" to TownyUtil.name(government.name)), 5, back) { private val isNation = government is Nation private val command = if (isNation) "towny:nation" else "towny:town" @@ -32,44 +32,39 @@ class BankMenu( ) guarded( - 11, + 20, if (isNation) PermissionNodes.TOWNY_COMMAND_NATION_DEPOSIT else PermissionNodes.TOWNY_COMMAND_TOWN_DEPOSIT, - Icons.icon(Material.EMERALD, tr("bank.deposit"), tr("bank.deposit-description")) + Icons.icon( + Material.EMERALD, tr("bank.deposit"), tr("bank.deposit-description"), + actions = listOf(tr("bank.click-choose")) + ) ) { - prompt( - tr("bank.deposit-title"), - tr("common.amount"), - tr("bank.prompt-balance", "balance" to TownyUtil.balance(government)) - ) { amount -> - run("$command deposit ${TownyUtil.argument(amount)}", ::balance) - } + BankAmountMenu(player, government, withdraw = false, bank = this).open() } guarded( - 13, + 22, if (isNation) PermissionNodes.TOWNY_COMMAND_NATION_WITHDRAW else PermissionNodes.TOWNY_COMMAND_TOWN_WITHDRAW, - Icons.icon(Material.REDSTONE, tr("bank.withdraw"), tr("bank.withdraw-description")) + Icons.icon( + Material.REDSTONE, tr("bank.withdraw"), tr("bank.withdraw-description"), + actions = listOf(tr("bank.click-choose")) + ) ) { - prompt( - tr("bank.withdraw-title"), - tr("common.amount"), - tr("bank.prompt-balance", "balance" to TownyUtil.balance(government)) - ) { amount -> - run("$command withdraw ${TownyUtil.argument(amount)}", ::balance) - } + BankAmountMenu(player, government, withdraw = true, bank = this).open() } guarded( - 15, + 24, if (isNation) PermissionNodes.TOWNY_COMMAND_NATION_BANKHISTORY else PermissionNodes.TOWNY_COMMAND_TOWN_BANKHISTORY, - Icons.icon(Material.WRITTEN_BOOK, tr("bank.history"), tr("bank.history-description")) + Icons.icon( + Material.WRITTEN_BOOK, tr("bank.history"), tr("bank.history-description"), + actions = listOf(tr("common.click-view")) + ) ) { runAndClose("$command bankhistory") } - tutorialButton(35, Tutorial.ECONOMY) - backButton(31) + tutorialButton(44, Tutorial.ECONOMY) + backButton(40) } - private fun balance(): Double = government.account.holdingBalance - private fun taxLabel(): String { val town = government as Town return if (town.isTaxPercentage) "${town.taxes}%" else TownyUtil.money(town.taxes) diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 9db3026..eb11fa8 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -23,3 +23,12 @@ live-menu-refresh: true # Announce in chat when a player is invited to a town, a town is invited to a nation, a nation is asked for an # alliance, or a town goes bankrupt or falls into ruin. Each message opens the menu that answers it. towny-alerts: true + +# The preset amounts offered by the deposit and withdraw menus, in the server's currency. +# Every player also gets an "All" button and a custom-amount dialog, so this list is only a shortcut. +# Towny takes whole numbers only; up to nine amounts fit in the menu row. +bank-amounts: + - 10 + - 100 + - 1000 + - 10000 diff --git a/src/main/resources/lang/en_US.yml b/src/main/resources/lang/en_US.yml index c283f73..c7453f4 100644 --- a/src/main/resources/lang/en_US.yml +++ b/src/main/resources/lang/en_US.yml @@ -207,6 +207,23 @@ bank: prompt-balance: "Bank balance: {balance}" history: "Bank History" history-description: "Open a book with recent transactions." + click-choose: "Click to choose an amount" + deposit-menu-title: "Deposit: {name}" + withdraw-menu-title: "Withdraw: {name}" + your-balance: "Your balance: {balance}" + minimum: "Minimum: {amount}" + maximum: "Maximum: {amount}" + amount: "{amount}" + amount-line: "Amount: {amount}" + all: "Everything" + custom: "Custom Amount" + custom-description: "Type any amount yourself." + click-deposit: "Click to deposit" + click-withdraw: "Click to withdraw" + not-enough: "Not enough money" + nothing-to-move: "Nothing to move" + below-minimum: "Below the {amount} minimum" + above-maximum: "Above the {amount} maximum" perm: gone: "This land no longer exists" @@ -1171,6 +1188,9 @@ admin-plugin: live-refresh-description: "Update open menus when Towny changes what they show, such as a resident joining or the new day." alerts: "Chat Alerts" alerts-description: "Announce invitations, bankruptcy, and ruin in chat, with a link to the menu that answers them." + bank-amounts: "Bank Amounts" + bank-amounts-description: "The preset amounts the deposit and withdraw menus offer. Players always get an Everything button and a custom amount too." + bank-amounts-label: "Amounts, separated by commas" admin-town: title: "Admin: {town}" diff --git a/src/main/resources/lang/zh_CN.yml b/src/main/resources/lang/zh_CN.yml index 7c17533..4c311ac 100644 --- a/src/main/resources/lang/zh_CN.yml +++ b/src/main/resources/lang/zh_CN.yml @@ -207,6 +207,23 @@ bank: prompt-balance: "银行余额: {balance}" history: "银行记录" history-description: "打开记录近期交易的书。" + click-choose: "点击选择金额" + deposit-menu-title: "存款: {name}" + withdraw-menu-title: "取款: {name}" + your-balance: "你的余额: {balance}" + minimum: "最低: {amount}" + maximum: "最高: {amount}" + amount: "{amount}" + amount-line: "金额: {amount}" + all: "全部" + custom: "自定义金额" + custom-description: "自行输入任意金额。" + click-deposit: "点击存入" + click-withdraw: "点击取出" + not-enough: "金钱不足" + nothing-to-move: "没有可转移的金额" + below-minimum: "低于最低限额 {amount}" + above-maximum: "高于最高限额 {amount}" perm: gone: "这片土地已不存在" @@ -1171,6 +1188,9 @@ admin-plugin: live-refresh-description: "当 Towny 改变菜单显示的内容时(例如居民加入或新的一天)刷新已打开的菜单。" alerts: "聊天提醒" alerts-description: "在聊天中提醒邀请、破产和废墟,并附上可打开对应菜单的链接。" + bank-amounts: "银行预设金额" + bank-amounts-description: "存款和取款菜单提供的预设金额。玩家始终还能使用“全部”按钮和自定义金额。" + bank-amounts-label: "金额,用逗号分隔" admin-town: title: "管理: {town}"