English
GUI and Display
What the player sees comes from the gui and display sections of config.yml, the structure of the gui/storage_gui.yml template, and what each click does
For capacity and slot counts see Capacity; for the remaining config.yml keys see Configuration
gui
yaml
gui:
storage_rows: 5
deposit_feedback: actionbar
require_empty_cursor_for_withdraw: false| Field | Type | Required | Default | Accepted values | Description |
|---|---|---|---|---|---|
storage_rows | int | No | 5 | Clamped to 1–5 at load time (a warning is logged when out of range) | Display row count. The function row always owns the last row, so the real window height is storage_rows + 1 and one page renders storage_rows × 9 slots |
deposit_feedback | enum | No | actionbar | actionbar / message / none | Feedback channel for a successful deposit. An unknown value falls back to actionbar. It only affects success and partial-deposit feedback; failure messages always go to chat |
require_empty_cursor_for_withdraw | boolean | No | false | true / false | Whether withdrawal requires an empty cursor. See the note below |
Recommendation: do not set deposit_feedback to none. Under mixed deposit an item lands where the entry order puts it and may end up on another page; with feedback disabled the player will believe the item vanished
require_empty_cursor_for_withdraw is a misclick guard. When you set it to true, clicking a display slot while holding an item on the cursor no longer deposits: the click becomes a no-op and reports gui.deposit.cursor_guard. This closes the "meant to withdraw but still had a leftover cursor" mis-click window, at the cost of losing the "click an entry while holding an item to deposit" shortcut. The default, false, keeps that shortcut deposit; you can still deposit through the fixed deposit port or deposit_all either way
For example, shrink the window to 3 display rows and send deposit feedback to chat
yaml
gui:
storage_rows: 3
deposit_feedback: message
require_empty_cursor_for_withdraw: truestorage_rows Conversion Table
The slots values in the template file are only placeholders, so you can ignore them. Real slot numbers are derived from gui.storage_rows at load time:
text
storageRows = clamp (gui.storage_rows, 1, 5)
totalRows = storageRows + 1
functionBase = storageRows * 9
display area = 0 .. functionBase - 1
function row = functionBase + offsetstorage_rows | totalRows | Slots per page | Display slots | Function slots (offset 0–8) |
|---|---|---|---|---|
| 1 | 2 | 9 | 0–8 | 9–17 |
| 2 | 3 | 18 | 0–17 | 18–26 |
| 3 | 4 | 27 | 0–26 | 27–35 |
| 4 | 5 | 36 | 0–35 | 36–44 |
| 5 | 6 | 45 | 0–44 | 45–53 |
Out-of-range values are pulled back in: a value >= 6 is clamped to 5 with a warning; a value <= 0 is clamped to 1 with a warning. totalRows peaks at 6, which already sits inside CoreLib's 1–6 range
storage_gui.yml Template Structure
yaml
id: storage_gui
gui_type: CHEST
title: "<gradient:#4DA6FF:#FFD166>绘卷仓库</gradient>"
slots:
storage_slot:
type: storage_slot
slots: [0]
item:
source: "gray_stained_glass_pane"
page_prev:
type: page_prev
offset: 0
slots: [0]
item:
source: "arrow"| Field | Type | Required | Default | Accepted values | Description |
|---|---|---|---|---|---|
id | string | Yes | shipped value storage_gui | Must be storage_gui | Template id; the implementation looks the template up by it. Any other value means the template cannot be found, the window fails to open and a warning is logged |
gui_type | string | No | shipped value CHEST | Bukkit InventoryType name | Container type. The layout rebuild always recreates the window as CHEST, so this key does not decide the final window type |
rows | integer | No | not declared in the shipped file | ignored | The shared template's row field. The warehouse layout rebuilds the template with gui.storage_rows + 1 rows, so this key has no effect; change gui.storage_rows instead |
title | string / object | No | shipped value "<gradient:#4DA6FF:#FFD166>绘卷仓库</gradient>" | MiniMessage string or a TextConfig object | Window title |
slots | map | No | shipped value holds 9 entries | Keys name the slot entries | The slot entry table. It must contain storage_slot, otherwise the display area cannot be built and the whole template fails to resolve |
slots.<key>.type | string | No | derived from the lower-cased entry key | see the slot type table below | Slot type label. When omitted it is derived from the key; an unrecognised value leaves the slot present but unresponsive to clicks |
slots.<key>.offset | integer | No | — | 0–8 | Row offset for function entries. When it is missing, not a number or out of range the entry is rejected with a warning; when two entries claim the same offset the later one is rejected. storage_slot does not use this key |
slots.<key>.slots | list<integer> | No | — | — | The slot indices the shared template parser requires. For function entries this key is overwritten by the offset conversion at load time, so whatever is written there never takes effect |
slots.<key>.item | section | No | — | shared item definition key set (source, amount, components, ...), see CoreLib GUI | Entry appearance. The item configured on storage_slot only supplies the placeholder appearance of a locked slot |
You get exactly these 9 slot types under slots:
| Slot type | Purpose |
|---|---|
storage_slot | The display-area prototype. Required: without it the display area cannot be built and the whole template fails to resolve |
deposit_slot | The fixed deposit port. Clicking it with a loaded cursor deposits |
page_prev | Previous page |
page_info | Current page information, display only |
page_next | Next page |
search | Opens the search prompt; right-click while searching clears the filter |
sort | Cycles the sort; shift-click cycles it in reverse |
deposit_all | Bulk deposit across the 36 main inventory slots |
unlock | Entry point for paid expansion |
An entry may also declare type explicitly; when omitted it is derived from the lower-cased entry key, so naming an entry after its type is enough
How offset Relates to slots
Keep this rule in mind: function-row entries only declare offset (valid 0–8). The slots key in the template exists only to satisfy the shared template parser and is overwritten by the conversion at load time, so it must never be read as a real position
- When
offsetis missing, not a number, or outside 0–8, the entry is rejected with a warning - When two entries claim the same
offset, the later one is rejected with a warning - When every function entry is rejected, a warning is logged and the paging and deposit buttons become unavailable
The display area's storage_slot is a prototype: at load time it expands across the whole display area, 0 .. functionBase - 1. The item you configure on it only supplies the placeholder appearance of a locked slot and does not affect how unlocked slots render
Once you change gui.storage_rows, the function row moves automatically; no slot number needs to be edited by hand
For example, give the window a custom title and move the search button from its default offset 4 to offset 2, the only gap in the default layout
yaml
title: "<gold>My Warehouse</gold>"
slots:
search:
type: search
offset: 2
item:
source: "compass"Check before you move an entry: the default function row occupies offsets 0, 1, 3, 4, 5, 6, 7 and 8, leaving offset 2 free. Make sure the target offset is not claimed by another entry, or the moved entry is rejected with a warning
GUI Click Semantics
| Location | Action | Effect |
|---|---|---|
| Display slot | Loaded cursor + left-click | Deposits the whole cursor |
| Display slot | Loaded cursor + right-click | Deposits 1 unit |
| Display slot | Empty cursor + left / right / shift-left / shift-right | Withdraws per withdraw_amounts |
| Display slot | Empty cursor + middle-click | Opens custom-amount input (governed by behavior.withdraw_prompt.enabled; behavior.withdraw_prompt.mode decides dialog versus chat input) |
deposit_slot | Loaded cursor | Deposits the whole cursor |
deposit_slot | Empty cursor | Reports that the port is empty |
deposit_all | Click | Bulk-deposits across the 36 main inventory slots |
sort | Click / shift-click | Cycles the sort / cycles it in reverse |
search | Click | Opens the search prompt; right-click while searching clears the filter |
page_prev / page_next | Click | Pages; an empty page cannot be paged into |
| Player inventory item | Shift-click | Redirected into the deposit port logic |
These click kinds are rejected outright: double-click, number keys, offhand swap, the drop key and Ctrl + drop. They respectively aggregate across slots, bypass cursor semantics or discard the item outright, so none of them can be given a safe meaning; such clicks report gui.click.unsupported
Note: while a search filter is active, deposits into the display area are refused (paging follows the filtered result set, so a display slot no longer identifies a real slot number), but the fixed deposit port keeps working
Withdrawal amounts, the default sort and the deposit filter are documented in the behavior section of Configuration
display
yaml
display:
amount_mode: percent
percent_scale: 99
compact_units: ["K", "M", "B", "T", "P", "E"]
compact_decimals: 2
show_exact_amount: true
lore_position: bottom| Field | Type | Required | Default | Accepted values | Description |
|---|---|---|---|---|---|
amount_mode | enum | No | percent | percent / one | How the slot item's amount is expressed: percent shows occupancy as 1–99, one is always 1. An unknown value falls back to percent |
percent_scale | int | No | 99 | Clamped to 1–99 before use (no error when out of range) | Scale ceiling for percent mode; it is also the value written to the slot item's max_stack_size component |
compact_units | list<string> | No | ["K", "M", "B", "T", "P", "E"] | At most 6 elements; an empty list falls back to the default | Compact amount units, in order for 10³... 10¹⁸. An empty list falls back to the six defaults rather than degrading to exact numbers |
compact_decimals | int | No | 2 | ≥ 0 (negatives are treated as 0) | Decimal places kept in compact format, rounded down with trailing zeroes trimmed |
show_exact_amount | boolean | No | true | true / false | Whether to also show the grouped exact amount in lore |
lore_position | enum | No | bottom | top / bottom | Where generated lines go relative to the template lore. An unknown value falls back to bottom |
For example, turn off occupancy rendering (slots always show 1), keep one decimal, and put the generated lines above the template lore
yaml
display:
amount_mode: "one"
compact_decimals: 1
show_exact_amount: false
lore_position: top1–99 Scale Rules
The valid range of the max_stack_size component is exactly 1–99, so the implementation never uses 100 or above and no packet trickery is involved. The scale boundaries are set deliberately:
| Case | Displayed amount |
|---|---|
amount_mode: one | Always 1 |
stackLimit unlimited (Long.MAX_VALUE) or <= 0 | Degrades to 1; a percentage is meaningless for an unlimited ceiling |
Stored amount <= 0 | 1 |
Stored amount >= stackLimit (genuinely full) | percent_scale (default 99) |
| Partially filled | clamp(round(amount / stackLimit * scale), 1, scale - 1), so at most 98 by default |
Why these clamps exist: without them a nearly-empty slot would render as 0 (invisible) and a nearly-full one would be indistinguishable from full
The slot percentage and the lore text are computed differently: the percentage text in lore is computed independently on the real 0–100 range and does not inherit the 1–99 clamping
Compact Units
compact_units maps in order onto the six thresholds 10³, 10⁶, 10⁹, 10¹², 10¹⁵ and 10¹⁸, taking at most 6 elements. Long.MAX_VALUE is roughly 9.22E, so the E unit covers the entire long range. Amounts below 1000 print the plain number. If you write an empty list in the configuration, the parser falls back to the six defaults, so the "empty list means no compact units" branch is unreachable from configuration
Related Pages
- Configuration: the remaining
config.ymlsections, includingbehaviorandsearch - Capacity: slots per page, total pages and the three-level
stackLimit - Unlock Costs: the tiers and batch options behind the
unlockbutton - Commands and Permissions:
/estorage reloadalso reloads the GUI template