> For the complete documentation index, see [llms.txt](https://windsmp.gitbook.io/windsmp-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://windsmp.gitbook.io/windsmp-docs/donutshop.md).

# DonutShop

DonutShop is a GUI-based server shop plugin for Paper/Folia (Minecraft 1.21 API). It includes a main menu, category pages, and a buy menu with quantity controls.

## Feature Summary

* Main shop menu with paged category buttons.
* Category menus with paged shop items.
* Buy menu with add/remove/set-min/set-max quantity controls.
* Vault money support (`MONEY` currency).
* Optional PlayerPoints support (`SHARDS` currency).
* Per-item command rewards, item rewards, or both.

## Compatibility

| Component                       | Required                | Notes                                      |
| ------------------------------- | ----------------------- | ------------------------------------------ |
| Paper or Folia server           | Yes                     | Uses Minecraft `1.21` API (`plugin.yml`).  |
| Java 21                         | Yes                     | Built with JVM toolchain 21.               |
| Vault                           | Yes for money purchases | Required for `MONEY` currency items.       |
| Vault-compatible economy plugin | Yes for money purchases | Example: EssentialsX Economy.              |
| PlayerPoints                    | Optional                | Required only for `SHARDS` currency items. |
| DonutShop license key           | Yes                     | Set in `plugins/DonutShop/license.yml`.    |

## Quick Start

{% stepper %}
{% step %}

### Place the plugin jar

Place the DonutShop jar in your server `plugins` folder.
{% endstep %}

{% step %}

### Start the server

Start the server once to generate plugin files.
{% endstep %}

{% step %}

### Set your license key

Set your license key in `plugins/DonutShop/license.yml`:

```yml
license-key: "YOUR_KEY_HERE"
```

{% endstep %}

{% step %}

### Install required dependencies

Ensure Vault and an economy provider are installed and enabled.
{% endstep %}

{% step %}

### Optionally install PlayerPoints

Optionally install PlayerPoints if you use shard-priced items.
{% endstep %}

{% step %}

### Edit the configuration files

Edit:

* `plugins/DonutShop/config.yml`
* `plugins/DonutShop/shop.yml`
* `plugins/DonutShop/message.yml`
* `plugins/DonutShop/sound.yml`
  {% endstep %}

{% step %}

### Reload the plugin

Run `/shop reload`.
{% endstep %}

{% step %}

### Test the GUI

Run `/shop` in-game and validate the GUI flow.
{% endstep %}
{% endstepper %}

## Commands and Permissions

| Command        | Permission        | Default | Description                                                       |
| -------------- | ----------------- | ------- | ----------------------------------------------------------------- |
| `/shop`        | `donutshop.use`   | `true`  | Opens the main shop GUI.                                          |
| `/shop reload` | `donutshop.admin` | `op`    | Reloads `config.yml`, `shop.yml`, `message.yml`, and `sound.yml`. |

> `/shop` must be run by a player (not console).

## Configuration Files

| File          | Purpose                                                              |
| ------------- | -------------------------------------------------------------------- |
| `license.yml` | Stores your license key.                                             |
| `config.yml`  | Global settings, fillers, navigation buttons, and buy-menu controls. |
| `shop.yml`    | Main menu setup, categories, and item definitions.                   |
| `message.yml` | Player-facing messages.                                              |
| `sound.yml`   | Sound events and playback settings.                                  |

## How Buying Works

{% stepper %}
{% step %}

### Open the shop

Player runs `/shop`.
{% endstep %}

{% step %}

### Open a category

Player opens a category.
{% endstep %}

{% step %}

### Select an item

Player selects an item and opens the buy menu.
{% endstep %}

{% step %}

### Change the quantity

Player changes quantity with configured buttons.
{% endstep %}

{% step %}

### Confirm the purchase

On confirm, DonutShop calculates requested units, requested items, and total price.
{% endstep %}

{% step %}

### Handle partial purchases

If balance or inventory space is limited, DonutShop performs a partial purchase when possible.
{% endstep %}

{% step %}

### Handle failed purchases

If nothing can be bought, the player receives an error message.
{% endstep %}

{% step %}

### Check command-only items

If an item uses `give-item: false`, it should include `commands`; otherwise purchase fails.
{% endstep %}
{% endstepper %}

## Configuration Reference

### `config.yml`

#### `settings`

| Path                             | Type    | Default | Description                                            |
| -------------------------------- | ------- | ------- | ------------------------------------------------------ |
| `settings.close-menus-on-reload` | boolean | `true`  | Closes open shop menus when `/shop reload` is used.    |
| `settings.text.caps-tag-enabled` | boolean | `true`  | Enables custom `<caps>` and `<small_caps>` transforms. |

#### `settings.global.buttons`

Global navigation templates:

* `previous-page-button`
* `next-page-button`
* `back-button`

Button fields:

| Field               | Type               | Notes                                                                                                                                           |
| ------------------- | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| `slot`              | integer            | For global prev/next/back templates, slot is auto-resolved at runtime. Use per-category overrides in `shop.yml` when you need custom placement. |
| `material`          | Bukkit material    | Example: `ARROW`.                                                                                                                               |
| `name`              | MiniMessage string | Supports placeholders and MiniMessage tags.                                                                                                     |
| `lore`              | list of strings    | Supports placeholders and MiniMessage tags.                                                                                                     |
| `amount`            | integer            | Clamped to `1..64`.                                                                                                                             |
| `custom-model-data` | integer            | Optional.                                                                                                                                       |

Navigation slot behavior:

* Main previous/next slots are auto-resolved from menu size.
* Category back button slot is auto-resolved unless overridden in `shop.yml`.
* Category previous/next slots can be overridden per category.

#### Fillers

Filler sections:

* `main-menu.filler`
* `category-menu.filler`
* `buy-menu.filler`

Fields:

| Field      | Type            | Description                                        |
| ---------- | --------------- | -------------------------------------------------- |
| `enabled`  | boolean         | If `true`, fills empty slots with the filler item. |
| `material` | Bukkit material | Display material for filler slots.                 |
| `name`     | string          | Display name.                                      |
| `lore`     | list of strings | Optional lore lines.                               |

#### `buy-menu`

| Path                      | Type    | Notes                                    |
| ------------------------- | ------- | ---------------------------------------- |
| `buy-menu.size`           | integer | Normalized to `9..54` and multiple of 9. |
| `buy-menu.preview-slot`   | integer | 0-based slot index inside buy menu.      |
| `buy-menu.add-buttons`    | object  | Quantity increase controls.              |
| `buy-menu.remove-buttons` | object  | Quantity decrease controls.              |
| `buy-menu.confirm-button` | object  | Confirm purchase button config.          |
| `buy-menu.cancel-button`  | object  | Cancel button config.                    |
| `buy-menu.filler`         | object  | Optional filler item config.             |

Quantity button fields:

| Field                                                             | Type    | Notes                                  |
| ----------------------------------------------------------------- | ------- | -------------------------------------- |
| `amount`                                                          | integer | Must be positive.                      |
| `mode`                                                            | enum    | `ADD`, `REMOVE`, `SET_MAX`, `SET_MIN`. |
| `slot`, `material`, `name`, `lore`, `amount`, `custom-model-data` | mixed   | Standard button display fields.        |

Accepted `mode` aliases:

* `REMOVE` also accepts `subtract`.
* `SET_MAX` also accepts `set-max`, `max`, `set-to-max`.
* `SET_MIN` also accepts `set-min`, `min`, `set-to-min`.

### `shop.yml`

#### `main-menu`

| Field                  | Type    | Notes                                    |
| ---------------------- | ------- | ---------------------------------------- |
| `main-menu.size`       | integer | Normalized to `9..54` and multiple of 9. |
| `main-menu.title`      | string  | Supports MiniMessage and placeholders.   |
| `main-menu.categories` | object  | Optional explicit category entries.      |

Each `main-menu.categories.<categoryId>` entry supports:

* `slot` (0-based)
* `page` (1-based)
* `material`
* `name`
* `lore`
* `custom-model-data`

Behavior:

* If `main-menu.categories` is omitted, DonutShop auto-maps configured categories.
* If `main-menu.categories` is present, only listed entries appear.

#### `buy-menu`

| Field            | Type   | Notes                    |
| ---------------- | ------ | ------------------------ |
| `buy-menu.title` | string | Buy menu title template. |

#### `categories.<id>`

| Field                  | Type             | Notes                                                  |
| ---------------------- | ---------------- | ------------------------------------------------------ |
| `size`                 | integer          | Category inventory size.                               |
| `title`                | string           | Category title template.                               |
| `back-button`          | number or object | Optional slot override (`{ slot: <n> }` is supported). |
| `previous-page-button` | number or object | Optional previous-page slot override.                  |
| `next-page-button`     | number or object | Optional next-page slot override.                      |
| `icon`                 | object           | Optional legacy icon used in auto main-menu mapping.   |
| `items`                | object           | Item definitions for that category.                    |

Slot override aliases also supported:

* Previous: `previous-button`, `prev-button`, `prev`
* Next: `next-button`, `next`

#### `categories.<id>.items.<itemId>`

| Field                                     | Type            | Required | Notes                                                        |
| ----------------------------------------- | --------------- | -------- | ------------------------------------------------------------ |
| `slot`                                    | integer         | No       | 0-based slot index. If missing, a fallback slot is assigned. |
| `page`                                    | integer         | No       | 1-based page number (defaults to 1).                         |
| `material`                                | Bukkit material | No       | Defaults to `STONE` when missing or invalid.                 |
| `price`                                   | number          | No       | Defaults to `0`. Negative values are clamped to `0`.         |
| `currency`                                | enum            | No       | `MONEY` (default) or `SHARDS`.                               |
| `name`                                    | string          | No       | Optional custom display name.                                |
| `lore`                                    | list            | No       | Optional lore lines.                                         |
| `custom-model-data`                       | integer         | No       | Optional.                                                    |
| `enchantments` / `enchants`               | map             | No       | Enchantment name to level.                                   |
| `unsafe-enchantments` / `unsafe-enchants` | boolean         | No       | Allows over-max enchant levels.                              |
| `potion-type` / `potion.type`             | string          | No       | Used for potion-like materials.                              |
| `start-amount`                            | integer         | No       | Initial quantity step (clamped by stack size).               |
| `amount-per-purchase`                     | integer         | No       | Items granted per quantity step.                             |
| `give-item`                               | boolean         | No       | Defaults to `true`.                                          |
| `commands`                                | string or list  | No       | Commands to run on successful purchase.                      |
| `command-dispatcher` / `command-sender`   | enum            | No       | `CONSOLE` (default) or `PLAYER`.                             |

Currency aliases accepted:

* `MONEY`: `money`, `vault`, `cash`, `coins`, `dollars`
* `SHARDS`: `shards`, `shard`, `sards`, `points`, `playerpoints`, `player-points`

#### Pricing and quantity rules

* `MONEY` uses configured `price` directly.
* `SHARDS` rounds the unit price up to a whole number.
* Total charged is based on actual delivered quantity.
* `amount-per-purchase` and item stack size limit maximum buy quantity.
* If inventory fit is lower than requested, DonutShop performs partial purchase when possible.

### `message.yml`

All keys are read under `messages:`.

| Key                       |
| ------------------------- |
| `prefix`                  |
| `player-only`             |
| `no-permission`           |
| `no-categories`           |
| `no-items`                |
| `item-missing`            |
| `reload-success`          |
| `reload-failed`           |
| `no-economy`              |
| `no-shards-provider`      |
| `not-enough-money`        |
| `not-enough-shards`       |
| `not-enough-space`        |
| `purchase-success`        |
| `purchase-success-shards` |
| `purchase-partial`        |
| `purchase-partial-shards` |
| `purchase-failed`         |
| `processing`              |

### `sound.yml`

All keys are read under `sounds:`.

| Key                   |
| --------------------- |
| `menu-main-open`      |
| `menu-category-open`  |
| `menu-buy-open`       |
| `buy-quantity-add`    |
| `buy-quantity-remove` |
| `menu-back`           |
| `purchase-success`    |
| `action-error`        |
| `plugin-reload`       |

Each sound key supports:

| Field     | Type    | Notes                                |
| --------- | ------- | ------------------------------------ |
| `enabled` | boolean | If `false`, the sound is disabled.   |
| `sound`   | string  | Bukkit sound name or namespaced key. |
| `volume`  | float   | `>= 0`.                              |
| `pitch`   | float   | `>= 0`.                              |

Legacy sound aliases are supported for migration, for example `main-open` -> `menu-main-open`.

## Placeholder Reference

Placeholders are case-insensitive.

> Not every placeholder is available in every context. Only placeholders provided by that render or message path are replaced.

### GUI text placeholders (titles, names, lore)

| Placeholder        |
| ------------------ |
| `<page>`           |
| `<max_page>`       |
| `<category_name>`  |
| `<category_title>` |
| `<item_name>`      |
| `<price>`          |
| `<unit_price>`     |
| `<amount>`         |
| `<purchase_steps>` |
| `<max_amount>`     |
| `<total_price>`    |
| `<currency>`       |
| `<change>`         |
| `<next_quantity>`  |
| `<next_amount>`    |

### Command placeholders (`items.<id>.commands`)

| Placeholder                  |
| ---------------------------- |
| `<player>` / `<player_name>` |
| `<uuid>`                     |
| `<world>`                    |
| `<x>`, `<y>`, `<z>`          |
| `<category>`                 |
| `<category_name>`            |
| `<item_id>`                  |
| `<item_name>`                |
| `<amount>`                   |
| `<purchase_steps>`           |
| `<total_price>`              |
| `<price>`                    |
| `<unit_price>`               |
| `<currency>`                 |

### Common message placeholders

| Placeholder     |
| --------------- |
| `<price>`       |
| `<balance>`     |
| `<amount>`      |
| `<bought>`      |
| `<item_name>`   |
| `<total_price>` |
| `<currency>`    |

## MiniMessage and Text Tags

Names, lore, titles, and messages support MiniMessage.

Examples:

* `<red>Text`
* `<#54fc54>Text`
* `<bold>Text`

Custom transform tags:

* `<caps>text</caps>`
* `<small_caps>text</small_caps>`

Enable or disable custom tags with:

```yml
settings:
  text:
    caps-tag-enabled: true
```

## Examples

### Basic money item

```yml
categories:
  blocks:
    size: 27
    title: "<gray>Shop - Blocks"
    back-button: 18
    items:
      obsidian:
        slot: 10
        page: 1
        material: OBSIDIAN
        price: 120
        currency: MONEY
        lore:
          - "<green>$<price>"
```

### Command-only shard reward

```yml
categories:
  shards:
    size: 27
    title: "<gray>Shard Shop"
    back-button: 18
    items:
      crate_key:
        slot: 13
        page: 1
        material: TRIPWIRE_HOOK
        name: "<light_purple>Crate Key"
        price: 250
        currency: SHARDS
        give-item: false
        command-dispatcher: CONSOLE
        commands:
          - "crate give <player> vote 1"
        lore:
          - "<light_purple><price> shards"
```

### Enchanted item

```yml
categories:
  gear:
    size: 27
    title: "<gray>Shop - Gear"
    back-button: 18
    items:
      sword:
        slot: 11
        page: 1
        material: DIAMOND_SWORD
        price: 2500
        currency: MONEY
        amount-per-purchase: 1
        enchantments:
          sharpness: 5
          unbreaking: 3
        unsafe-enchantments: false
        lore:
          - "<green>$<price>"
```

## Troubleshooting

| Problem                                  | Likely cause                                     | What to check                                        |
| ---------------------------------------- | ------------------------------------------------ | ---------------------------------------------------- |
| `/shop` says player-only or does nothing | Command run outside game                         | Run the command as an in-game player.                |
| "No shop categories are configured"      | `shop.yml` category structure missing or invalid | Check `shop.yml > categories`.                       |
| "Vault economy is unavailable right now" | Vault or economy plugin missing                  | Install/enable Vault and an economy provider plugin. |
| "PlayerPoints is unavailable right now"  | PlayerPoints missing for shard item              | Install/enable PlayerPoints.                         |
| Reload fails                             | YAML or value validation issues                  | Check console warnings for exact path/value errors.  |
| Purchase fails unexpectedly              | Item has no valid reward                         | Ensure `give-item: true` or non-empty `commands`.    |
| Page or slot appears wrong               | Invalid values were clamped                      | Verify slot ranges and 1-based page values in YAML.  |

## Safe Editing Workflow

{% stepper %}
{% step %}

### Edit one section at a time

Edit one section at a time.
{% endstep %}

{% step %}

### Reload the plugin

Run `/shop reload`.
{% endstep %}

{% step %}

### Test in game

Test `/shop` in-game.
{% endstep %}

{% step %}

### Check console warnings

Watch console warnings for invalid keys, slot/page values, and fallback behavior.
{% endstep %}

{% step %}

### Repeat as needed

Repeat until all menus and purchases behave as expected.
{% endstep %}
{% endstepper %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://windsmp.gitbook.io/windsmp-docs/donutshop.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
