Numerical Storage
Overview
Numerical Storage lets you create named numeric stores for each player: a bank account, a mana pool, a token wallet, a reserve of any resource. You describe a store once in a definition, and the extension saves one balance and one level per player.
A store can have levels, each with a maximum capacity. Players deposit into and withdraw from it through an inventory menu built with the GUI extension. The money can come from your economy plugin through Vault, or from any placeholder and console command you choose. Interest can be added on a schedule, and stores can be reset, edited or opened with commands.
Balances are saved in a TypeWriter artifact and written asynchronously, so a deposit never blocks the server thread. The balances, levels and capacity are exposed as PlaceholderAPI placeholders for scoreboards, holograms and menus.
Features
- Several stores — create as many definitions as you need, each with its own name, prefix, levels and menu.
- Levels and capacity — every level has a maximum balance; players upgrade to raise it.
- Level requirements — unlock a level with placeholder or fact comparisons, and optionally take a cost.
- Interest — a cron schedule, a global rate, a rate per level and a rate per permission, with a cap per cycle.
- Vault or commands — deposits and withdrawals go through Vault, or through a placeholder plus console commands.
- Transfers — move a balance between two stores atomically.
- Menu buttons — fixed amount, percentage, all, custom amount (dialog), sub-menus, upgrade and close.
- Safe persistence — asynchronous saves, one lock per artifact and an automatic migration of old files.
- Profile mode — optional per-profile balances with MMOProfiles.
- Placeholders and commands — read values anywhere and edit them from the console.
Installation
- Install the GUI extension: Numerical Storage needs it for its menus. The GUI extension is published under the name
GuiAndDialogs. - Install PlaceholderAPI if you want to use the
%typewriter_ns_...%placeholders. - Optional: install Vault (the extension is built against VaultUnlocked) and an economy plugin, to use the
VAULTtransaction mode. - Optional: install MMOProfiles to use
profileMode. - Put the Numerical Storage JAR in
plugins/Typewriter/extensions/and restart the server.
The custom amount window uses Paper dialogs, so it needs a Paper version that supports them (Minecraft 1.21.6 or later).
Since version 0.9, new artifacts use a technical identifier and a JSON schema version 2. The first time an old file is read, it is copied to the new path, saved under backups/numericalstorage/ and removed. Keep a copy of the TypeWriter assets/ folder before you update.
Getting started
Create a bank paid with your Vault economy:
- In the TypeWriter web panel, open a manifest page and add a Player Numerical Storage artifact. Leave
artifactIdempty: TypeWriter generates it. - Add a Numerical Storage Definition. Set
displayName(for exampleBank),prefixand link the artifact inartifact. - In
levels, add the first level with alimitof1000, then a second one with5000. FilldepositMessageandlevelUpMessageif you want custom text. - In
transaction, settransactionModetoVAULT. - In
menu.transactionButtons, add a button of typeDEPOSITwithamountTypeFIXED, anamountof100, and the item to show. Add a second one of typeWITHDRAW. - Add a Open Numerical Storage Menu action. Link the
definition. - In its
layoutPool, add asimplelayout with theidmainand setmainLayoutIdtomain. In the layout, add an item withbuttonPrefixset tonumericalstorage_button:andbuttonTypeset toDEPOSIT_FIXED:100. Do the same withWITHDRAW_FIXED:100,UPGRADE_LEVELandNAV_CLOSE. - Link the action to an NPC dialogue or a command event, publish, and open the bank in game. You can also run
/typewriter ns open <definition>.
After each transaction the menu is reopened to show the new balance. The extension looks for an Open Numerical Storage Menu entry linked to the same definition. Without one, the menu is simply closed.
How it works
Menus
The menu is a normal GUI layout. The Open Menu action opens it with the title and number of rows of the definition (menu.title, menu.rows, 1 to 6), and your layoutPool decides where each item goes. The extension turns an item into a working button when the item has buttonPrefix set to numericalstorage_button: and a buttonType from this list:
buttonType | What the button does |
|---|---|
DEPOSIT_FIXED | Deposit a fixed amount. Add the amount after a colon, for example DEPOSIT_FIXED:100. |
DEPOSIT_PERCENT | Deposit a percentage of the source balance, for example DEPOSIT_PERCENT:50. |
DEPOSIT_CUSTOM | Close the menu and ask for an amount in a dialog. |
DEPOSIT_ALL | Deposit the source balance, up to the remaining capacity. This needs an amountPlaceholder: in VAULT mode, the button tries to deposit the whole remaining capacity and fails if the economy balance is lower. |
DEPOSIT_SUBMENU | Open a sub-menu of deposit buttons. The sub-menu id follows the colon. |
WITHDRAW_FIXED, WITHDRAW_PERCENT, WITHDRAW_CUSTOM, WITHDRAW_ALL, WITHDRAW_SUBMENU | Same as above, for withdrawals. Percent is taken from the stored balance. |
TRANSFER_FIXED, TRANSFER_PERCENT, TRANSFER_ALL | Move balance to another definition. The button comes from a transactionButtons entry of type TRANSFER with a transferTarget. |
SUB_MENU | Open the sub-menu whose id follows the colon, for example SUB_MENU:deposit_options. |
UPGRADE_LEVEL | Upgrade to the next level, or show the max level item. |
NAV_CLOSE | Close the menu. |
When buttonType has an amount (DEPOSIT_FIXED:100), the extension picks the entry of menu.transactionButtons with the same type, amount type and amount, and uses its template as the item. Without an amount, it uses the first matching entry. When nothing matches, a plain dye item with the default label is shown.
Sub-menus are defined in menu.subMenus, as a map from an id to a title, a number of rows, transaction buttons placed in slots, a back button and a close button.
A transfer button needs the transferTarget of the other definition (its entry id), and the player must have room in the target store.
Commands
All commands are sub-commands of /typewriter (alias /tw). <definition> is a definition entry. The optional [target] selects another player; without it the command applies to the sender.
| Command | Permission | Description |
|---|---|---|
/typewriter ns open <definition> [target] | typewriter.ns.open | Open the menu of the storage, through its Open Menu entry. |
/typewriter ns add <definition> <amount> [target] | typewriter.ns.add | Add to the balance. The capacity is not checked. |
/typewriter ns remove <definition> <amount> [target] | typewriter.ns.remove | Remove from the balance, down to 0. |
/typewriter ns level <definition> <level> [target] | typewriter.ns.level | Set the level and reset the balance to 0. |
/typewriter ns reset <definition> [target] | typewriter.ns.reset | Reset the level to 1 and the balance to 0. |
The commands /typewriter numstorage tx ..., upgrade and back_main are used internally by the menu buttons. They do nothing for a player without an open menu.
With profileMode on, add, remove, level and reset change the balance and level of the target's profile, like the menu does.
Command replies
The replies of add, remove, level, reset and open, and the error replies below, are the fields of adminMessages on the definition. They are MiniMessage. Each one can use <player>, <storage> (the id of the definition) and <prefix>.
| Field | Extra placeholder | Default |
|---|---|---|
resetMessage | none | Numerical storage reset for <player>. |
levelSetMessage | <level> | Set numerical storage level to <level> for <player>. |
addMessage | <amount> | Added <amount> to <player>. |
removeMessage | <amount> | Removed <amount> from <player>. |
noMenuMessage | none | The message shown when the storage has no Open Menu entry. |
invalidAmountMessage | <amount> (the value typed) | Invalid amount '<amount>'. Use a positive number. Shown by add and remove when the amount is not a positive number. |
noArtifactMessage | none | The message shown by add, remove, level and reset when no artifact is linked to the definition. |
noActiveProfileMessage | none | <player> has no active profile. Shown by add, remove, level and reset, and to a player who clicks a menu action, when profileMode is on and the player has no active MMOProfiles profile. Nothing is written. |
The defaults start with the Typewriter prefix. Three texts belong to no definition, so they are snippets in snippets.yml instead: numericalstorage.command.player_only (the menu button commands run by a non-player), and the placeholder texts numericalstorage.placeholder.interest_disabled (Disabled), numericalstorage.placeholder.interest_error (Error) and numericalstorage.placeholder.capacity_unlimited (the infinity sign). The interest_cooldown time units are the snippets numericalstorage.duration.days, hours, minutes, seconds (each a template with {value}, {value}j by default) and numericalstorage.duration.separator.
Permissions
| Permission | Grants |
|---|---|
typewriter.ns.open | /typewriter ns open. |
typewriter.ns.add | /typewriter ns add. |
typewriter.ns.remove | /typewriter ns remove. |
typewriter.ns.level | /typewriter ns level. |
typewriter.ns.reset | /typewriter ns reset. |
Permissions in interestRates | Give a player a specific interest rate. The first permission of the list that the player has is used. |
Placeholders
Replace <id> with the entry id of the definition. They need PlaceholderAPI and are read from a cache, so they are safe to use in scoreboards. With profileMode on, balance, level, capacity and interest follow the profile the player is playing, like the menu does.
| Placeholder | Returns |
|---|---|
%typewriter_ns_balance_<id>% | The current balance. |
%typewriter_ns_level_<id>% | The current level, starting at 1. |
%typewriter_ns_capacity_<id>% | The capacity of the current level, or the infinity sign when no level is defined. |
%typewriter_ns_interest_<id>% | The interest rate that applies to the player, in percent. |
%typewriter_ns_interest_cooldown_<id>% | The time until the next scheduled interest cycle, or Disabled. |
%typewriter_ns_name_<id>% | The displayName of the definition. |
%typewriter_ns_prefix_<id>% | The prefix of the definition. |
Entries
4 entries, generated from the extension source.
Actions2
- Numericalstorage MenuOpens a Numerical Storage menu
- Numericalstorage Open MenuOpens a Numerical Storage menu (triggerable)
Artifacts1
- Player Numericalstorage ArtifactStores the numericalstorage amount per player
Manifests1
- Numericalstorage DefinitionDefines a configurable numerical storage
Troubleshooting
The menu opens empty or buttons do nothing. Check that each item has buttonPrefix set to numericalstorage_button: exactly and a valid buttonType. If the prefix is wrong, the item stays a normal item with no action. Also check that mainLayoutId matches the id of a layout in layoutPool.
The menu closes after each deposit. The menu is reopened through an Open Numerical Storage Menu entry linked to the same definition. Add one, even if you open the menu with another entry.
"Cannot add ... Would exceed limit". The storage is at the capacity of the player's level (levels[].limit). Upgrade the level or raise the limit. With no level defined, there is no limit.
A deposit in INTERNAL mode fails. The player's source balance is read with amountPlaceholder and must return a plain number. The addCommand runs from the console before the deposit and must succeed. Use {player} and {amount} in the command.
"Economy system is not available." transactionMode is VAULT but Vault or an economy provider is missing. Install both, then restart.
Interest is not paid. Interest is applied when a player joins, for the cron cycles that passed since their last payment (at most 100 cycles). interestEnabled must be on and interestCron filled in. The first join only records the starting time. A balance at the capacity of its level earns nothing.
The upgrade asks for the capacity as its cost. When the next level has no criteria, the cost of an upgrade is the limit of the current level, taken from the stored balance. Add criteria to the next level to define a different requirement.
Placeholders show the balance of another profile. With profileMode on, the placeholders and the upgrade button read the balance and level of the profile the player has selected. Without MMOProfiles they read the player's own balance. With MMOProfiles and no active profile (profile selection screen, after /mmoprofiles quit), they show 0, because the player's UUID is the id of their Main profile and its balance must not be shown or credited. Writes are refused with noActiveProfileMessage, and the interest of a profile is settled when it is selected.
A placeholder is empty right after a restart. The storage is loaded in the background at join, so the first read can come before the load ends; the next read shows the stored value.
The placeholder is empty. The id in the placeholder must be the entry id of the definition, not its name. Run /papi parse me %typewriter_ns_balance_<id>% to test.
Related extensions
- GUI — the menu engine used for the layouts and buttons.
- MMOProfiles — optional, used by
profileMode.