# Welcome to Halos Development

A quick overview on what will be in these docs.

{% hint style="info" %}
Join our [discord server](https://discord.gg/PF5wqRz6m7) for support.&#x20;
{% endhint %}

## Overview

Welcome to Halos Development official docs for all our products

> Some of the things we will going over in the docs is permissions, commands, configuration.
>
> There will be also a step by step on installing products on your server.&#x20;

## Quick links

{% content-ref url="/pages/hvOyb3J0MORnxRjzlk7e" %}
[About Us](/overview/about-us)
{% endcontent-ref %}


# About Us

Quick Information on Halos development.

## What do we do?

Halos Development provides high quality Minecraft Development Services. We offer a range of services including Plugin Configuration, Server Setups, Server Optimization, Web Nameless MC Setup, Part Time Development, Server Marketing, Video editing/Content Creation, and Minecraft Logo/Banner Creation. We make sure that every client is satisfied with our services.&#x20;


# First Install

HalosAntiXray is delivered through **HalosLoader**, our loader plugin. The loader verifies your license, downloads the latest build automatically, and keeps it up to date. You never need to manually download and upload jars for updates.

### Step 1 - Put the Loader in your plugins folder

Drop **HalosLoader.jar** into the `plugins` folder on your Minecraft server.

### Step 2 - Start the server once

Start the server so the loader can generate its config file:

```
plugins/HalosLoader/config.yml
```

HalosAntiXray won't load yet. That's expected, you still need to enable it in the next steps.

### Step 3 - Get your license key

Join our [Discord server](https://discord.gg/halosdev) and open a support ticket to verify your purchase. Make sure you include **proof of purchase**. Our team will send you your **license key**.

### Step 4 - Enable HalosAntiXray in the loader config

Open `plugins/HalosLoader/config.yml` and find the HalosAntiXray entry under `plugins`. Set `enabled` to `true` and paste your license key:

```yaml
plugins:
- name: HalosAntiXray
  enabled: true
  license: XXXX-XXXX-XXXX
  version: STABLE
```

{% hint style="info" %}
Leave `version` on `STABLE`. Only switch to `DEV` or `PREV` if our team asks you to.
{% endhint %}

### Step 5 - Restart the server

Restart your server. The loader will verify your license and download HalosAntiXray. Once it loads, a `plugins/HalosAntiXray` folder will generate with the plugin's config files. From there you can set up HalosAntiXray however you like.

That's it, you're installed.

### Troubleshooting

**HalosAntiXray didn't load after restarting?** Check these in order:

1. `enabled` is set to `true` in `plugins/HalosLoader/config.yml`
2. Your license key is pasted correctly (no extra spaces, replaces the `XXXX-XXXX-XXXX` placeholder)
3. Your server has internet access, the loader needs it to verify your license and download the plugin
4. Check the server console for HalosLoader errors on startup

Still stuck? Open a ticket in our [Discord](https://discord.gg/halosdev) and send us your startup log.


# Overview

## HalosAntiXray

HalosAntiXray protects your Minecraft server against X-Ray and ESP cheating with three independent layers: **Anti X-Ray**, **Anti ESP**, and **Fake Ores with detection scoring**. This page covers every option in `config.yml` and how to tune it.

For commands and permissions, see the [Commands & Permissions](/halos-anti-xray/commands) page.

***

### Table of Contents

* [Anti X-Ray](#anti-x-ray)
* [Anti ESP](#anti-esp)
* [Fake Ores](#fake-ores)
  * [Detection & Alerts](#detection-and-alerts)
  * [Per-World Generation](#per-world-generation)
* [Bypass System](#bypass-system)
* [Threads](#threads)
* [Player Data Storage](#player-data-storage)
* [Ghost Block Environment](#ghost-block-environment)
* [Folia Compatibility](#folia-compatibility)
* [Admin Command](#admin-command)

***

### Anti X-Ray

Prevents X-Ray clients from seeing ore blocks. Each entry maps a list of ores to a single replacement block.

```yaml
anti-xray:
  replacements:
    - ores:
        - coal_ore
        - iron_ore
        - copper_ore
        - gold_ore
        - redstone_ore
        - emerald_ore
        - lapis_ore
        - diamond_ore
      replacement: stone

    - ores:
        - deepslate_coal_ore
        - deepslate_iron_ore
        - deepslate_copper_ore
        - deepslate_gold_ore
        - deepslate_redstone_ore
        - deepslate_emerald_ore
        - deepslate_lapis_ore
        - deepslate_diamond_ore
      replacement: deepslate

    - ores:
        - nether_gold_ore
        - nether_quartz_ore
        - ancient_debris
      replacement: netherrack
```

Invalid ore or replacement names are logged as warnings on startup and skipped.

{% hint style="info" %}
Match the replacement to the surrounding terrain (stone for the stone layer, deepslate for the deepslate layer, netherrack for the Nether). Mismatched replacements create visible patches.
{% endhint %}

***

### Anti ESP

Hides container-style blocks (chests, furnaces, hoppers, spawners, etc.) from players until those blocks become visible to them.

```yaml
anti-esp:
  anti-esp-blocks:
    - chest
    - trapped_chest
    - furnace
    - blast_furnace
    - smoker
    - crafting_table
    - anvil
    - chipped_anvil
    - damaged_anvil
    - enchanting_table
    - end_portal_frame
    - spawner
    - trial_spawner
    - dropper
    - dispenser
    - hopper
    - barrel
    - '*_bed'
    - nether_portal
    - end_portal
    - ender_chest
```

#### Pattern matching

| Pattern  | Matches                                                                 |
| -------- | ----------------------------------------------------------------------- |
| `chest`  | Exact match only — `chest` itself, not `trapped_chest` or `ender_chest` |
| `*_bed`  | Every bed colour (`white_bed`, `orange_bed`, …) but **not** `bedrock`   |
| `*_door` | Every door variant (`oak_door`, `iron_door`, …)                         |
| `*bed*`  | Anything containing `bed` — including `bedrock`                         |

Patterns matching zero block types are logged as warnings on startup. Invalid names without wildcards are warned about and skipped.

{% hint style="warning" %}
Anti ESP is more expensive than Anti X-Ray. Only add blocks that aren't generated naturally in bulk — adding common natural blocks (stone, dirt) will severely impact server performance.&#x20;
{% endhint %}

***

### Fake Ores

Generates decoy ore clusters to detect X-Ray cheaters. Cheaters who try to mine these decoys reveal themselves.

```yaml
fake-ores:
  enabled: true
  view-distance: 3
```

| Option          | Description                                                      |
| --------------- | ---------------------------------------------------------------- |
| `enabled`       | Master switch for fake ore generation.                           |
| `view-distance` | Radius (in chunks) around the player where fake ores are active. |

{% hint style="warning" %}
Disabling `fake-ores.enabled` also disables detection scoring. The detection layer depends on fake ores being active.
{% endhint %}

#### Detection & Alerts

```yaml
fake-ores:
  detection:
    enabled: true
    low-confidence: 30
    high-confidence: 40

    alerts:
      default-enabled: true
      check-interval: 60
```

| Option                   | Description                                                                                   |
| ------------------------ | --------------------------------------------------------------------------------------------- |
| `detection.enabled`      | Toggles detection scoring and staff alerts.                                                   |
| `low-confidence`         | Score threshold for a "possible cheater" alert. Lower = more sensitive, more false positives. |
| `high-confidence`        | Score threshold for a "likely cheater" alert.                                                 |
| `alerts.default-enabled` | Default alert preference for staff. Each player can override their own via `/hax alerts`.     |
| `alerts.check-interval`  | How often (in seconds) the plugin re-evaluates player scores for alerting.                    |

**Tuning:**

* Too many false alerts? **Raise** the thresholds.
* Suspected cheaters slipping through? **Lower** the thresholds.

Adjust one threshold at a time and give the server a day or two of activity before re-tuning.

#### Per-World Generation

Fake ore generation is configured **per world**. Only worlds listed and marked `enabled: true` receive fake ores. World names are matched case-insensitively.

```yaml
fake-ores:
  worlds:
    world:
      enabled: true
      generation:
        - stone-ore-type: coal_ore
          deepslate-ore-type: deepslate_coal_ore
          vein-size: 16
          min-height: 0
          max-height: 127
          rarity: 0.95
        - stone-ore-type: diamond_ore
          deepslate-ore-type: deepslate_diamond_ore
          vein-size: 8
          min-height: -64
          max-height: 16
          rarity: 0.7

    world_nether:
      enabled: true
      generation:
        - ore-type: ancient_debris
          vein-size: 4
          min-height: 0
          max-height: 24
          rarity: 0.65
```

| Option               | Description                                                                                      |
| -------------------- | ------------------------------------------------------------------------------------------------ |
| `stone-ore-type`     | Ore variant used at stone-layer heights (overworld).                                             |
| `deepslate-ore-type` | Ore variant used at deepslate-layer heights. Optional — omit to use `stone-ore-type` everywhere. |
| `ore-type`           | Single ore type for non-overworld worlds (e.g. Nether) without stone/deepslate variants.         |
| `vein-size`          | Cluster size. Higher = larger veins.                                                             |
| `min-height`         | Minimum Y level for generation.                                                                  |
| `max-height`         | Maximum Y level for generation.                                                                  |
| `rarity`             | Spawn frequency from `0.0` to **just under** `1.0`. Lower = rarer.                               |

{% hint style="info" %}
Mirror vanilla ore distributions when picking heights — diamonds at Y=80 look wrong, diamonds at Y=-50 look natural.&#x20;
{% endhint %}

***

### Bypass System

Per-player **bypass** flag — when enabled, that player sees the unfiltered world (all three protection layers skipped). Useful for staff verification and trusted players you don't want flagged.

There is no config option for bypass; it's managed entirely through `/hax toggle` (see [Commands & Permissions](/halos-anti-xray/commands)). Bypass state is persisted via the configured `player-data` storage and survives restarts.

***

### Threads

Performance tuning per subsystem.

```yaml
threads:
  fake-ore-generator: 1
  ghost-block-ticker: 2
  outgoing-packet-handler: 4
  force-async-block-checks: false
```

| Option                     | Recommended                                  | Purpose                                 |
| -------------------------- | -------------------------------------------- | --------------------------------------- |
| `fake-ore-generator`       | 1/8 of CPU cores (1/4 for higher view-dist.) | Threads for fake ore generation.        |
| `ghost-block-ticker`       | 1/4 of CPU cores                             | Threads for ongoing block updates.      |
| `outgoing-packet-handler`  | 1/2 of CPU cores                             | Threads for outbound packet processing. |
| `force-async-block-checks` | `false`                                      | See warning below.                      |

{% hint style="danger" %}
**`force-async-block-checks`**: Disabled by default on Minecraft versions below 1.13 because older Bukkit, Spigot, and Paper builds are not thread-safe. Forcing this on pre-1.13 servers causes ghost blocks and undefined behaviour. Use at your own risk.&#x20;
{% endhint %}

***

### Player Data Storage

The plugin persists per-player flags (bypass status, alert preferences) between sessions.

```yaml
player-data:
  storage: sqlite
```

| Backend  | Notes                                                                                                    |
| -------- | -------------------------------------------------------------------------------------------------------- |
| `sqlite` | Efficient at any player count. **Recommended for production.**                                           |
| `yaml`   | Human-readable, easy to hand-edit, but rewrites the entire file on each change. Use only at small scale. |

Unrecognized values fall back to `sqlite` with a warning logged.

{% hint style="warning" %}
Switching backends does **not** migrate data. Players fall back to defaults until re-flagged.
{% endhint %}

***

### Ghost Block Environment

Throttles for how aggressively the plugin processes block updates each tick. These are **per-player** limits.

```yaml
ghost-block-environment:
  unchecked-ghost-blocks-per-tick: 1500
  tickable-blocks-per-tick: 200
```

| Option                            | Description                                                        |
| --------------------------------- | ------------------------------------------------------------------ |
| `unchecked-ghost-blocks-per-tick` | Max new Anti X-Ray block updates processed per tick, per player.   |
| `tickable-blocks-per-tick`        | Max ongoing Anti ESP block updates processed per tick, per player. |

Raise if players see delayed ore-reveal on chunk load. Lower if you see tick lag on busy worlds.

***

### Folia Compatibility

HalosAntiXray supports [Folia](https://papermc.io/software/folia). On Folia:

* `threads.fake-ore-generator` and `threads.ghost-block-ticker` are **ignored** — Folia uses its own region-based scheduler.
* `threads.outgoing-packet-handler` still applies.
* No additional configuration required.

***

### Admin Command

```yaml
hax-command: 'hax'
```

Default is `/hax`. Change this if it conflicts with another plugin. See [Commands & Permissions](/halos-anti-xray/commands) for full command and permission details.

***

### Quick Reference: Disabling Features

| To disable…                 | Set…                                                   |
| --------------------------- | ------------------------------------------------------ |
| Anti X-Ray for an ore       | Remove that ore from `anti-xray.replacements`          |
| Anti ESP for a block        | Remove that block from `anti-esp.anti-esp-blocks`      |
| Fake ore generation         | `fake-ores.enabled: false`                             |
| Detection alerts            | `fake-ores.detection.enabled: false`                   |
| Fake ores in one world      | `fake-ores.worlds.<name>.enabled: false`               |
| All staff alerts by default | `fake-ores.detection.alerts.default-enabled: false`    |
| Protection for one player   | `/hax toggle <player>` (persistent — survives restart) |


# Commands

All staff-facing functionality in HalosAntiXray is exposed through a single command — /hax — gated by a small set of permission nodes.

The command label is configurable via `hax-command` in `config.yml` (default: `hax`). All examples on this page assume the default.

### Permission Nodes

| Permission                                 | Grants                                                                |
| ------------------------------------------ | --------------------------------------------------------------------- |
| `halosantixray.commands.hax`               | Access to the `/hax` command and all base subcommands.                |
| `halosantixray.commands.hax.toggle`        | Use of `/hax toggle` to bypass protection for yourself.               |
| `halosantixray.commands.hax.toggle.others` | Use of `/hax toggle <player>` to bypass protection for other players. |
| `halosantixray.alerts`                     | Eligibility to receive X-Ray alert messages in chat.                  |


# Setup Guide

Here is some important guides needed to start using HalosPlayerSpoofer. Please make sure you read the guide before asking for help.


# First Install

HalosPlayerSpoofer is delivered through **HalosLoader**, our loader plugin. The loader verifies your license, downloads the latest build automatically, and keeps it up to date. You never need to manually download and upload jars for updates.

{% hint style="info" %}
**Running a network?** Install HalosLoader on your proxy (BungeeCord or Velocity) **and** on every backend server you want fakes to join. The steps below are the same on each one, and your license key works on all of them.
{% endhint %}

### Step 1 - Put the Loader in your plugins folder

Drop **HalosLoader.jar** into the `plugins` folder on your Minecraft server. On a network, do this on the proxy and every backend server.

### Step 2 - Start the server once

Start the server so the loader can generate its config file:

```
plugins/HalosLoader/config.yml
```

HalosPlayerSpoofer won't load yet. That's expected, you still need to enable it in the next steps.

### Step 3 - Get your license key

Join our [Discord server](https://discord.gg/halosdev) and open a support ticket to verify your purchase. Make sure you include **proof of purchase**. Our team will send you your **license key**.

One license key covers your whole network. Use the same key on the proxy and every backend server.

### Step 4 - Enable HalosPlayerSpoofer in the loader config

Open `plugins/HalosLoader/config.yml` and find the HalosPlayerSpoofer entry under `plugins`. Set `enabled` to `true` and paste your license key:

```yaml
plugins:
- name: HalosPlayerSpoofer
  enabled: true
  license: XXXX-XXXX-XXXX
  version: STABLE
```

On a network, repeat this on every server running the loader.

{% hint style="info" %}
Leave `version` on `STABLE`. Only switch to `DEV` or `PREV` if our team asks you to.
{% endhint %}

### Step 5 - Restart the server

Restart your server (on a network, restart the proxy and backends). The loader will verify your license and download HalosPlayerSpoofer. Once it loads, a `plugins/HalosPlayerSpoofer` folder will generate with the plugin's config files.

From there, follow the additional pages below for configurations on spigot server and network setup.

### Troubleshooting

**HalosPlayerSpoofer didn't load after restarting?** Check these in order:

1. `enabled` is set to `true` in `plugins/HalosLoader/config.yml`
2. Your license key is pasted correctly (no extra spaces, replaces the `XXXX-XXXX-XXXX` placeholder)
3. Your server has internet access, the loader needs it to verify your license and download the plugin
4. Check the server console for HalosLoader errors on startup

Still stuck? Open a ticket in our [Discord](https://discord.gg/halosdev) and send us your startup log.


# Spigot Plugin Configurations

These are some important settings you should turn on use your plugin successfully.

## Step 1 - Configure Security Features

First thing that I highly recommend that you do to keep the plugin hidden is change the plugins details. The configuration option can be found in the config.yml .&#x20;

```yaml
plugin_info: # Modify plugin information
  name: "HalosPlayerSpoof" # Name that appears when using /plugins
  authors: [ "HalosDev" ] # Author that appears when using /version
  version: "1.0.0" # Version that appears when using /version
  command_proxy: "spoof" # Change the name of the main plugin command
```

## Step 2 - Configure Fluctuation. (Skip if running Velocity/Bungeecord)

Fluctuation will be the backbone to how the plugin works and spoofs your player counts. The configuration config may be a little tricky to get correct for your server.&#x20;

```yaml
provider:
  # Available types: NAMEMC, LOCAL
  type: NAMEMC
  # This URL is ONLY relevant if your using namemc account generator.
  url: https://api.namemc.com/server/mc.hypixel.net/likes
min-delay: 5
max-delay: 30
max-fake: 50
# Define how many real players need to be online before fluctuation will start.
# If set to -1 it will always run.
min-real-players: -1
# Customize LifeSpan.
# How long a fake player will stay on the server before leaving.
# Numbers are in mins here.
min-lifespan: 5
max-lifespan: 10
```

This field is very important. This will control min and max of spoof players on your server. The percentages will fluctuate in between and will not go under or over.

```yaml
max-fake: 50
```

This setting will determine at what real player count will flucation turn on. If you have a smaller server I suggest you do 1-5. Bigger servers you can do more.&#x20;

```yaml
min-real-players: -1
```

## Step 3 - Configure Ping Module

Setting the ping will make your spoof players look more legit when looked at from the tab list. So it is make sure you set them correctly.&#x20;

```yaml
  ping:
    enabled: true
    settings:
      delay-min: 10
      delay-max: 25
      ping-min: 50
      ping-max: 150
```

## Step 4 - Configure Ranks Module

The ranks module is a very important module to have configured. So that spoofed players will have ranks to match the real players on your server.

```yaml
enabled: true
min-delay: 30
max-delay: 60
persistent-ranks: true
ranks:
  default: 100
```

If you have a Minecraft network, we recommend going to the next page and learn how to setup our Network sync option.


# Proxy Setup

So you're running a network with a **Velocity** or **BungeeCord** proxy and want **HalosPlayerSpoofer (HPS)** running across it? You're in the right place. This guide takes you through the whole thing step by step. No guesswork, no prior experience needed. By the end, your proxy and your backend servers will be working together as one.

### Before You Start

Grab these things first, and the rest of the setup will be smooth sailing:

* The **HalosLoader** `.jar` file (HPS installs through it, and it keeps the plugin up to date for you)
* Your HPS license key. Don't have one yet? Join [discord.gg/halosdev](https://discord.gg/halosdev) and open a ticket with your proof of purchase
* Access to your proxy (Velocity or BungeeCord) and your backend servers (Spigot/Paper)
* A **MySQL** database **or** a **Redis** server. Your proxy and servers use this to stay in sync, so it's a must-have

{% hint style="info" %}
HPS needs to be installed on your **proxy** and on **every backend server you want fake players on**. You don't have to install it on all of your servers, just the ones you want spoofed. One license key covers your whole network. If you skip some servers, there's one small extra step later in Step 4, and we'll walk you through it.
{% endhint %}

***

### Step 1 — Install HPS on Your Proxy (via HalosLoader)

Let's start at the top of your network. If you haven't already, follow the [First Install guide](https://wiki.halosdev.com/halos-player-spoofer/setup-guide/first-install) on your proxy. In short:

1. Drop `HalosLoader.jar` into your proxy's `plugins` folder.
2. Start the proxy once so the loader generates its config, then open `plugins/HalosLoader/config.yml`.
3. Find the HalosPlayerSpoofer entry, set it to enabled, and paste in your license key:

```yaml
plugins:
- name: HalosPlayerSpoofer
  enabled: true
  license: XXXX-XXXX-XXXX
  version: STABLE
```

4. Restart the proxy. The loader verifies your license, downloads the latest HPS build, and generates the plugin's config at `plugins/HalosPlayerSpoofer/config.yml`. Open that file up, we'll be working in here for the next step.

### Step 2 — Connect Your Database (Proxy)

In the proxy's `plugins/HalosPlayerSpoofer/config.yml`, find the `database` section. This is how your proxy and servers talk to each other, so let's get it right.

Set `type` to either `SQL` or `REDIS`, then fill in your connection details:

{% hint style="warning" %}
On a network, `type` must **not** be left as `NONE`. Without a database, your proxy and servers have no way to sync, and nothing will work.
{% endhint %}

**Option A — MySQL:**

```yaml
database:
  type: SQL
  sql:
    host: localhost
    port: 3306
    database: spoofer
    username: root
    password: yourpassword
```

**Option B — Redis:**

```yaml
database:
  type: REDIS
  redis:
    host: localhost
    port: 6379
    password: yourpassword
```

Save the file and restart the proxy. If the console comes up clean with no database errors, you're ready for the next step.

### Step 3 — Install HPS on Your Backend Servers

Now repeat this for **each Spigot/Paper server you want fake players on**. It only takes a couple of minutes per server:

1. Install HPS through HalosLoader, exactly like you did on the proxy: drop `HalosLoader.jar` into the server's `plugins` folder, start the server once, enable HalosPlayerSpoofer and add your license key in `plugins/HalosLoader/config.yml`, then restart. (Full details in the [First Install guide](https://wiki.halosdev.com/halos-player-spoofer/setup-guide/first-install).)
2. Open `plugins/HalosPlayerSpoofer/config.yml`.
3. Scroll to the `proxy` section and set it up like this:

```yaml
proxy:
  proxy-mode: true
  server-name: lobby1
  database:
    type: SQL
    sql:
      host: localhost
      port: 3306
      database: spoofer
      username: root
      password: yourpassword
```

Here's what each part means:

* `proxy-mode` sets this server up as part of a network. Flip it to `true`.
* `server-name` is this server's unique ID. It **must exactly match** the name you gave this server in your proxy config (the `[servers]` section of `velocity.toml`, or the `servers` section of BungeeCord's `config.yml`).
* `database` needs the **exact same** settings you used on the proxy in Step 2.

{% hint style="warning" %}
Every server needs its **own unique** `server-name`, and it has to match the proxy config exactly. If two servers share a name, or a name doesn't line up with the proxy, fake players won't sync. This is the single most common setup mistake, so double-check it!
{% endhint %}

4. One more thing: check the `account-fetcher` setting. It must be the **same value everywhere**, on the proxy and all servers. The default (`MINE_TOOLS`) is fine for most networks.
5. Save and restart the server. Then move on to the next one.

### Step 4 — Restart and Verify

Almost there! Time to bring it all online:

1. Restart your proxy and your backend servers one more time.
2. On its first proper startup, the proxy looks at your network and automatically creates a settings file for **every** server it knows about in `plugins/HalosPlayerSpoofer/servers/` (for example `lobby1.yml`, `survival.yml`). Yes, that includes servers you didn't install HPS on.
3. **Did you skip HPS on some servers?** Then open the matching file in the `servers/` folder for each one and set `active: false` and `primary-server: false`. This tells HPS to leave those servers alone. Also make sure they're not listed in `fallback-server-ordering` in the proxy's `config.yml`, so fake players are never sent there. Then run `/spoof reload`.
4. Check the proxy console. HPS prints a handy summary at startup showing each server and whether fluctuation is `ACTIVE` on it, along with any day/time overrides. It's the quickest way to confirm everything is wired up correctly.
5. Give it a moment, and you should see fake players start to appear.

{% hint style="warning" %}
This part matters: any server that does **not** have HPS installed must be disabled in its file in the `servers/` folder (`active: false` and `primary-server: false`). If you leave it enabled, HPS will try to send fake players to a server that can't handle them, and you'll run into errors.
{% endhint %}

{% hint style="success" %}
Seeing fake players on your network? That's it, your setup is done! 🎉 Read on to learn what everything in the config does and how to fine-tune the behavior.
{% endhint %}

***

## The Proxy Config, Explained

Here's a plain-English tour of everything you'll find in the proxy's `config.yml`, so you know exactly what each setting does and whether you should touch it.

#### `debug`

Turns on extra logging in the console. Handy if you're troubleshooting or our support team asks you for logs. Otherwise, feel free to set it to `false` to keep your console quiet.

#### `database`

Your MySQL or Redis connection, covered in Step 2 above. This is the backbone of the network setup.

{% hint style="info" %}
If you're an advanced user, you can also provide a full connection `url` instead of the host/port fields (for example `jdbc:mysql://localhost:3306/spoofer` or `redis://localhost:6379`). When a `url` is set, the host/port fields are ignored. If you're not sure whether you need this, you don't. 🙂
{% endhint %}

#### `plugin-disguise`

Lets you rename the HPS command to whatever you like. By default the command is `/spoof`, but you can change `spoof-command` to anything. Nice for keeping things low-key.

{% hint style="info" %}
On a network, set the same command name on the proxy **and** every backend server, so everything responds to the same command.
{% endhint %}

#### `account-fetcher`

Where HPS gets the usernames and skins for its fake players. Your options:

* `MINE_TOOLS` is the default and works great for most networks
* `PLAYER_DB` is an alternative source if you have trouble with the default
* `OFFLINE` is for offline-mode (cracked) networks

If your network runs in **online mode**, you can't use `OFFLINE`. If your network runs in **offline mode**, you can still use an online fetcher. You'll just be limited to real online-mode accounts.

{% hint style="warning" %}
Whatever you pick, it must be the **same value everywhere**: on the proxy and on every backend server.
{% endhint %}

#### `account-fetcher-timeout`

How long HPS waits when fetching account data before giving up. The default (`2s`) is fine for almost everyone. Only bump it up if you start seeing time-out errors about account fetching in your console.

#### `enable-fluctuation`

The master switch for fluctuation (the automatic joining and leaving of fake players). On a network, fluctuation runs from the proxy, so you'll want this set to `true` here.

{% hint style="warning" %}
Running more than one proxy? Enable fluctuation on **only one** of them, or they'll both be adding players and your counts will double up.
{% endhint %}

#### `kick-fake-players-on-real-player-join`

If a real player tries to log in with an account that a fake player happens to be using, this kicks the fake player so the real one gets in without an "already online" error. Keep it on. There's really no reason to turn it off.

#### `fluctuation → account-provider`

Where HPS gets its pool of accounts to pick fake players from:

* `NAMEMC` (default) pulls a list of accounts from a NameMC URL. You can point the `url` at any NameMC server-likes page. A popular server's likes page gives you a big, realistic pool of names.
* `LOCAL` uses your own list of accounts from an `accounts.json` file in the proxy's `plugins/HalosPlayerSpoofer/` folder. Perfect if you want full control over exactly which names show up. The file is just a list of usernames or UUIDs:

```json
[
  "Notch",
  "7389929f-8656-4755-aa2a-eb92d2358097",
  "AnotherUsername"
]
```

{% hint style="info" %}
When using `LOCAL`, make sure your list has plenty of accounts in it. If your list is smaller than the number of fake players you're asking for, HPS will run out of accounts to use.
{% endhint %}

#### `fluctuation → fallback-server-ordering`

Your safety net. If one of your servers shuts down, the fake players on it get moved to the first available server on this list, checked from top to bottom:

```yaml
fluctuation:
  fallback-server-ordering:
    - lobby1
    - lobby2
```

Put your lobbies at the top since they're always a safe place to land. HPS fills this in automatically on first startup, but feel free to reorder it.

{% hint style="warning" %}
Only list servers that actually have HPS installed. A fallback server without HPS can't receive fake players.
{% endhint %}

#### `fluctuation → enable-account-blacklist`

When a real player joins your network, HPS remembers their account and will never use it for a fake player again. This keeps things looking natural, so your regulars won't spot "themselves" online. Recommended to leave this on.

***

## Fluctuation (Fake Player Behavior)

{% hint style="info" %}
As of **v3.0**, fluctuation on a network is controlled entirely **from the proxy**. You no longer configure fluctuation on the backend servers. Everything lives in the proxy's `servers/` folder.
{% endhint %}

### Per-Server Settings

Each server on your network gets its own file in `plugins/HalosPlayerSpoofer/servers/`. HPS generates sensible defaults automatically based on each server's name (it recognizes lobbies, survival, creative, minigames, and so on), so things work well right out of the box. But tweaking these files is where you make the plugin truly yours.

The settings you'll care about most:

| Setting                                             | What it does                                                                                                                                                                |
| --------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `primary-server`                                    | Whether fake players can join the network through this server. Usually `true` for lobbies/hubs, `false` for gamemodes.                                                      |
| `active`                                            | Turns fluctuation on or off for this server only. Set it to `false` (along with `primary-server: false`) on any server that doesn't have HPS installed.                     |
| `min-fixed-fake-players` / `max-fixed-fake-players` | The base number of fake players kept on this server.                                                                                                                        |
| `min-real-multiplier` / `max-real-multiplier`       | Adds extra fake players based on your real player count (e.g. `1.5` means up to 1.5 fake players per real player). Your fake count grows naturally as your real count does. |
| `max-total-fake-players`                            | A hard cap on fake players for this server, no matter what the other settings add up to. If your counts feel stuck at a certain number, check this first.                   |
| `min-real-players`                                  | How many real players need to be online before fluctuation kicks in. Set to `-1` to always run.                                                                             |
| `min-lifespan` / `max-lifespan`                     | How long each fake player sticks around on this server before leaving.                                                                                                      |
| `move-chance`                                       | The chance (0-100) that a leaving fake player hops to another server instead of logging off the network.                                                                    |
| `random-walk-chance`                                | How often the player count drifts up or down. Higher means more joins and leaves, lower means a steadier count.                                                             |
| `min-tick-interval` / `max-tick-interval`           | How often HPS re-evaluates the player count on this server. The defaults are fine for almost everyone.                                                                      |
| `override-options`                                  | Day and time based overrides, explained just below.                                                                                                                         |

After editing any server file, run `/spoof reload` on the proxy (or restart it) to apply your changes.

### Day and Time Overrides

Real networks are busier on weekends and evenings, and your fake counts should be too. That's what `override-options` is for. HPS even generates Saturday and Sunday boosts for you by default.

Each override picks a day, a time window, and a full set of options to use during that window:

```yaml
override-options:
  - day: SATURDAY
    start-hour: 18
    end-hour: 23
    options:
      active: true
      max-total-fake-players: 35
      min-fixed-fake-players: 4
      max-fixed-fake-players: 15
      # ... and the rest of the options
```

A few things to know:

* `day` is the day of the week in capital letters (`MONDAY`, `TUESDAY`, and so on).
* `start-hour` and `end-hour` use 24-hour time and are both inclusive. `0` to `23` covers the full day.
* The `options` block is a complete replacement, not a patch. Fill in every option, not just the ones you're changing.
* Want quiet hours instead of a boost? Add an override with `active: false` for that window.
* Don't want any overrides? Set the whole thing to nothing: `override-options:`

{% hint style="warning" %}
Overrides follow the **clock and timezone of the machine your proxy runs on**. If your host is in a different timezone than your players, shift your hours to match. If your weekend boost seems to fire at odd times, this is almost always why.
{% endhint %}

### Adding, Renaming, or Removing Servers Later

This is the part that catches most people out, so give it a quick read.

HPS only auto-generates the `servers/` files **once**, on the first startup when the folder is empty. It won't touch your files after that, so your tweaks are always safe. But that also means:

**Adding a new server to your network?** HPS won't create a file for it automatically. The proxy console will show `yourserver -> Fluctuation DISABLED` and no fake players will go there. To fix it, install HPS on the new server (see Step 3), then copy one of your existing files in `plugins/HalosPlayerSpoofer/servers/`, rename the copy to match the new server's name (in lowercase, e.g. `skyblock.yml`), adjust the settings, and run `/spoof reload`.

**Renaming or removing a server?** Rename or delete its matching file in the `servers/` folder too. If a file points at a server your proxy no longer knows, HPS warns you about it in the console at startup.

{% hint style="info" %}
Want HPS to regenerate everything from scratch? Stop the proxy, delete the entire `servers/` folder, and start it again. Fresh files will be created for every server currently on your proxy. Just know this wipes any custom tweaks you made, including the files you disabled for servers without HPS, so remember to disable those again.
{% endhint %}

### Handy Commands

A few commands you'll actually use day to day (all run from the proxy, and all need the `/spoof` permission):

| Command                      | What it does                                                                         |
| ---------------------------- | ------------------------------------------------------------------------------------ |
| `/spoof reload`              | Reloads the proxy's config and server files. Run this after editing anything.        |
| `/spoof reload backend`      | Tells all your backend servers to reload their configs too, without restarting them. |
| `/spoof fluctuation status`  | Shows what fluctuation is currently doing on each server.                            |
| `/spoof fluctuation suspend` | Pauses fluctuation without touching your config. Great during maintenance.           |
| `/spoof fluctuation resume`  | Picks fluctuation back up after a suspend.                                           |
| `/spoof players list`        | Lists the fake players currently online.                                             |

***

## Routing Styles

There are two common ways to route your fake players around the network. Pick whichever matches how real players move through yours.

### Style 1 — Lobby Funnel (Recommended)

Fake players join a lobby first, then spread out to your gamemodes, exactly like real players do. This is the most natural-looking option.

* **Lobby servers:** set `primary-server: true`, a short lifespan (e.g. `2s` to `45s`), and a high `move-chance` (e.g. `80`). Players pass through quickly instead of piling up in the lobby.
* **Gamemode servers:** set `primary-server: false` and a longer lifespan (e.g. `5m` to `60m`), so players settle in and actually "play."

### Style 2 — Direct Joining

Fake players join your gamemodes directly, skipping the lobby entirely.

* Set `primary-server: true` on each gamemode server you want fake players joining directly.
* Set `primary-server: false` on the lobby (or leave it `true` if you'd like some lobby traffic too).

{% hint style="danger" %}
At least **one** server with HPS installed must have `primary-server: true`. If none do, fake players have no way to enter your network and nothing will join at all.
{% endhint %}

***

## Troubleshooting

Something not working? Run through these. Nine times out of ten, it's one of the first two.

**HPS didn't download or start at all**

* Head back to the [First Install guide](https://wiki.halosdev.com/halos-player-spoofer/setup-guide/first-install) and double-check your HalosLoader config: `enabled: true` is set, and the license key has no typos or extra spaces.
* Make sure the server has internet access so the loader can verify your license.

**Fake players aren't showing up on my servers**

* Check that `server-name` on each backend server **exactly** matches the name in your proxy config.
* Make sure every server has a **unique** `server-name`, no duplicates.
* Confirm the proxy and all servers point to the **same** database, and that `type` isn't `NONE`.
* Confirm `account-fetcher` is the same value on the proxy and all servers.
* Check the proxy console at startup. The per-server summary tells you exactly which servers fluctuation is running on.

**Fake players show up everywhere except one server**

* First things first: is HPS actually installed on that server? Fake players only appear on servers running the plugin.
* If it's a server you added after the initial setup, it has no file in the `servers/` folder yet. See Adding, Renaming, or Removing Servers Later.
* Check that server's file for `active: false` or a `min-real-players` value higher than your current real player count.
* If that server has its whitelist turned on, either add the accounts to the whitelist or set `respect-whitelist: false` in that server's HPS config. By default HPS respects your whitelist and won't force players past it.

**I'm getting errors about a server I didn't install HPS on**

* Open that server's file in the `servers/` folder on the proxy and set `active: false` and `primary-server: false`.
* Remove it from `fallback-server-ordering` in the proxy's `config.yml` too, then run `/spoof reload`.

**Fake players join but never move between servers**

* Check that `move-chance` is above `0` in the server files on the proxy.
* Make sure at least one server has `primary-server: true`.

**My player counts feel too low**

* Check `max-total-fake-players` in each server file. It's a hard cap that overrides everything else.
* Remember the account blacklist: accounts that real players have used are never reused, so a small `LOCAL` account list can shrink over time. Add more accounts or use `NAMEMC`.

**My weekend boost fires at the wrong time**

* Overrides use the clock and timezone of the machine the proxy runs on. Adjust `start-hour` and `end-hour` to match, or change your host machine's timezone.

**Still stuck?**

No worries. Join our Discord at [discord.gg/halosdev](https://discord.gg/halosdev) and open a support ticket. We're happy to help you get it running!


# Offline Fake Player Accounts

This page covers the `account-fetcher` setting: what it does, and how to switch it to OFFLINE so your fake players are generated locally instead of being looked up through the MineTools or PlayerDB online services. Use OFFLINE when you want fake players with completely custom usernames, zero external API calls, and no dependency on an internet lookup service.

{% hint style="info" %}
**What "offline fakes" actually means:** By default, HalosPlayerSpoofer fetches each fake player's account data (UUID + skin texture) from an online service. In OFFLINE mode it skips that entirely. It builds the account itself from the username, exactly the way a Minecraft server generates UUIDs for offline-mode players, so no HTTP request ever leaves your server for account data.
{% endhint %}

### How account fetching works

The `account-fetcher` setting in `plugins/HalosPlayerSpoofer/config.yml` decides where the plugin gets each fake player's account and texture data:

| Value        | What it does                                    | Skins?              | Custom usernames?             | External calls? |
| ------------ | ----------------------------------------------- | ------------------- | ----------------------------- | --------------- |
| `MINE_TOOLS` | Looks up account + skin from MineTools          | ✅ Real skins        | ❌ Name must be a real account | ✅ Yes           |
| `PLAYER_DB`  | Looks up account + skin from PlayerDB           | ✅ Real skins        | ❌ Name must be a real account | ✅ Yes           |
| `OFFLINE`    | Generates the account locally from the username | ❌ Default skin only | ✅ Any username you want       | ❌ None          |

With `MINE_TOOLS` or `PLAYER_DB`, the fake player's username has to match a **real, registered Minecraft account**, because that's what the service resolves. If it can't find the name, the fake fails to fetch.

`OFFLINE` removes that restriction. The UUID is derived directly from the username (`OfflinePlayer:<name>`, the same scheme Minecraft itself uses in offline mode), so you can use **any username you invent** and there are **no rate limits, timeouts, or network dependencies**.

{% hint style="warning" %}
**The trade-off:** Offline fakes have **no custom skin.** Because textures only come from the online fetchers, an OFFLINE fake renders with the default Steve/Alex skin. If real skins matter more to you than custom names, use `MINE_TOOLS` or `PLAYER_DB` instead. Those work on an offline-mode server too, you'll just be limited to usernames of real accounts.
{% endhint %}

{% hint style="danger" %}
**OFFLINE only works on an offline-mode server.** If the server is in online mode the plugin will reject the OFFLINE fetcher, since a locally-generated account can't authenticate against Mojang.
{% endhint %}

### Switching to OFFLINE mode

#### Step 1: Set the account fetcher to OFFLINE

In `plugins/HalosPlayerSpoofer/config.yml`, find the `account-fetcher` setting and change it to `OFFLINE`:

```yaml
# Where to fetch player account and texture data.
# Possible values: MINE_TOOLS, PLAYER_DB, OFFLINE.
# When your server is running in online mode, this may not be OFFLINE.
account-fetcher: OFFLINE

# The HTTP timeout the account fetcher uses.
# Not used in OFFLINE mode since no requests are made.
account-fetcher-timeout: 2s
```

#### Step 2: Restart

Restart the server, to apply the change.

#### Step 3: Verify

Confirm it's working using the Verifying it works section below.

### Verifying it works

1. Turn on debug logging temporarily. Set `debug: true` in `config.yml` and reload.
2. Add a fake player the way you normally would (via the Fluctuation module or your spoof command).
3. Add a fake with a **made-up username** that isn't a real account. In `MINE_TOOLS`/`PLAYER_DB` this would fail to fetch. In `OFFLINE` it should appear immediately, which confirms the online lookup is being skipped.
4. The fake will show the **default skin** (Steve/Alex). That's expected in OFFLINE mode and further confirms no texture was fetched.
5. Check the console. You should see **no** MineTools or PlayerDB request/timeout log lines for account fetching.

Turn `debug` back to `false` when you're done.

### Troubleshooting

**"OFFLINE fetcher is not allowed in online mode" (or fakes never spawn after switching to OFFLINE)** The plugin only allows OFFLINE on an offline-mode server. Make sure the server is in offline mode, then fully restart.

**Fake players have no skin** Expected behavior in `OFFLINE` mode. Offline accounts carry no texture. If you need skins, switch to `MINE_TOOLS` or `PLAYER_DB` (those work on an offline-mode server too, but your fake usernames must be real registered accounts).

### FAQ

**Q: Why would I use OFFLINE instead of MINE\_TOOLS/PLAYER\_DB?**\
A: Two reasons. You want fakes with **any custom username** (not limited to real accounts), and you want **zero external API calls** (no rate limits, no lookup timeouts, works with no internet).

**Q: Can I have offline fakes and real skins?**\
A: No. Skins come from the online fetchers. It's one or the other: custom names with no skins (`OFFLINE`), or real skins with real-account names (`MINE_TOOLS`/`PLAYER_DB`).

### Support

Stuck? Open a ticket in our Discord: [discord.gg/halosdev](https://discord.gg/halosdev)


# AI Chat Setup Guide

The AI Chat module brings your fake players to life — they hold real conversations with players and with each other, powered by an LLM you provide. This guide walks through setup from zero.

{% hint style="warning" %}
**Read this first:** The AI Chat module is currently in BETA. You are responsible for any LLM provider bills you incur. Set a spending limit on your provider account or API key before enabling. We cannot guarantee any specific bill amount — that depends entirely on your settings and server activity.
{% endhint %}

### Quick Start

If you've used an LLM API before, the short version:

1. Pick a provider: OpenAI, Gemini, Anthropic, or local Ollama
2. Generate an API key
3. Edit `plugins/HalosPlayerSpoofer/ai-chat.yml`:
   * Set `enabled: true`
   * Set `provider-type` to your provider
   * Paste your API key into `api-key`
   * Set `model-name` to your provider's exact API identifier (see Step 3)
4. `/halos reload` or restart the server
5. Watch fake players start chatting in-game within a few minutes

If anything fails, see Troubleshooting.

### Step 1: Choose a Provider

| Provider           | Cost                | Quality   | Setup difficulty |
| ------------------ | ------------------- | --------- | ---------------- |
| **Google Gemini**  | Free tier available | Excellent | Easy             |
| **OpenAI**         | Pay-per-use         | Excellent | Easy             |
| **Anthropic**      | Pay-per-use         | Excellent | Easy             |
| **Ollama** (local) | Free                | Variable  | Advanced         |

For most server owners, **Gemini is the cheapest entry point** because of its generous free tier. **OpenAI** is the most reliable and widely tested. **Ollama** is the only option if you want zero LLM costs, but requires you to run a local model on your own hardware.

### Step 2: Get Your API Key

Generate a key from your chosen provider:

* **Gemini:** <https://aistudio.google.com/apikey>
* **OpenAI:** <https://platform.openai.com/api-keys>
* **Anthropic:** <https://console.anthropic.com/settings/keys>
* **Ollama:** No key needed — set `base-url` to your local Ollama endpoint instead

{% hint style="danger" %}
**Set a spending limit immediately after generating your key.** All three commercial providers let you cap monthly spend on the account or key. Do this BEFORE pasting the key into the config. If you don't, a misconfiguration could cost you hundreds of dollars before you notice.
{% endhint %}

### Step 3: Configure the Module

Open `plugins/HalosPlayerSpoofer/ai-chat.yml` and edit the top section:

```yaml
enabled: true

llm-provider:
  provider-type: GEMINI       # OPEN_AI | OLLAMA | ANTHROPIC | GEMINI
  api-key: YOUR_KEY_HERE
  model-name: gemini-2.5-flash
  base-url: none              # only used for OLLAMA
```

#### Valid model names per provider

{% hint style="danger" %}
**Model names must be your provider's exact API identifier.** Using marketing names like "GPT-4" or "Gemini 2.5 Flash" (with spaces and capitals) will fail with an `Illegal character in path` error. Use the exact lowercase, hyphen-separated identifier below.
{% endhint %}

**OpenAI:**

* `gpt-4.1-mini` — recommended for cost/quality balance
* `gpt-4.1`
* `gpt-4o`
* `gpt-4o-mini`

**Gemini:**

* `gemini-2.5-flash` — recommended (free tier eligible)
* `gemini-2.0-flash`
* `gemini-1.5-flash`
* `gemini-1.5-pro`

**Anthropic:**

* `claude-haiku-4-5-20251001`
* `claude-sonnet-4-6`
* `claude-opus-4-6`

**Ollama:**

* Whatever model name you have installed locally (e.g. `llama3.2`, `mistral`)
* Set `base-url` to your Ollama endpoint, e.g. `http://localhost:11434`

### Step 4: Set the Activity Level

These three settings control how often AI chat fires — which directly controls your LLM costs:

```yaml
chatter-chance: 0.75      # 75% of new fake players become chatters
max-chatters: 10          # cap on simultaneous chatting fakes
min-real-players: 1       # don't run AI chat until N real players are online
```

| Setting                   | Effect on cost             | Recommended starting value      |
| ------------------------- | -------------------------- | ------------------------------- |
| Higher `chatter-chance`   | More fakes will chat       | Start at `0.5`                  |
| Higher `max-chatters`     | More LLM calls in parallel | Start at `5`, raise once stable |
| Higher `min-real-players` | Less time chat is active   | `1-3` for cost control          |

{% hint style="info" %}
**Cost-control tip:** Set `min-real-players: 3` or higher to prevent AI chat from running on an empty server. There's no point spending API tokens when no real players are watching.
{% endhint %}

### Step 5: Test It

1. Save the config
2. In-game, run `/halos reload` or restart the server
3. Have at least `min-real-players` real players online
4. Wait for fake players to spawn (depends on your Fluctuation settings)
5. Watch chat — you should see fakes talking within a minute or two

If nothing shows up after \~3 minutes, jump to Troubleshooting.

### Personalities

Halos ships with 6 built-in personalities. Each fake player that becomes a chatter is randomly assigned one:

| Personality | Vibe                                                 |
| ----------- | ---------------------------------------------------- |
| **Eli**     | Dry humor, slightly grumpy, gives practical tips     |
| **Casey**   | Friendly, witty, asks occasional follow-up questions |
| **Nia**     | Warm, mentions her pets casually                     |
| **Milo**    | Lowercase only, chill, sleepy vibe                   |
| **Bex**     | Enthusiastic builder, lots of punctuation!!!         |
| **Toby**    | Friendly, occasional small typos                     |

You can edit any of them — or add new ones — under `prompt-config.personalities` in `ai-chat.yml`. Each personality has two prompts:

* `decision-prompt-template` — used when the agent is deciding whether to respond to a player's message
* `idle-prompt-template` — used when the agent decides to start a random chat during a quiet period

#### Adding a Custom Personality

Copy an existing personality block and rename the key. Example — adding a new personality called `kira`:

```yaml
prompt-config:
  personalities:
    kira:
      decision-prompt-template: |
        You are chatting in a public Minecraft server as '{agentName}'.
        Persona: Kira — competitive PvPer, brags about kills, friendly rivalry.
        Style rules:
        - short, confident replies.
        - occasional trash-talk in a friendly way.
        Behavior rules:
        - Avoid echo-chamber behavior: do NOT mindlessly agree, mirror, or repeat.
        - If message mentions your name, respond with high likelihood.
        - Output MUST be strict JSON only:
          {"respond":true|false,"recipient":"<optional username>","message":"<chat message if respond>"}
        - If respond=false, message must be "".
        - recipient should usually be the sender '{fromUser}' if replying.
      idle-prompt-template: |
        You are chatting in a public Minecraft server as '{agentName}'.
        Persona: Kira — competitive PvPer.
        We are in a lull. Write one short PvP-related chat line.
        Output MUST be strict JSON only:
          {"respond":true,"recipient":"","message":"..."}
```

{% hint style="info" %}
**Prompt rules:** Both templates MUST end with the JSON output instruction. Halos parses the agent's response as strict JSON — free-form text will fail.
{% endhint %}

### Cost Management

This is the most-asked-about topic. Here's what actually drives your bill:

**Bigger cost drivers:**

* High `chatter-chance` (more fakes are chatting)
* High `max-chatters` (more parallel API calls)
* High `max-context-events` (more tokens per call)
* Low `min-real-players` (chat running with no audience)
* Low `quiet-period-for-idle-ms` (more idle chats happening)

**Smaller cost drivers (worth tuning second):**

* Cheaper model (`gpt-4o-mini`, `gemini-2.5-flash`, `claude-haiku-4-5`)
* Higher cooldown settings (less frequent messages)
* Lower `p-idle-chatter` (less random chatting when no players)

#### Sample monthly cost estimates

| Profile         | Setup                                 | Estimated monthly cost |
| --------------- | ------------------------------------- | ---------------------- |
| Hobby server    | 5 fakes, `gpt-4o-mini`, 8h/day active | \~$3-8/mo              |
| Mid-size server | 10 fakes, `gpt-4o-mini`, 24/7         | \~$15-30/mo            |
| Large network   | 25+ fakes, `gpt-4.1`, 24/7            | $50+/mo                |

These are rough estimates. **Always set a hard spending limit with your provider before enabling.**

### Advanced Tuning

The `agent-config` section is for fine-tuning behavior. Defaults work for most servers. Touch these only if you have a specific reason.

#### Cooldowns & Timing

| Setting                     | What it does                                                        | Default          |
| --------------------------- | ------------------------------------------------------------------- | ---------------- |
| `engagement-ttl-ms`         | How long an agent stays engaged with a player after being mentioned | `180000` (3 min) |
| `global-speak-cooldown-ms`  | Cooldown between chats by any agent                                 | `3000`           |
| `per-recipient-cooldown-ms` | Per-agent and per-engagement cooldown between chats                 | `3000`           |
| `quiet-period-for-idle-ms`  | Quiet time before an agent might initiate idle chat                 | `18000` (18s)    |
| `min-idle-speak-gap-ms`     | Minimum delay between random idle messages                          | `35000` (35s)    |
| `min-response-delay-ms`     | Minimum delay before agent responds to a player                     | `5000` (5s)      |
| `max-response-delay-ms`     | Maximum delay before agent responds                                 | `10000` (10s)    |
| `tick-interval-ms`          | How often agents are evaluated                                      | `5000`           |
| `echo-suppress-window-ms`   | Window to ignore duplicate chat messages                            | `25000`          |

#### Response Probabilities

| Setting                    | What it does                                             | Default |
| -------------------------- | -------------------------------------------------------- | ------- |
| `p-reply-when-mentioned`   | Probability of replying when mentioned by name           | `0.85`  |
| `p-reply-when-engaged`     | Probability of replying when in active engagement        | `0.65`  |
| `p-reply-random-when-idle` | Probability of replying to a random chat when idle       | `0.08`  |
| `p-idle-chatter`           | Probability an idle agent submits a random chat per tick | `0.05`  |

#### Multi-Agent Coordination

| Setting                         | What it does                                                      | Default |
| ------------------------------- | ----------------------------------------------------------------- | ------- |
| `p-ignore-other-agents-base`    | Probability of ignoring another agent's chat when idle            | `0.75`  |
| `p-ignore-other-agents-if-busy` | Probability of ignoring other agents when engaged with a player   | `0.85`  |
| `p-bypass-ignore-if-mentioned`  | Multiplier reducing ignore probability if mentioned               | `0.35`  |
| `global-throttle-multiplier`    | Multiplier reducing reply probability when many agents are active | `0.35`  |

#### Focus System

| Setting                           | What it does                                                           | Default       |
| --------------------------------- | ---------------------------------------------------------------------- | ------------- |
| `focus-ttl-ms`                    | How long an agent focuses on a specific player                         | `90000` (90s) |
| `focus-boost-multiplier`          | Reply probability multiplier when sender is the focused player         | `5.0`         |
| `focus-suppress-multiplier`       | Reply probability multiplier from others when agent has a focused user | `0.4`         |
| `focus-mention-bypass-multiplier` | Reduction in focus suppression if agent is mentioned                   | `0.65`        |

#### Context

| Setting              | What it does                                          | Default |
| -------------------- | ----------------------------------------------------- | ------- |
| `max-history`        | Max history events kept internally per agent          | `200`   |
| `max-context-events` | Max events sent to the LLM per call (lower = cheaper) | `60`    |
| `debug-logging`      | Enable verbose decision logging — useful for tuning   | `false` |

### Troubleshooting

#### "Illegal character in path at index N"

The `model-name` in your config has invalid characters (usually spaces). Provider model names must be exact API identifiers, not marketing names.

* ❌ Wrong: `model-name: Gemini 2.5 Flash`
* ✅ Right: `model-name: gemini-2.5-flash`

See Step 3 for the full list of valid names per provider.

#### Chat isn't appearing in-game

Check in this order:

1. Is `enabled: true` in `ai-chat.yml`?
2. Are there at least `min-real-players` real players online?
3. Are fake players actually spawning? (Check the Fluctuation module is working first)
4. Is your API key valid? Test it directly with your provider.
5. Check `latest.log` for errors — search for `LLMAgent` or `RetryUtils`.
6. Set `debug-logging: true` under `agent-config` to see what each agent is deciding.

#### "401 Unauthorized" or "Invalid API key"

Your API key is wrong, expired, or revoked. Generate a new one with your provider and paste it into `api-key`.

#### "429 Too Many Requests" / Rate limit errors

You're hitting your provider's rate limits. Either:

* Lower `max-chatters` so fewer parallel calls happen
* Raise the cooldown settings (`global-speak-cooldown-ms`, `min-idle-speak-gap-ms`)
* Upgrade your provider plan to a higher rate limit tier

#### Bill is higher than expected

1. Lower `max-chatters`
2. Lower `chatter-chance`
3. Raise `min-real-players`
4. Switch to a cheaper model (`gpt-4o-mini` or `gemini-2.5-flash`)
5. Set a hard spending limit on your provider account
6. Reduce `max-context-events` to send less context per call

#### Fakes are repeating themselves or echoing real players

* Raise `echo-suppress-window-ms` (default 25000)
* Review your personality prompts — make sure "Avoid echo-chamber behavior" rule is present
* Lower `p-reply-when-engaged` so agents don't pile onto every message

#### Fakes are too chatty / spamming

* Lower `chatter-chance`
* Lower `p-idle-chatter` and `p-reply-random-when-idle`
* Raise `quiet-period-for-idle-ms` and `min-idle-speak-gap-ms`

#### Fakes are too quiet / never chat

* Raise `chatter-chance`
* Raise `p-reply-when-mentioned` and `p-reply-when-engaged`
* Lower `quiet-period-for-idle-ms`
* Confirm fakes are actually being spawned by Fluctuation

### FAQ

**Q: Will real players know they're talking to AI?**\
A: They shouldn't if your prompts are configured well. The default prompts explicitly tell agents not to behave like assistants. Test with friends before going live.

**Q: Can I use multiple LLM providers at once?**\
A: Not currently — one provider per server.

**Q: What about NSFW or rule-breaking content?**\
A: Your LLM provider handles content filtering. OpenAI and Anthropic block most disallowed content by default. Gemini is configurable. Your prompts also influence what gets generated.

**Q: My fakes are speaking in non-English languages.**\
A: Add an explicit language rule to the `global-system-prompt`, e.g. "Always respond in English."

**Q: Can fakes mention other fakes by name?**\
A: Yes — they're aware of other agents and can reference them. Tune `p-ignore-other-agents-base` to control how often this happens.

**Q: Does AI chat work on Folia?**\
A: Yes. All AI chat operations are async and Folia-compatible.

### Support

Stuck? Open a ticket in our Discord: [discord.gg/halosdev](https://discord.gg/halosdev)

Average response time: same day to 24 hours. We read every ticket.


# Commands/Permissions

Here all the commands + Permissions will be listed.

<table><thead><tr><th width="342.3333333333333">Command</th><th width="198">Permission</th><th>Description</th></tr></thead><tbody><tr><td>/spoof help</td><td>spoof.admin</td><td>View this help page</td></tr><tr><td>/spoof reload</td><td>spoof.admin</td><td>Reload plugin on the proxy.</td></tr><tr><td>/spoof reload backend</td><td>spoof.admin</td><td>Reload the backend config.</td></tr><tr><td>/spoof status</td><td>spoof.admin</td><td>View current status.</td></tr><tr><td>/spoof chat &#x3C;player> &#x3C;message></td><td>spoof.admin</td><td>Send a message from a fake player.</td></tr><tr><td>/spoof players add &#x3C;name> &#x3C;server|random></td><td>spoof.admin</td><td>Manually add a fake player to a server.</td></tr><tr><td>/spoof players move &#x3C;name>&#x3C;server|random></td><td>spoof.admin</td><td>Move a fake player to another server or random server.</td></tr><tr><td>/spoof players clear &#x3C;server|all></td><td>spoof.admin</td><td>Remove all fake players from a server or all servers.</td></tr><tr><td>/spoof fluctuation status</td><td>spoof.admin</td><td>View current fluctuation status.</td></tr><tr><td>/spoof fluctuation manual &#x3C;server></td><td>spoof.admin</td><td>View the manual fluctuation modifier for a server.</td></tr><tr><td>/spoof fluctuation manual &#x3C;server> add &#x3C;amount></td><td>spoof.admin</td><td>Increase the manual fluctuation value.</td></tr><tr><td>/spoof fluctuation manual &#x3C;server> remove &#x3C;amount></td><td>spoof.admin</td><td>Decrease the manual fluctuation value.</td></tr><tr><td>/spoof fluctuation manual &#x3C;server> rest</td><td>spoof.admin</td><td>Reset the fluctuation</td></tr><tr><td>/spoof fluctuation suspend &#x3C;server|all>></td><td>spoof.admin</td><td>Temporarily disable fluctuation.</td></tr><tr><td>/spoof flucutuation resume &#x3C;server|all></td><td>spoof.admin</td><td>Resume fluctuation after suspension.</td></tr></tbody></table>


# API

This page helps you to understand how to use our SpooferAPI on different platforms. We offer a abstract layer for Spigot, BungeeCord & Velocity.

### Usage

To get started using our API, import our library via either Gradle or Maven. Found at [https://maven.halosdev.com](https://maven.halosdev.com/#/) .<br>

### Implement Repository

Add the Maven repository to your Gradle or Maven project. Check out [ https://maven.halosdev.com/#/releases](< https://maven.halosdev.com/#/releases>)

#### Maven

```xml
<repository>
  <id>halosdev-releases</id>
  <name>HalosDev Repository</name>
  <url>https://maven.halosdev.com/releases</url>
</repository>
```

#### Gradle Groovy

```groovy
maven {
    name "halosdevReleases"
    url "https://maven.halosdev.com/releases"

```

#### Gradle Kotlin

```kotlin
maven {
    name = "halosdevReleases"
    url = uri("https://maven.halosdev.com/releases")
}
```

### Declare Dependencies

Declare the dependencies, Check out <https://maven.halosdev.com/#/releases/com/halosdev/spoofer/api-spigot>

#### Maven

```xml
<dependency>
  <groupId>com.halosdev.spoofer</groupId>
  <artifactId>api-spigot</artifactId>
  <version>3.2.5</version>
</dependency>
```

#### Gradle Groovy

```groovy
implementation "com.halosdev.spoofer:api-spigot:3.2.5"
```

#### Gradle Kotlin

```kotlin
implementation("com.halosdev.spoofer:api-spigot:3.2.5")
```

### FakePlayer Global Event

You can get the instance of a fake player from all platform.

<pre class="language-java"><code class="lang-java">import com.halos.spoofer.shared.core.player.FakePlayer;
import com.halos.spoofer.shared.core.account.type.UsernameAccount;
import com.halos.spoofer.shared.core.account.type.UUIDAccount;
import java.util.UUID;
import com.halos.spoofer.shared.core.profile.Profile;

var player = (FakePlayer) null;

player.account(); // can be null; either a UsernameAccount or UUIDAccount
<strong>String name = player.profile().name(); // name of fake player
</strong>UUID uniqueId = player.profile().uniqueId(); // uuid of fake player
Profile.Texture = texture = player.profile().texture(); // skin texture of fake player
long joinedAt = player.joinedAt(); // join time stamp
long expiresAt = player.expiresAt(); // when the player will leave the server
boolean chatter = player.chatter(); // if the player can chat
FakePlayerMetadata metadata = player.metadata(); // metadata for fake player

// use metadata to store different values
// caution: metadata will be saved. For example the Vote &#x26; Rank Module utilizes this.
// so clear keys on the destroy event to prevent many files

boolean createdByFluctuation = metadata.has(MetadataKey.FLUCTUATION); // check if player is created by fluctuation

metadata.put("rank", "default"); // store a property
metadata.getOrDefault("rank", "default") // retrieve a property
</code></pre>

### Spigot Events

We provide several events, that are called when a fake player is created on the current server

```java
import com.halos.spoofer.api.spigot.event.FakePlayerCreatedEvent;
import com.halos.spoofer.api.spigot.event.FakePlayerDestroyEvent;
import com.halos.spoofer.api.spigot.event.FakePlayerLoginEvent;

// This event is called after a fake player is created & joined the server.
var playerCreatedEvent = (FakePlayerCreatedEvent) null;
// This event is called before a fake player gets destroyed.
var playerDestroyEvent = (FakePlayerDestroyEvent) null;
// This event is called before a fake player logs in.
var playerLoginEvent = (FakePlayerLoginEvent) null;

playerCreatedEvent.fakePlayer(); // get the fake player
playerCreatedEvent.player(); // get the bukkit player

playerDestroyEvent.fakePlayer(); // get the fake player
playerDestroyEvent.player(); // get the bukkit player

playerLoginEvent.fakePlayer(); // get the fake player

```

```java
import com.halos.spoofer.api.spigot.SpigotSpooferAPI;
import com.halos.spoofer.shared.core.player.FakePlayer;

var api = SpigotSpooferAPI.get(); // access the spigot spoofer api

api.isFakePlayer("username");
api.isFakePlayer(UUID.randomUUID());
api.isFakePlayer(Bukkit.getPlayer("username"));

// careful: all find methods return optionals with FakePlayer
FakePlayer fakePlayer = api.find("username").orElseThrow();
FakePlayer fakePlayer = api.find(UUID.randomUUID().orElseThrow());
FakePlayer fakePlayer = api.find(Bukkit.getPlayer("username").orElseThrow());

CompletableFuture<FakePlayer> future = api.createFakePlayer(new Profile("username", UUID.randomUUID(), new Profile.Texture("value", "signature")));
CompletableFuture<FakePlayer> future = api.createFakePlayer(new UsernameAccount("rexlManu")); // this fetches the profile from a profile fetcher type
CompletableFuture<FakePlayer> future = api.createFakePlayer(new UUIDAccount(UUID.randomUUID())); // this fetches the profile from a profile fetcher type

// future will fail with a FakePlayerNotFoundException if player can't be found
CompletableFuture<Void> future = api.removeFakePlayer(UUID.randomUUID());
CompletableFuture<Void> future = api.removeFakePlayer("username");
CompletableFuture<Void> future = api.removeFakePlayer(Bukkit.getPlayer("username"));
CompletableFuture<Void> future = api.removeFakePlayer(fakePlayer);
```

### BungeeCord / Velocity Events

Both platforms provide a similar api like spigot with the exception of creating / removing fake players. When creating a fake player on spigot, the fake player will also be created on the proxy.

<pre class="language-java"><code class="lang-java"><strong>import com.halos.spoofer.api.bungee.event.FakePlayerCreatedEvent;
</strong>import com.halos.spoofer.api.bungee.event.FakePlayerDestroyEvent;
import com.halos.spoofer.api.velocity.event.FakePlayerCreatedEvent;
import com.halos.spoofer.api.velocity.event.FakePlayerDestroyEvent;

// all events give access to fakePlayer via FakePlayerEvent#fakePlayer() 
// and FakePlayerCreatedEvent#player() for velocity
// or proxied player instance on bungeecord

import com.halos.spoofer.api.velocity.VelocitySpooferAPI;
import com.halos.spoofer.api.bungee.BungeeSpooferAPI;

// both apis are called similar like the spigot variant exempt the creation / removing methods.
// the native platforms give access to get fake players by ProxiedPlayer or Player from velocity

</code></pre>


