> For the complete documentation index, see [llms.txt](https://docs.spoof.dev/asteroid/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.spoof.dev/asteroid/configuration/commands-and-scheduler.md).

# Commands and Scheduler

This page covers making fake players run commands, chat, and do actions. It is the most powerful part of Asteroid.

There are two moments where commands happen:

1. **When a fake player joins**, handled by the `commands` section (on-join, one-time, and void commands).
2. **While a fake player is online**, handled by the `scheduler` section, which fires actions at random intervals.

Both share the same idea, but they use slightly different prefixes. The tables below make the differences clear.

## The building blocks

### Prefixes

A prefix at the start of a command tells Asteroid how to run it. Important: the join-time systems and the scheduler have different defaults and different prefixes.

| Prefix      | On-join, one-time, void               | Scheduler                           |
| ----------- | ------------------------------------- | ----------------------------------- |
| no prefix   | Runs as a **console** command         | Runs **as the fake player**         |
| `chat!`     | Chat message **as the fake player**   | Chat message **as the fake player** |
| `sudo!`     | Runs a command **as the fake player** | not used                            |
| `console->` | not used                              | Runs as a **console** command       |

Read that table twice. The most common mistake is expecting a bare command to run as the player in the on-join list (it runs as console there) or as console in the scheduler (it runs as the player there).

```
eco give %player% 1000          on-join: console gives the fake money
chat!hey everyone               either: the fake says "hey everyone" in chat
sudo!spawn                       on-join: the fake runs /spawn as itself
console->eco give %player% 1000  scheduler: console gives the fake money
```

### Placeholders

| Placeholder       | Replaced with                                    |
| ----------------- | ------------------------------------------------ |
| `%player%`        | The fake player's name                           |
| `{random[x,y]}`   | A random number from x to y                      |
| `{random[x,y,z]}` | A random number from x to y, multiples of z only |

### Running several commands at once with `&&`

You can chain multiple commands in a single line using `&&`. They run in order, left to right. This works in the on-join lists **and** in the scheduler.

```
console->give %player% netherite_sword 1 && ah sell 100000
```

That runs two things: the console gives the fake a netherite sword, then the fake runs `/ah sell 100000`. Each piece keeps its own prefix, so you can mix a console command and an as-player command in one chain. The pieces run one after another, in order.

## On-join commands

Every time a fake joins, each command in this list is rolled on its own. Several can fire on the same join.

```yaml
commands:
  on_join_commands:
    enabled: true
    delay: 5
    commands:
      - '50:eco give %player% {random[1000,10000,100]}'
      - '80:eco give %player% {random[100,9000]}'
      - '10:eco give %player% {random[10000,100000,5]}'
    pick_one: []
```

Each entry is `chance:command`, where `chance` is a 0 to 100 percent chance that entry runs. Each rolls separately, so a fake might get none, one, or all of them. Use `-999` to guarantee an entry always runs.

### Pick-one commands (weighted)

Use `pick_one` when you want exactly one thing chosen by weight instead of independent rolls.

```yaml
pick_one:
  - '50:eco give %player% {random[1000,10000,100]}'
  - '80:eco give %player% {random[100,9000]}'
  - '-999:chat!this always runs regardless of the pick'
```

Here the number is a **weight**, not a percent. With weights 50 and 80, the 80 entry is picked about 62 percent of the time. Only one fires per join. An entry with `-999` always runs, separately from the pick.

## One-time commands

Just like on-join commands (independent `chance:command` rolls plus an optional `pick_one`), but each entry runs only **once per profile, ever**, tracked across restarts. Good for first-join starter kits.

```yaml
one_time_commands:
  enabled: true
  delay: 5
  commands:
    - '100:chat!one time command!'
  pick_one: []
```

## Void commands

Runs commands on fakes that fall below a Y level, for example into the void. All fakes are checked every 10 ticks, and each triggers only once.

```yaml
void_commands:
  enabled: false
  min_y: -64
  disconnect: false
  commands:
    - '100:eco set %player% 0'
```

`min_y` is the threshold, `disconnect` decides whether the fake is kicked afterward, and `commands` uses the same `chance:command` format and prefixes as on-join.

## The scheduler

While a fake is online, the scheduler gives it something to do at random intervals: chatting, selling items, or starting AI conversations.

```yaml
scheduler:
  time: '{random[10,60]}'
  cooldown: 5
  commands:
    - ":100:{initAIConv}"
    - '{updateRandomItemInMainHand}:80:pa sell {random[100,1000,50]}'
    - '{updateRandomItemInMainHand}:60:pa sell {random[100,1000]}'
```

* `time`: seconds between each pick, per fake.
* `cooldown`: seconds across all fakes between any two scheduler actions. Set to 0 to disable.
* `commands`: the weighted list. One entry is picked per cycle.

### The scheduler entry format

```
{function}:{weight}:{command}
```

* `{function}` is an optional action run **before** the command. You can list several, separated by colons. Leave it empty like `:80:some command`.
* `{weight}` is the relative weight for the weighted pick.
* `{command}` uses the scheduler prefixes from the table above.

Each cycle, Asteroid picks **one** entry by weight, runs its functions, then runs its command. With weights 100, 80, and 60 (total 240), they are picked about 42, 33, and 25 percent of the time.

### Functions

Functions are pre-actions that only exist in the scheduler.

| Function                       | What it does                                                       |
| ------------------------------ | ------------------------------------------------------------------ |
| `{updateRandomItemInMainHand}` | Swaps the held item to a random one from the fake's inventory      |
| `{randomItemInMainHand}`       | Re-equips a random item into the main hand                         |
| `{equipRandomArmor}`           | Puts on 1 to 3 random armor pieces from the inventory              |
| `{equipWholeArmor}`            | Fills all armor slots with matching pieces from the inventory      |
| `{initAIConv}`                 | Starts an AI conversation (needs AI enabled). Used as the command. |

You can stack functions: `'{equipWholeArmor}:{updateRandomItemInMainHand}:80:pa sell {random[100,1000]}'` runs two functions, then one command. Functions run **before** the command and only move items the fake already owns. They do not give new items.

## Worked example: give, hold, then sell

`/ah sell` sells the item the fake is **holding**, so the order matters. The cleanest way is a chained command that gives the item then sells it:

```yaml
scheduler:
  commands:
    - ':80:console->give %player% netherite_sword 1 && ah sell 100000'
```

If the auction plugin reads the main hand and the sword is not sold, the item may not be held yet when the sell fires. Either give the item into the held slot, or split it into a give step and a sell step. You can also hold an existing item with a function first: `'{updateRandomItemInMainHand}:80:pa sell {random[100,1000]}'`.

## Command overrides

Command overrides intercept commands real players run against a fake and send back a believable fake response.

```yaml
command_overrides:
  command1:
    sent: "msg {fakePlayer}"
    response: "You have messaged {fakePlayer} the text: {playerText}"
```

`sent` is the pattern to intercept (use `{fakePlayer}` for the name; text after it is captured as `{playerText}`), and `response` is the reply. By default this section is empty (`command_overrides: {}`); to enable, replace the `{}` with nothing and add entries beneath it.
