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

# Variables

## General

Every variable is declared under `variables`, scoped either `global` (one shared value) or `player` (one value per player).

```yaml
variables:
  kills:
    scope: player
    type: integer
    default: 0
```

### Managing

| Command                                    | Effect                                                                                                                                                                            |
| ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `/aircore var get <key> [player]`          | Shows the current value. `[player]` only applies to `player`-scoped variables; omit it for `global`.                                                                              |
| `/aircore var set <key> <value> [player]`  | Sets the value directly, bypassing any conditions on `on-set`.                                                                                                                    |
| `/aircore var add <key> <amount> [player]` | Adds to the current value. Only valid for `integer`/`double` types.                                                                                                               |
| `/aircore var reset <key> [player]`        | Resets to the variable's declared `default`.                                                                                                                                      |
| `/aircore var toggle <key> [player]`       | Flips the value. Only valid for `boolean` types.                                                                                                                                  |
| `/aircore var list [player]`               | Without `[player]`: lists all variable definitions and, for `global` ones, their current value. With `[player]`: lists that player's values across all `player`-scoped variables. |

### Placeholders

| Placeholder                    | Description                                                 |
| ------------------------------ | ----------------------------------------------------------- |
| `%aircore_var_<key>%`          | Self (player scope) or the global value.                    |
| `%aircore_var_<key>_<player>%` | Another player's value (player scope only).                 |
| `%aircore_var_<key>_delta%`    | Amount just added/subtracted (`on-add`/`on-subtract` only). |

### Orphan check

When `orphan-check` is `true`, removing a variable from config drops its stored rows on the next startup. Set to `false` to keep orphaned rows instead of deleting them.

```yaml
orphan-check: true
```

### Creating your own file

Files can be split across `/variables/` subdirectories (e.g. `/variables/events/halloween.yml`). Everything defined in split files is merged into the same list at startup. Keys must be unique across **all** files.

```yaml
variables:
  server_open:
    ...
```

## Configuration

### Scope

| Scope    | Behavior                          |
| -------- | --------------------------------- |
| `global` | One value, shared server-wide.    |
| `player` | One independent value per player. |

### Type & constraints

Every variable declares a `type`, which determines the constraints available to it.

| Type      | Constraints              | Behavior                     |
| --------- | ------------------------ | ---------------------------- |
| `string`  | `max-length: <n>`        | Truncates on write.          |
| `string`  | `allowed-values: [a, b]` | Rejects anything not listed. |
| `integer` | `min` / `max`            | Clamps on write.             |
| `double`  | `min` / `max`            | Clamps on write.             |
| `boolean` | *-*                      | -                            |

```yaml
xp_multiplier:
  scope: global
  type: double
  default: 1.0
  min: 1.0
  max: 5.0
```

### Hooks

Every hook holds an `entries` list. Entries are evaluated **top to bottom** - unlike [Placeholders](/docs/aircore/configuration/modules/placeholders.md) and [Announcements](/docs/aircore/configuration/modules/announcements.md), **every** entry whose conditions pass runs, not just the first match. This lets one entry perform a write while later entries react to the value that write just produced.

An entry can carry a write verb - `add` / `set` / `subtract` / `reset` / `toggle` - and/or any of the Components below. A bare condition-only entry with no write verb is a pure reaction.

#### Event hooks

Fire when something happens in-game. Typically where a variable's write actually happens.

