Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand Down
15 changes: 10 additions & 5 deletions docs/DEVELOPER_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,7 @@ override fun entries(): List<MenuEntry> =
| `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. |

Expand Down Expand Up @@ -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`:

Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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<Int>
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<Int>): List<Int> = amounts.filter { it > 0 }.distinct().sorted().take(9)

companion object {
private val DEFAULT_BANK_AMOUNTS = listOf(10, 100, 1000, 10000)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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<String> = 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 }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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)
Expand Down
9 changes: 9 additions & 0 deletions src/main/resources/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Loading