| Hook          | Fires on                                                     |
| ------------- | ------------------------------------------------------------ |
| `on-join`     | Player join                                                  |
| `on-kill`     | Player kills another player                                  |
| `on-death`    | Player dies                                                  |
| `on-quit`     | Player quits                                                 |
| `on-startup`  | Server startup                                               |
| `on-interval` | Fixed interval (seconds)                                     |
| `on-custom`   | A declared custom event. See [Custom events](#custom-events) |

```yaml
kills:
  scope: player
  type: integer
  default: 0
  min: 0
  on-kill:
    entries:
      - add: 1
```

#### Mutation hooks

Fire after **any** write of that kind lands on the variable, regardless of what triggered it - an event hook's write, a staff-run command, or the API. This is the right place for reactions that must never be skippable (milestones, notifications), since event hooks alone won't catch a manually-issued `/aircore var add`.

| Hook          | Fires after          |
| ------------- | -------------------- |
| `on-add`      | Any `add` write      |
| `on-subtract` | Any `subtract` write |
| `on-set`      | Any `set` write      |
| `on-reset`    | Any `reset` write    |
| `on-toggle`   | Any `toggle` write   |

{% hint style="info" %}
`on-add` and `on-subtract` also expose `%aircore_var_<key>_delta%`, the amount just added or subtracted.
{% endhint %}

```yaml
on-add:
  entries:
    - conditions:
        - "%aircore_var_kills% % 100 == 0"
      title:
        text: "<gold><bold>%aircore_var_kills% KILLS"
        fade-in: 10
        stay: 40
        fade-out: 10
      sound:
        key: "ui.toast.challenge_complete"
        volume: 1.0
        pitch: 1.2
```

### Conditions

Every entry in the list must pass for the block to be considered satisfied.

```yaml
conditions:
  - "%aircore_var_kill_streak% > %aircore_var_best_streak%"
```

Conditions are evaluated as strings. See [Conditions](/docs/global-features/conditions.md) for more information.

### Components

Side-effect components attach to a hook entry and fire alongside (or instead of) a write. Reused directly from [Announcements](/docs/aircore/configuration/modules/announcements.md#components): `chat`, `actionbar`, `title`, `subtitle`, `bossbar`, `sound`, `command`.

Two components are new to this module:

| Component      | Description                                                                                                  |
| -------------- | ------------------------------------------------------------------------------------------------------------ |
| `variable`     | Write to another variable.                                                                                   |
| `announcement` | Fire a named announcement from [Announcements](/docs/aircore/configuration/modules/announcements.md) module. |

```yaml
variable:
  key: <key>
  add | set | subtract | reset | toggle: <value>
```

```yaml
announcement: kills_milestone_broadcast
```

### Custom events

Declared once under `custom-events`, referenced from any number of variables via `on-custom`. Lets a variable hook into any event on the classpath - a Bukkit event you haven't wired a shorthand for, or another plugin's own event - via reflection, without redeclaring the class/method every time a second variable wants to react to it.

```yaml
custom-events:
  <event-key>:
    class: <fully-qualified event class>
    player-method: <getter returning the Player; omit for non-player events>
    capture:
      - name: <placeholder name, scoped to this event's entries only>
        method: <zero-arg getter to call on the fired event object>
```

Captured values must come from primitive-returning getters (`String`, `int`, `double`, `boolean`, `enum`). A getter returning a complex object won't resolve meaningfully - chain to a primitive-returning method instead (`getLocation().getWorld().getName()`, not `getLocation()`).

{% hint style="info" %}
If a declared method fails to resolve against the class at startup, a warning is logged naming the event key and method, and that capture resolves to `""` at runtime rather than throwing.
{% endhint %}

**Example:** (hooking a third-party plugin event)

```yaml
custom-events:
  mcmmo-level-up:
    class: "com.gmail.nossr50.events.experience.McMMOPlayerLevelUpEvent"
    player-method: "getPlayer()"
    capture:
      - name: skill_name
        method: "getSkill().name()"
      - name: skill_level
        method: "getSkillLevel()"
```

Referencing it from a variable:

```yaml
mcmmo_levels_gained:
  scope: player
  type: integer
  default: 0
  min: 0
  on-custom:
    event: mcmmo-level-up
    entries:
      - add: 1
        conditions:
          - "%skill_level% > 0" # Engine-provided
```

## Examples

{% tabs %}
{% tab title="Example 1" %}
**Simple counter with milestone reactions**

Increments on every kill, then reacts separately in a mutation hook so staff-issued `/aircore var add kills` also triggers the milestone.

```yaml
variables:
  kills:
    scope: player
    type: integer
    default: 0
    min: 0
  
    on-kill:
      entries:
        - add: 1
  
    on-add:
      entries:
        - conditions:
            - "%aircore_var_kills% % 100 == 0"
          title:
            text: "<gold><bold>%aircore_var_kills% KILLS"
            fade-in: 10
            stay: 40
            fade-out: 10
          subtitle: "<gray>You're on a roll."
          sound:
            key: "ui.toast.challenge_complete"
            volume: 1.0
            pitch: 1.2
  
        - conditions:
            - "%aircore_var_kills% % 1000 == 0"
          variable:
            key: last_milestone_player
            set: "%player%"
          announcement: kills_milestone_broadcast
```

{% endtab %}

{% tab title="Example 2" %}
**Pure mutation-hook reaction**

No event hook of its own - another module writes via `/aircore var add xp <amount> <player>`, and everything here just reacts to that add.

```yaml
variables:
  xp:
    scope: player
    type: integer
    default: 0
    min: 0
    max: 1000
  
    on-add:
      entries:
        - chat: "<gray>+%aircore_var_xp% XP"
  
        - conditions:
            - "%aircore_var_xp% == 1000"
          variable:
            key: xp
            reset: true
          command: "lp user %player% parent add ranked_up"
          title:
            text: "<yellow><bold>RANK UP"
            fade-in: 10
            stay: 60
            fade-out: 10
          subtitle: "<gray>You reached 1,000 XP"
          sound:
            key: "ui.toast.challenge_complete"
            volume: 1.0
            pitch: 1.0
```

{% endtab %}

{% tab title="Example 3" %}
**Reacting to a vanilla event with no built-in shorthand**

<pre class="language-yaml"><code class="lang-yaml"><strong>custom-events:
</strong>  advancement_earned:
    class: "org.bukkit.event.player.PlayerAdvancementDoneEvent"
    player-method: "getPlayer()"
    capture:
      - name: advancement_key
        method: "getAdvancement().getKey().getKey()"

variables:
  advancements_earned:
    scope: player
    type: integer
    default: 0
    min: 0

    on-custom:
      event: advancement_earned
      entries:
        - add: 1
</code></pre>

{% endtab %}

{% tab title="Example 4" %}
**Global scope written from a player-scope hook**

`server_open` has no hooks of its own - it's just read directly by other modules' conditions (e.g. a join-gate check). `total_players` is global too, but unlike `server_open` it does have a hook: it increments once per brand-new player. Since the variable is global, every player's join counts toward the same shared value rather than a per-player one.

```yaml
variables:
  server_open:
    scope: global
    type: boolean
    default: true

  total_players:
    scope: global
    type: integer
    default: 0
    min: 0
    on-join:
      entries:
        - conditions:
            - "%aircore_player_is_first_join% == true"
          add: 1
```

{% endtab %}

{% tab title="Example 5" %}
**Reacting to the size of a single write, not the running total**

Most `on-add`/`on-subtract` reactions key off the variable's new total (`kills` above checks `% 100 == 0`, for instance). `%aircore_var_<key>_delta%` is for the opposite case - reacting to *how much a single write changed the value by*, regardless of where the total lands. Useful for distinguishing a normal, incremental write from a large one-off write (an admin command, a bulk grant, a big purchase).

```yaml
coins:
  scope: player
  type: double
  default: 0.0
  min: 0.0

  on-add:
    entries:
      - conditions:
          - "%aircore_var_coins_delta% >= 10000"
        chat: "<gold><bold>Big deposit!</bold> <white>+%aircore_var_coins_delta% coins."
        announcement: big_deposit_broadcast
```

A normal `add: 50` from gameplay never satisfies the condition and stays silent. An admin running `/aircore var add coins 25000 Steve` fires it. The reaction is about the size of that write, not whether `coins` crossed some fixed total like `1000` or `10000`.
{% endtab %}
{% endtabs %}


---

# 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://airdevelopment.gitbook.io/docs/aircore/configuration/modules/variables.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.
