# Welcome

Connect your Ragnarok Online server to Discord

**Your players are already on Discord.** Now your game can be too.

RagnaSync bridges your Ragnarok Online server with Discord, keeping your community connected whether they're grinding in-game or chatting on their phone. No more missed conversations, no more isolated players.

***

## Why Server Owners Choose RagnaSync

### Keep Players Engaged

When players log off, they don't have to miss out. In-game chat flows to Discord so everyone stays in the loop. Players who can't log in can still be part of the community, and that keeps them coming back.

### Build a Stronger Community

Discord users can see game activity in real time - MVP kills, rare drops, who's online. It creates buzz and excitement that makes your server feel alive, even when player count is low.

### Monitor Your Server Effortlessly

GM commands get logged to private channels. Player @requests appear instantly. You can keep an eye on your server from anywhere, without being logged in.

### Celebrate Your Players

When someone lands a rare card or kills an MVP, the whole community sees it. These moments become shared experiences that build loyalty and keep players talking about your server.

***

## What RagnaSync Does

<table data-view="cards"><thead><tr><th></th><th></th></tr></thead><tbody><tr><td><strong>Chat Bridging</strong></td><td>In-game channels appear in Discord. Players chat across both platforms seamlessly.</td></tr><tr><td><strong>Event Logging</strong></td><td>MVP kills, rare drops, and logins post to Discord automatically.</td></tr><tr><td><strong>Staff Tools</strong></td><td>@commands and @requests logged for easy monitoring and accountability.</td></tr><tr><td><strong>Custom Scripts</strong></td><td>Build your own Discord notifications with RagnaScript.</td></tr><tr><td><strong>Server Monitoring</strong></td><td>Track uptime, crashes, and console errors — and give players a public status page.</td></tr></tbody></table>

***

## How It Works

Your Ragnarok server connects to RagnaSync, which forwards messages to your Discord. Setup takes minutes, and our team handles the installation for you.

```
Your Server  ───────>  RagnaSync  ───────>  Discord
```

You configure which channels bridge where, what events get logged, and how messages appear. Everything runs automatically once it's set up.

***

## Ready to Get Started?

<table data-view="cards"><thead><tr><th></th><th></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><strong>See Pricing</strong></td><td>Free tier available. Pro plans start at €4.99/month.</td><td><a href="/account/pricing">Pricing</a></td></tr><tr><td><strong>Installation</strong></td><td>Get RagnaSync running on your server</td><td><a href="/getting-started/installation">Installation</a></td></tr><tr><td><strong>Channels Config</strong></td><td>Bridge your in-game channels to Discord</td><td><a href="/getting-started/channels-conf">Channels Config</a></td></tr><tr><td><strong>Build with AI</strong></td><td>Have Claude or ChatGPT write your RagnaScripts</td><td><a href="/ragnascript/build-ragnascripts-with-ai">Build with AI</a></td></tr><tr><td><strong>Troubleshooting</strong></td><td>Common issues and how to fix them</td><td><a href="/getting-started/troubleshooting">Troubleshooting</a></td></tr></tbody></table>

***

## Questions?

Join our Discord to see RagnaSync in action and chat with other server owners:

[Join RagnaSync Discord](https://discord.gg/FQjjeaMZK2)


# Installation

Get RagnaSync installed on your server

RagnaSync installation is handled by our team - you just need to provide your server files and configure your Discord settings.

***

## Installation Service

We handle the technical work, you complete the setup!

### How It Works

1. **Open a support ticket** in our Discord contact channel
2. **Upload your Ragnarok server files** (we'll tell you which ones)
3. **Our team installs RagnaSync** on your server
4. **You receive back:**
   * Fully modified server files
   * Configuration instructions
   * Ready-to-customize setup
5. **Update the configuration** with your Discord settings
6. **Recompile and launch** - you're done!

{% hint style="info" %}
Installation is free for all plans and handled by our support team. After we complete our part, you'll configure your Discord server settings.
{% endhint %}

[Start Installation Request](https://discord.gg/FQjjeaMZK2)

***

## Add the RagnaSync Discord Bot

For your server to communicate with Discord, you need to add our bot to your Discord server.

[Install RagnaSync Bot](https://discord.com/oauth2/authorize?client_id=1220865145513836721)

The bot will request the permissions it needs - just approve them and you're set.

***

## Configure channels.conf

After installation, you'll configure your `channels.conf` file to connect your server to Discord.

### Step 1: Get Your Discord Server ID

1. Right-click on any channel in your Discord server
2. Click **"Copy Link"**
3. Look at the URL - the **first number** is your Server ID

**Example:**

```
https://discord.com/channels/1215013159291846656/1359301736656932976
                              ↑ Server ID          ↑ Channel ID
```

### Step 2: Update Global Settings

Open your `channels.conf` and find the `discord` section. Update it with your Server ID:

```diff
discord: {
    ragnasync_host_name: "ragnasync.com"
-   ragnasync_discord_id: ""
+   ragnasync_discord_id: "1215013159291846656"
    ragnasync_discord_guid: ""
    ragnasync_client_guid: ""
}
```

### Step 3: Get Your Discord Channel IDs

For each in-game channel you want to bridge to Discord, you need the Discord channel ID.

Using the same "Copy Link" method, the **second number** is the Channel ID.

### Step 4: Configure Each Channel

Find the channel you want to bridge in the `channels` section and add the `discord_channel_id`:

```diff
{
    name: "#global"
    alias: "[Global]"
    color: "White"
    type: "CHAN_TYPE_PUBLIC"
    delay: 1000
    autojoin: true
    leave: false
    chat: true
-   // discord_channel_id: "1234567890"
+   discord_channel_id: "1359301736656932976"
},
```

{% hint style="warning" %}
**Don't forget to uncomment!** Remove the `//` prefix to enable the setting.
{% endhint %}

***

## Verify Your Installation

After configuring, recompile your server and start it. You can check the connection status in-game:

```
@syncstatus
```

If connected, you should see a success message. Try sending a message in your bridged channel - it should appear in Discord!

***

## Need Help?

Having trouble with installation or configuration? Our support team is happy to help.

[Contact Support](https://discord.gg/FQjjeaMZK2)


# Channels Config

The `channels.conf` file configures the in-game channel system and RagnaSync integration settings. This file is located in your rAthena server's `conf/channels.conf`.

## RagnaSync Discord Integration

The `discord` block contains all RagnaSync-specific settings for connecting your Ragnarok server to Discord.

### Connection Settings

Core settings for connecting to the RagnaSync service.

```conf
discord: {
    // RagnaSync Service Host
    ragnasync_host_name: "ragnasync.com"

    // Discord Server Settings
    ragnasync_discord_id: "123456789012345678"
    ragnasync_discord_guid: "a1b2c3d4e5f6789012345678abcdef00"

    // Ragnarok Server Client Settings
    ragnasync_client_guid: "f0e1d2c3b4a5678901234567fedcba99"

    // Connection Settings
    ragnasync_enabled: true
    ragnasync_retry_first: 300
    ragnasync_retry_delay: 1800
    ragnasync_retry_max: 3
}
```

| Setting                  | Type   | Default           | Description                                                                         |
| ------------------------ | ------ | ----------------- | ----------------------------------------------------------------------------------- |
| `ragnasync_host_name`    | string | `"ragnasync.com"` | RagnaSync service hostname                                                          |
| `ragnasync_discord_id`   | string | `""`              | Discord server ID (17-19 digit snowflake)                                           |
| `ragnasync_discord_guid` | string | `""`              | Discord server security key (32 hex chars, issued with your `ragnasync_discord_id`) |
| `ragnasync_client_guid`  | string | `""`              | Ragnarok client security key (32 hex chars, from dashboard)                         |
| `ragnasync_enabled`      | bool   | `true`            | Auto-connect when server starts                                                     |
| `ragnasync_retry_first`  | int    | `300`             | First retry delay in seconds (5 minutes)                                            |
| `ragnasync_retry_delay`  | int    | `1800`            | Delay between subsequent retries (30 minutes)                                       |
| `ragnasync_retry_max`    | int    | `3`               | Maximum retry attempts (0 = unlimited)                                              |

{% hint style="warning" %}
**The retry defaults decide how long your relay stays down.**

If the connection drops — a network blip, or RagnaSync restarting for an update — your server waits `ragnasync_retry_first` seconds before its **first** reconnect attempt. At the default of `300` that is a full five minutes with no chat relay in either direction, even though the service is back within seconds.

If that first attempt misses, the next one is `ragnasync_retry_delay` away — another **30 minutes** by default.

Lowering the first retry costs nothing and closes most of that gap:

```
ragnasync_retry_first: 20      // try again after 20s instead of 5 minutes
ragnasync_retry_delay: 120     // and every 2 minutes after that
ragnasync_retry_max: 0         // keep trying indefinitely
```

Both values are read from this file when your server starts, so a restart of your Ragnarok server is all that is needed — no recompile. Avoid going below \~10 seconds: very aggressive retries add load without reconnecting meaningfully faster.
{% endhint %}

***

### @Command Logging

Log GM commands to a Discord channel for moderation and audit purposes.

```conf
ragnasync_at_command_log_enabled: true
ragnasync_at_command_log_channel_id: "123456789012345678"
ragnasync_at_command_log_group_min: 1
ragnasync_at_command_log_group_max: 99
```

| Setting                               | Type   | Default | Description                         |
| ------------------------------------- | ------ | ------- | ----------------------------------- |
| `ragnasync_at_command_log_enabled`    | bool   | `false` | Enable @command logging             |
| `ragnasync_at_command_log_channel_id` | string | `""`    | Discord channel ID for command logs |
| `ragnasync_at_command_log_group_min`  | int    | `1`     | Minimum GM group level to log       |
| `ragnasync_at_command_log_group_max`  | int    | `99`    | Maximum GM group level to log       |

**Tip:** Set `group_min: 99` to only log admin commands, or `group_min: 1` to log all staff.

***

### Mob Item Drop Logging

Log rare item drops from monsters to Discord.

```conf
ragnasync_mob_item_drop_log_enabled: true
ragnasync_mob_item_drop_log_channel_id: "123456789012345678"
ragnasync_mob_item_drop_log_min_rate: 100
ragnasync_mob_item_drop_log_types: "card,weapon,armor"
```

| Setting                                  | Type   | Default | Description                                                      |
| ---------------------------------------- | ------ | ------- | ---------------------------------------------------------------- |
| `ragnasync_mob_item_drop_log_enabled`    | bool   | `false` | Enable drop logging                                              |
| `ragnasync_mob_item_drop_log_channel_id` | string | `""`    | Discord channel for drop logs                                    |
| `ragnasync_mob_item_drop_log_min_rate`   | int    | `500`   | Minimum rate to log (see drop rates below)                       |
| `ragnasync_mob_item_drop_log_types`      | string | `"all"` | Item categories to log (see [Item Drop Types](#item-drop-types)) |

**Drop Rate Values:**

| Value   | Rate |
| ------- | ---- |
| `10000` | 100% |
| `1000`  | 10%  |
| `500`   | 5%   |
| `100`   | 1%   |
| `10`    | 0.1% |

***

### Homunculus Item Drop Logging

Log item drops from Homunculus kills.

```conf
ragnasync_hom_item_drop_log_enabled: true
ragnasync_hom_item_drop_log_channel_id: "123456789012345678"
ragnasync_hom_item_drop_log_min_rate: 500
ragnasync_hom_item_drop_log_types: "all"
```

| Setting                                  | Type   | Default | Description                                                      |
| ---------------------------------------- | ------ | ------- | ---------------------------------------------------------------- |
| `ragnasync_hom_item_drop_log_enabled`    | bool   | `false` | Enable drop logging                                              |
| `ragnasync_hom_item_drop_log_channel_id` | string | `""`    | Discord channel for drop logs                                    |
| `ragnasync_hom_item_drop_log_min_rate`   | int    | `500`   | Minimum rate to log                                              |
| `ragnasync_hom_item_drop_log_types`      | string | `"all"` | Item categories to log (see [Item Drop Types](#item-drop-types)) |

***

### Mercenary Item Drop Logging

Log item drops from Mercenary kills.

```conf
ragnasync_merc_item_drop_log_enabled: true
ragnasync_merc_item_drop_log_channel_id: "123456789012345678"
ragnasync_merc_item_drop_log_min_rate: 500
ragnasync_merc_item_drop_log_types: "all"
```

| Setting                                   | Type   | Default | Description                                                      |
| ----------------------------------------- | ------ | ------- | ---------------------------------------------------------------- |
| `ragnasync_merc_item_drop_log_enabled`    | bool   | `false` | Enable drop logging                                              |
| `ragnasync_merc_item_drop_log_channel_id` | string | `""`    | Discord channel for drop logs                                    |
| `ragnasync_merc_item_drop_log_min_rate`   | int    | `500`   | Minimum rate to log                                              |
| `ragnasync_merc_item_drop_log_types`      | string | `"all"` | Item categories to log (see [Item Drop Types](#item-drop-types)) |

***

### Mob Death Logging

Log when specific mob types are killed (MVP, Mini-boss, or normal mobs).

```conf
ragnasync_mob_death_log_enabled: true
ragnasync_mob_death_log_channel_id: "123456789012345678"
ragnasync_mob_death_log_types: "mvp,miniboss"
```

| Setting                              | Type   | Default | Description                                                  |
| ------------------------------------ | ------ | ------- | ------------------------------------------------------------ |
| `ragnasync_mob_death_log_enabled`    | bool   | `false` | Enable death logging                                         |
| `ragnasync_mob_death_log_channel_id` | string | `""`    | Discord channel for death logs                               |
| `ragnasync_mob_death_log_types`      | string | `"all"` | Mob classes to log (see [Mob Death Types](#mob-death-types)) |

***

### Container Logging

Log when players open containers like Card Albums and Gift Boxes.

```conf
ragnasync_container_log_enabled: true
ragnasync_container_log_channel_id: "123456789012345678"
ragnasync_container_log_types: "card_album,giftbox"
ragnasync_container_log_group_min: 0
ragnasync_container_log_group_max: 0
```

| Setting                              | Type   | Default | Description                                                      |
| ------------------------------------ | ------ | ------- | ---------------------------------------------------------------- |
| `ragnasync_container_log_enabled`    | bool   | `false` | Enable container logging                                         |
| `ragnasync_container_log_channel_id` | string | `""`    | Discord channel for container logs                               |
| `ragnasync_container_log_types`      | string | `"all"` | Container types to log (see [Container Types](#container-types)) |
| `ragnasync_container_log_group_min`  | int    | `0`     | Minimum GM group level to log                                    |
| `ragnasync_container_log_group_max`  | int    | `99`    | Maximum GM group level to log                                    |

**Tip:** Set `group_min: 0` and `group_max: 0` to only log regular players (no GMs).

***

### Shop Logging

Log NPC shop buy/sell transactions.

```conf
ragnasync_shop_log_enabled: true
ragnasync_shop_log_channel_id: "123456789012345678"
ragnasync_shop_log_buy_enabled: true
ragnasync_shop_log_sell_enabled: true
ragnasync_shop_log_min_zeny: 1000000
ragnasync_shop_log_group_min: 0
ragnasync_shop_log_group_max: 0
```

| Setting                           | Type   | Default | Description                       |
| --------------------------------- | ------ | ------- | --------------------------------- |
| `ragnasync_shop_log_enabled`      | bool   | `false` | Enable NPC shop logging           |
| `ragnasync_shop_log_channel_id`   | string | `""`    | Discord channel for shop logs     |
| `ragnasync_shop_log_buy_enabled`  | bool   | `true`  | Log buy transactions              |
| `ragnasync_shop_log_sell_enabled` | bool   | `true`  | Log sell transactions             |
| `ragnasync_shop_log_min_zeny`     | int    | `0`     | Minimum zeny to log (0 = log all) |
| `ragnasync_shop_log_group_min`    | int    | `0`     | Minimum GM group level to log     |
| `ragnasync_shop_log_group_max`    | int    | `99`    | Maximum GM group level to log     |

***

### Vending Logging

Log player-to-player vending transactions and shop openings.

```conf
ragnasync_vending_log_enabled: true
ragnasync_vending_log_channel_id: "123456789012345678"
ragnasync_vending_log_min_zeny: 100000
ragnasync_vending_log_group_min: 0
ragnasync_vending_log_group_max: 99
```

| Setting                            | Type   | Default | Description                       |
| ---------------------------------- | ------ | ------- | --------------------------------- |
| `ragnasync_vending_log_enabled`    | bool   | `false` | Enable vending logging            |
| `ragnasync_vending_log_channel_id` | string | `""`    | Discord channel for vending logs  |
| `ragnasync_vending_log_min_zeny`   | int    | `0`     | Minimum zeny to log (0 = log all) |
| `ragnasync_vending_log_group_min`  | int    | `0`     | Minimum GM group level to log     |
| `ragnasync_vending_log_group_max`  | int    | `99`    | Maximum GM group level to log     |

**Note:** This setting also controls `vending_item` messages sent when a player opens their shop.

***

### Refine Logging

Log equipment refining attempts (success/failure/downgrade).

```conf
ragnasync_refine_log_enabled: true
ragnasync_refine_log_channel_id: "123456789012345678"
ragnasync_refine_log_min_level: 7
ragnasync_refine_log_group_min: 0
ragnasync_refine_log_group_max: 0
```

| Setting                           | Type   | Default | Description                               |
| --------------------------------- | ------ | ------- | ----------------------------------------- |
| `ragnasync_refine_log_enabled`    | bool   | `false` | Enable refine logging                     |
| `ragnasync_refine_log_channel_id` | string | `""`    | Discord channel for refine logs           |
| `ragnasync_refine_log_min_level`  | int    | `0`     | Minimum refine level to log (0 = log all) |
| `ragnasync_refine_log_group_min`  | int    | `0`     | Minimum GM group level to log             |
| `ragnasync_refine_log_group_max`  | int    | `99`    | Maximum GM group level to log             |

**Tip:** Set `min_level: 7` to only log high-refine attempts where things get exciting!

***

### Population Logging

Send periodic server population statistics to Discord.

```conf
ragnasync_population_log_enabled: true
ragnasync_population_log_channel_id: "123456789012345678"
ragnasync_population_log_interval: 1800
```

| Setting                               | Type   | Default | Description                         |
| ------------------------------------- | ------ | ------- | ----------------------------------- |
| `ragnasync_population_log_enabled`    | bool   | `false` | Enable population stats             |
| `ragnasync_population_log_channel_id` | string | `""`    | Discord channel for population logs |
| `ragnasync_population_log_interval`   | int    | `3600`  | Update interval in seconds (min 60) |

***

### Ticket Support System

Enable player support tickets via PM @Support or @request command.

```conf
ragnasync_ticket_support_system_enabled: true
ragnasync_ticket_support_system_close_on_logout: true
```

| Setting                                           | Type | Default | Description                            |
| ------------------------------------------------- | ---- | ------- | -------------------------------------- |
| `ragnasync_ticket_support_system_enabled`         | bool | `false` | Enable ticket support system           |
| `ragnasync_ticket_support_system_close_on_logout` | bool | `false` | Auto-close ticket when player logs out |

***

## Log Type Filters

The `*_log_types` keys all take a **comma-separated, case-insensitive** list of tokens. Common behavior across every filter:

* `all` (or an empty string) disables the filter — everything is logged.
* Unknown tokens are skipped with a `[Warning]: Unknown ... type '...'` line printed at map-server startup.
* If every token in your list is invalid, the mask is empty — which currently falls back to "log everything." If your filter isn't doing anything, check the map-server startup log first.

Each filter key accepts a **different** set of tokens. Mixing them up is the most common configuration mistake: for example, `ragnasync_mob_item_drop_log_types` is filtered by **item** category, not by the class of mob that dropped it, so `"mvp,miniboss"` is silently invalid and logs nothing specific.

### Item Drop Types

Used by `ragnasync_mob_item_drop_log_types`, `ragnasync_hom_item_drop_log_types`, and `ragnasync_merc_item_drop_log_types`. Filters which **item categories** are announced when a monster drops them.

| Token        | Filters drops of                     |
| ------------ | ------------------------------------ |
| `all`        | Every item category (default)        |
| `healing`    | Healing items (potions, herbs, food) |
| `usable`     | Consumables / usable items           |
| `etc`        | Miscellaneous / crafting materials   |
| `armor`      | Armor pieces                         |
| `weapon`     | Weapons                              |
| `card`       | Monster cards                        |
| `petegg`     | Pet eggs                             |
| `petarmor`   | Pet equipment                        |
| `ammo`       | Arrows, bullets, other ammunition    |
| `shadowgear` | Shadow equipment                     |
| `cash`       | Cash shop items                      |

{% hint style="warning" %}
There is currently no way to filter item-drop logs by the **mob class** that dropped the item (e.g. "only log drops from MVPs"). A value like `"mvp,miniboss"` here will be rejected and log nothing — use `min_rate` to narrow drops, or route messages through the dashboard if you need MVP-specific alerts.
{% endhint %}

### Mob Death Types

Used by `ragnasync_mob_death_log_types`. Filters which **mob classes** fire a death log when killed.

| Token      | Filters kills of                            |
| ---------- | ------------------------------------------- |
| `all`      | Every mob class (default)                   |
| `normal`   | Regular mobs — logs every kill, very spammy |
| `miniboss` | Mini-boss monsters                          |
| `mvp`      | MVPs                                        |

### Container Types

Used by `ragnasync_container_log_types`. Filters which container items fire a log when opened.

| Token        | Filters openings of                        |
| ------------ | ------------------------------------------ |
| `all`        | Every container type (default)             |
| `card_album` | Card Albums (Old Card Album, variants)     |
| `giftbox`    | Gift boxes (note: one word, no underscore) |
| `misc_box`   | Other miscellaneous container items        |

***

## Channel Discord Linking

Any in-game channel can be linked to Discord by adding the `discord_channel_id` property to its configuration.

### Public Channel Example

```conf
{
    name: "#global"
    alias: "[Global]"
    color: "White"
    type: "CHAN_TYPE_PUBLIC"
    delay: 1000
    autojoin: true
    leave: false
    chat: true
    discord_channel_id: "123456789012345678"  // Link to Discord channel
}
```

### Private Channel Default

Private channels can also have a default Discord link:

```conf
private_channel: {
    allow: true
    color: "Default"
    delay: 1000
    max_member: 1000
    discord_channel_id: "123456789012345678"  // Default Discord channel
}
```

**Notes:**

* Only channels with `discord_channel_id` configured will send/receive Discord messages
* Messages from in-game appear in Discord and vice versa

***

## Channel Types

| Type               | Description                   |
| ------------------ | ----------------------------- |
| `CHAN_TYPE_PUBLIC` | Open channels anyone can join |
| `CHAN_TYPE_ALLY`   | Guild alliance channels       |
| `CHAN_TYPE_MAP`    | Map-specific local channels   |

***

## Available Colors

```conf
colors: {
    Default: "0xffffff"
    Red: "0xff0000"
    Blue: "0x83cfe9"
    Orange: "0xe57c00"
    Cyan: "0x00b89d"
    Yellow: "0xffff90"
    Green: "0x28bf00"
    White: "0xFFFFFF"
    Purple: "0xD67FFF"
    LightGreen: "0xB6FF00"
    Normal: "0x00ff00"
}
```

***

## GM Commands

These in-game commands allow GMs to manage the RagnaSync service connection.

### @startsync

Starts the RagnaSync service if it was stopped.

```
@startsync
```

**Notes:**

* Reloads configuration from `channels.conf` before connecting
* Resets retry counter
* No effect if service is already running

### @stopsync

Stops the RagnaSync service and disconnects from the server.

```
@stopsync
```

**Notes:**

* Gracefully disconnects from RagnaSync
* Service will not auto-reconnect until manually started
* Useful for maintenance or debugging

### @reloadsync

Reloads the RagnaSync configuration and reconnects to the service.

```
@reloadsync
```

**Aliases:** `@syncreload`

**Notes:**

* Reads fresh settings from `channels.conf`
* Reconnects with new configuration
* Use after modifying any RagnaSync settings
* Displays current retry settings after reload

### @syncstatus

Displays the current RagnaSync connection status.

```
@syncstatus
```

**Output includes:**

* Connection state (connected/disconnected/stopped)
* Retry attempt count (if applicable)
* Current configuration summary

***

## Getting Your Discord IDs

See [Installation](/getting-started/installation) for instructions on obtaining your Discord Server ID and Channel IDs.

The `ragnasync_discord_guid` and `ragnasync_client_guid` are provided by RagnaSync during installation.

{% hint style="warning" %}
`ragnasync_discord_id` and `ragnasync_discord_guid` are a matched pair — the GUID belongs to that one Discord server. Your server is turned away at connect time if they don't match, so if you move to a different Discord server, ask support to reissue **both** together. Copying a `channels.conf` from another server won't work for the same reason.
{% endhint %}

***

## Using conf/import/channels.txt

{% hint style="info" %}
**Running multiple environments?** This section is for you.
{% endhint %}

If you run both a live server and a test server from the same codebase, you'll want each environment to have its own `ragnasync_client_guid`. The problem is that `conf/channels.conf` is typically committed to git — so any GUID you set there gets shared across all environments.

The solution: use `conf/import/channels.txt` instead.

### Why channels.txt?

| File                       | Git tracked?    | Best for                                                        |
| -------------------------- | --------------- | --------------------------------------------------------------- |
| `conf/channels.conf`       | Yes (committed) | Shared settings like channel colors, types, Discord channel IDs |
| `conf/import/channels.txt` | No (gitignored) | Per-environment settings like `ragnasync_client_guid`           |

Most rAthena and Hercules setups gitignore the `conf/import/` folder by default. This makes it the perfect place for settings that differ between your live and test servers.

### How to set it up

Create or edit `conf/import/channels.txt` on each server and add your environment-specific GUID:

```conf
ragnasync_client_guid: "your-unique-guid-here"
```

That's it! Settings in `conf/import/channels.txt` override anything in `conf/channels.conf`, so each environment gets its own identity on RagnaSync.

### Example: Live + Test setup

**Live server** (`conf/import/channels.txt`):

```conf
ragnasync_client_guid: "a1b2c3d4e5f6789012345678abcdef00"
```

**Test server** (`conf/import/channels.txt`):

```conf
ragnasync_client_guid: "f0e1d2c3b4a5678901234567fedcba99"
```

Each server now has its own dashboard, crash logs, metrics, and status tracking on RagnaSync — completely independent.

{% hint style="success" %}
**Tip:** You can leave `ragnasync_client_guid` empty in `conf/channels.conf` and only set it in `conf/import/channels.txt` on each server. A GUID is generated automatically on first connect if you don't set one.
{% endhint %}


# Message Sync

Bridges in-game chat channels to Discord channels

Bridges in-game chat channels to Discord - players can chat across both platforms seamlessly.

## What It Does

* In-game messages appear in Discord with character name and avatar
* Discord messages appear in-game (bi-directional)
* Works with any channel: #main, #trade, #support, etc.

## Configuration

Add `discord_channel_id` to any channel in `channels.conf`:

```
{
    name: "#main"
    alias: "[Main]"
    color: "Green"
    discord_channel_id: "1234567890123456789"
}
```

For full configuration options, see [Channels Config](/getting-started/channels-conf).

## Message Format

Messages appear in Discord with:

* Character name as the webhook username
* Character head sprite as the avatar
* Message content as sent in-game

## Bi-Directional Chat

RagnaSync also supports Discord-to-game messaging. Discord users can send messages that appear in the corresponding in-game channel.

### Message Prefix

When a Discord message appears in-game, the sender's name is shown with a prefix so players know it came from Discord. The default is `@`:

```
@DiscordUser: Hey everyone!
```

You can change this prefix — up to 10 characters, or remove it entirely — on the **Configure** page in your dashboard, under the **Ragnarok** section. A live preview shows exactly how it'll look in-game as you type.

| Prefix        | In-Game Result                   |
| ------------- | -------------------------------- |
| `@` (default) | `@PlayerName: Hello everyone!`   |
| `[D]`         | `[D]PlayerName: Hello everyone!` |
| `~`           | `~PlayerName: Hello everyone!`   |
| *(empty)*     | `PlayerName: Hello everyone!`    |


# Troubleshooting

Common errors and how to fix them

Having issues with RagnaSync? This page covers common errors you might see in your server console and how to resolve them.

***

## Connection Errors

{% hint style="info" %}
Every connection error below is followed by a second console line pointing you back to this page (or to contact support) -- so if your console shows an error not listed here, it is worth checking again after an update.
{% endhint %}

### "Discord Guild ID is required."

**What it means:** Your Discord server ID is missing or empty.

**How to fix:**

1. Get your Discord server ID (right-click server icon → Copy Server ID)
2. Open `conf/channels.conf`
3. Set your Discord server ID:

```
ragnasync_discord_id: "1234567890123456789"
```

***

### "Invalid ragnasync\_discord\_id value found inside channels.conf"

**What it means:** The Discord server ID you entered is not a valid format.

**How to fix:**

* Discord server IDs are 17-19 digit numbers
* Make sure it's wrapped in quotes: `"1234567890123456789"`
* Don't leave the default placeholder value

***

### "Set ragnasync\_discord\_guid in channels.conf"

**What it means:** Your Discord GUID (security key) is missing, so your server can't prove who it is.

**How to fix:**

1. Contact RagnaSync support to get your Discord GUID
2. Add it to your `channels.conf`:

```
ragnasync_discord_guid: "your-guid-here"
```

{% hint style="info" %}
The Discord GUID is provided by RagnaSync during installation. If you don't have it, contact support.
{% endhint %}

***

### "guid does not match discord\_id. Reissue both."

**What it means:** Your `ragnasync_discord_guid` isn't the one that belongs to your `ragnasync_discord_id`. The two are issued together as a pair, and your server won't connect unless they match.

**How to fix:**

* Double-check you copied the GUID correctly (no extra spaces)
* **Moved to a new Discord server?** This is the usual cause. Changing `ragnasync_discord_id` without a matching new GUID lands here — ask support to reissue **both** values together
* **Copied `channels.conf` from another server?** The GUID in it belongs to that server's Discord, not yours. Ask support for your own pair
* Contact RagnaSync support if the problem persists

{% hint style="warning" %}
Your Discord GUID is also your dashboard login. Treat it like a password — don't paste it into a config you share with anyone else.
{% endhint %}

***

### "IP blocked: invalid guid" / "IP blocked: missing guid"

**What it means:** Your server has been temporarily blocked after several connection attempts with a wrong or missing GUID.

**How to fix:**

* Wait for the block to expire (the error message shows remaining time)
* While waiting, verify your `channels.conf` settings are correct
* Once the block expires, your server will retry automatically

{% hint style="info" %}
The block is scoped to your Discord server, not to your whole machine — an unrelated Discord server sharing the same IP (shared hosting, a multi-tenant VPS) keeps connecting normally. Your own Ragnarok servers are blocked alongside it if they use the same `ragnasync_discord_id`, because they share the one GUID that needs fixing.
{% endhint %}

***

### "GUID already connected. Use unique GUID."

**What you'll see in console:**

```
[Info]: RagnaSync Service: Connection accepted
[Status]: RagnaSync Service: Logging in to server...
[Error]: [RagnaSync] GUID already connected. Use unique GUID.
[Status]: RagnaSync service has disconnected.
[Status]: RagnaSync service: Will attempt reconnection in 5 minute(s)
```

**What it means:** Another server is already connected using the same `client_guid` in your `channels.conf`. This commonly happens when you have both a test server and live server using the same GUID.

**How to fix:**

1. **Quick Fix: Disconnect your test server** - Simply disconnect your test server until your live server connection is established. This is the easiest solution if you don't need both servers connected simultaneously.
2. **For simultaneous connections: Use unique GUIDs** - If you need both test and live servers connected at the same time, each server **must have a different `client_guid`** in their `channels.conf` file.

{% hint style="info" %}
**Running multiple servers long-term?** Don't hand-edit `channels.conf` on each one -- put the per-server `ragnasync_client_guid` in `conf/import/channels.txt` instead, which is gitignored by default so each environment keeps its own identity without fighting over a shared, git-tracked file. See [Using conf/import/channels.txt](https://docs.ragnasync.com/getting-started/pages/wH2yl4PsWTEIyYft1ZzU#using-conf-import-channels.txt) for the full setup.
{% endhint %}

After making changes, use `@reloadsync` in-game to restart the RagnaSync service.

{% hint style="info" %}
If you recently restarted your server, wait a few minutes for the old connection to timeout before trying again.
{% endhint %}

***

### "Client outdated. Contact support to upgrade."

**What it means:** Your RagnaSync client files are too old for the current protocol -- a bigger gap than a normal version bump.

**How to fix:**

* Contact RagnaSync support for updated installation files
* Make sure you're using the files provided by our installation team, not a copy from another server

***

### "Upgrade required. \[version] (Latest: \[version]). N attempts left"

**What it means:** Your client version is below the minimum supported version. Repeated attempts on an outdated version temporarily block your IP (see "IP blocked" below).

**How to fix:**

* Update to the latest RagnaSync client files
* Check the version shown in the message against the "Latest" version -- if you're already on it, contact support, as this usually means an update did not fully apply

***

### "IP blocked. Nm remaining"

**What it means:** Your server kept connecting with an outdated client version, so your IP is temporarily blocked from retrying.

**How to fix:**

* Wait for the block to expire (the error message shows remaining minutes)
* Update your client files while you wait, so the next attempt succeeds
* Once the block expires, `@reloadsync` or the automatic reconnect will try again

***

### "Invalid client GUID format. Contact Support."

**What it means:** The `ragnasync_client_guid` in your `channels.conf` isn't a valid GUID -- it may be corrupted, truncated, or edited by hand.

**How to fix:**

* Contact RagnaSync support for your correct `ragnasync_client_guid`
* Don't hand-edit this value -- copy it exactly as issued

***

### "Maximum reconnection attempts reached. Stopping."

**What it means:** Your server tried to connect multiple times but failed each time.

**How to fix:**

1. Check your `channels.conf` settings are correct
2. Verify your Discord server ID and GUIDs
3. Use `@reloadsync` to try connecting again
4. If issues persist, contact support

***

## Script Errors

### "Script validation failed: Missing required fields"

**What it means:** A RagnaScript message is missing required data.

**How to fix:** Make sure your RagnaScript calls include all required fields:

```c
// Required fields for most scripts:
ragnascript("[action: message][channel: 1234567890][name: " + strcharinfo(0) + "][message: Hello][account_id: " + getcharid(3) + "][char_id: " + getcharid(0) + "]");
```

Common missing fields:

* `action` - The script type
* `channel` - Discord channel ID
* `account_id` - Player's account ID
* `char_id` - Player's character ID

***

### "Message is too long (max 2000 characters)"

**What it means:** The message you're trying to send exceeds Discord's limit.

**How to fix:**

* Shorten your message to under 2000 characters
* For NPC scripts, add length validation:

```c
if (getstrlen(.@message$) > 1900) {
    mes "Message too long!";
    close;
}
```

***

### "Discord server is not connected, message not queued"

**What it means:** Your server isn't connected to RagnaSync, so messages can't be sent.

**How to fix:**

1. Check connection status with `@syncstatus`
2. If disconnected, use `@reloadsync` to reconnect
3. Verify your `channels.conf` settings

***

### "Queue full, dropping message"

**What it means:** Too many messages are waiting to be sent. New messages are being dropped.

**How to fix:**

* This usually means your server is sending messages faster than they can be delivered
* Add rate limiting to your NPC scripts
* Check if your connection is stable

***

## Channel Errors

### "Invalid channel ID" / "Default channel ID detected"

**What it means:** The Discord channel ID is missing or still set to the placeholder value.

**How to fix:**

1. Get your Discord channel ID (right-click channel → Copy Channel ID)
2. Update your channel configuration:

```
{
    name: "#global"
    alias: "[Global]"
    discord_channel_id: "1234567890123456789"  // Your actual channel ID
}
```

{% hint style="warning" %}
Don't forget to uncomment the line by removing `//` if it's commented out.
{% endhint %}

***

## GM Command Reference

Use these commands to manage RagnaSync:

| Command       | Description                                        |
| ------------- | -------------------------------------------------- |
| `@startsync`  | Start RagnaSync service                            |
| `@stopsync`   | Stop RagnaSync service                             |
| `@reloadsync` | Reload config and reconnect (alias: `@syncreload`) |
| `@syncstatus` | Check connection status                            |

***

## Still Having Issues?

If you've tried the solutions above and still have problems:

1. **Check your server console** for the exact error message
2. **Verify all settings** in `channels.conf` match what RagnaSync provided
3. **Contact support** with:
   * The exact error message
   * Your Discord server ID
   * When the problem started

[Contact RagnaSync Support](https://discord.gg/FQjjeaMZK2)


# Overview

How RagnaScript works and the npc\_message action

RagnaScript is how your Ragnarok server talks to Discord. You add `ragnascript()` calls to your NPC scripts, and RagnaSync turns them into Discord announcements.

## Syntax

```c
ragnascript("[action: npc_message][name: ...][message: ...][channel: ...]");
```

Every call is an `action` plus a set of `[key: value]` fields. For scripts you write yourself, that action is **`npc_message`** — it posts a message to Discord on your server's behalf.

```c
ragnascript("[action: npc_message][name: Event Manager][message: Daily Hunt is now active!][channel: YOUR_CHANNEL_ID]");
```

## Required fields

`npc_message` takes just three fields:

| Field     | Description                                                                                          |
| --------- | ---------------------------------------------------------------------------------------------------- |
| `name`    | The author name shown in Discord — an NPC label like `System` or `Event Manager` (not a real player) |
| `message` | The text that appears in Discord                                                                     |
| `channel` | The Discord channel ID to post to                                                                    |

{% hint style="info" %}
`npc_message` is unattributed — there's no player attached, so it needs no `account_id` or `char_id`. That's what makes it the right action for anything the *server* says.
{% endhint %}

## What about chat, requests and logs?

Other Discord features aren't actions you hand-write — they're turned on in your configuration and run automatically:

* **Player chat** bridging is configured, not scripted — see [Message Sync](/getting-started/channel-messages).
* **Player `@request` support messages** are handled for you — see [Ticket Support Command](/features/ticket-system/ticket-support).
* **Logging** (mob drops, deaths, vending, refine and more) is enabled in config — see the **Event Logs** section in the sidebar.

## Next steps

* [NPC Examples](/ragnascript/custom-messages) — ready-to-use `npc_message` scripts and the mechanics behind them.
* [Build with AI](/ragnascript/build-ragnascripts-with-ai) — have Claude or ChatGPT write scripts for you.


# NPC Examples

Ready-to-use npc\_message scripts for Discord announcements

Ready-to-use `npc_message` scripts for server-generated announcements. Replace `YOUR_CHANNEL_ID` with your Discord channel ID.

The `npc_message` action is for messages your server sends on its own — events, maintenance notices, WoE status, staff alerts. It only needs `name`, `message`, and `channel`. (For player chat use [Message Sync](/getting-started/channel-messages); for staff `@request` alerts see [Ticket Support Command](/features/ticket-system/ticket-support).)

***

## It's just a script function

`ragnascript()` is a normal rAthena/Hercules script function — exactly like `announce`, `mes`, or `getitem`. That means it drops straight into **any script your server already runs**: NPCs, event labels (`OnPCKillEvent`, `OnAgitStart`, …), quest scripts, instances, functions, item bonus scripts, `@command` handlers — anywhere a script command is valid.

There's **no limit on what you can announce**. If rAthena/Hercules can script it, you can post it to Discord. The examples below are common starting points, not the boundary — wherever you can already write a line of script, you can add a `ragnascript()` call beside it. It's plug-and-play: paste the function in, set your channel, done.

{% hint style="info" %}
Anything you can detect in script — a player hitting max level, a custom currency changing, an MVP spawning, a GM toggling an event — can trigger a Discord announcement. Just put the `ragnascript()` call wherever that logic already lives.
{% endhint %}

***

## How npc\_message works

Every `npc_message` call has the same three fields. That's the whole mechanic:

```c
ragnascript("[action: npc_message][name: Town Crier][message: The shop is now open!][channel: YOUR_CHANNEL_ID]");
```

The syntax is always `[action: npc_message]` followed by the three fields, in any order:

```
ragnascript("[action: npc_message][name: <author>][message: <text>][channel: <id>]");
```

| Field     | Required | What it does                                                                                                                                                   |
| --------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `action`  | Yes      | Always `npc_message` for these scripts. Tells RagnaSync this is an unattributed server announcement.                                                           |
| `name`    | Yes      | The author name shown in Discord. This is **not** a real player — it's whatever label you want the message to come from (`System`, `WoE`, `Event Manager`, …). |
| `message` | Yes      | The text that appears in Discord. Plain text, and supports Discord emoji shortcodes + Markdown. Build it however you like — see below.                         |
| `channel` | Yes      | The Discord channel ID the message is posted to. Different calls can target different channels.                                                                |

No `account_id` or `char_id` is needed — `npc_message` is unattributed, so it never shows a player avatar or links to a character. That's exactly why it's the right action for anything the *server* says.

### Choosing when it fires

The `ragnascript()` call is identical everywhere — what changes between implementations is the rAthena **event label** that triggers it. Pick the label that matches when you want the announcement to fire:

| Trigger label                         | Fires when                                      | Example below                                                                                  |
| ------------------------------------- | ----------------------------------------------- | ---------------------------------------------------------------------------------------------- |
| `OnInit`                              | Once, when scripts finish loading (server boot) | [Server Online Notice](#server-online-notice)                                                  |
| `OnHour00`–`OnHour23` / `OnClockHHMM` | At a fixed time of day                          | [Event Announcements](#event-announcements), [Scheduled Server Stats](#scheduled-server-stats) |
| `OnMinuteNN`                          | Every hour at minute NN                         | [Server Maintenance Notice](#server-maintenance-notice)                                        |
| `OnAgitStart` / `OnAgitEnd`           | WoE begins / ends                               | [WoE Status](#woe-status)                                                                      |
| `OnNPCKillEvent`                      | A monster dies (use `killedrid`)                | [MVP Kill Announcements](#mvp-kill-announcements)                                              |
| `OnPCKillEvent`                       | A player kills another player/monster           | [Anti-Bot Alerts](#anti-bot-alerts)                                                            |
| `function` / `callfunc`               | On demand, called from your other scripts       | [Anti-Bot Alerts](#anti-bot-alerts) (`ReportBot`)                                              |

These are standard rAthena/Hercules labels — RagnaSync doesn't add any of its own. Any event that can run script code can call `ragnascript()`.

### Building the message dynamically

`message` is just a string, so you can concatenate game data into it with `+`, the same as any rAthena script. Common sources for announcement text:

| Function                           | Returns                          | Used in                                                           |
| ---------------------------------- | -------------------------------- | ----------------------------------------------------------------- |
| `strcharinfo(0)`                   | The triggering player's name     | [MVP Kill](#mvp-kill-announcements), [Anti-Bot](#anti-bot-alerts) |
| `strcharinfo(3)`                   | The player's current map name    | [MVP Kill](#mvp-kill-announcements)                               |
| `strmobinfo(1, <mobID>)`           | A monster's display name         | [MVP Kill](#mvp-kill-announcements)                               |
| `getmonsterinfo(<id>, MOB_MVPEXP)` | `>0` for MVP mobs (filter kills) | [MVP Kill](#mvp-kill-announcements)                               |
| `getusers(1)`                      | Online player count              | [Scheduled Server Stats](#scheduled-server-stats)                 |
| `gettime(DT_HOUR)`                 | Current server hour (0–23)       | [Server Maintenance](#server-maintenance-notice)                  |

Here's the pattern — pull a value, drop it into the message:

```c
-	script	BossWatch	-1,{
OnNPCKillEvent:
    // killedrid holds the mob ID that just died
    if (killedrid == 1086) {   // Golden Thief Bug
        ragnascript("[action: npc_message][name: Boss Watch][message: " + strcharinfo(0) + " just slayed " + strmobinfo(1, killedrid) + "!][channel: YOUR_CHANNEL_ID]");
    }
    end;
}
```

{% hint style="info" %}
Even though you can reference the player who triggered the event (like `strcharinfo(0)` above), the message is still posted as the **NPC name** you set — not as that player. Use `npc_message` when you want the *announcement* in Discord, not a player-attributed chat line.
{% endhint %}

***

## Event Announcements

Announce events to Discord automatically:

```c
-	script	EventAnnouncer	-1,{
OnHour12:
    ragnascript("[action: npc_message][name: Event Manager][message: Daily Hunt is now active!][channel: YOUR_CHANNEL_ID]");
    announce "Daily Hunt event is now active!", bc_all;
    end;
}
```

***

## Server Maintenance Notice

```c
-	script	Maintenance	-1,{
OnMinute30:
    if (gettime(DT_HOUR) == 5) {
        ragnascript("[action: npc_message][name: System][message: Server maintenance in 30 minutes.][channel: YOUR_CHANNEL_ID]");
    }
    end;
}
```

***

## WoE Status

```c
-	script	WoEAnnounce	-1,{
OnAgitStart:
    ragnascript("[action: npc_message][name: WoE][message: War of Emperium has begun!][channel: YOUR_CHANNEL_ID]");
    end;

OnAgitEnd:
    ragnascript("[action: npc_message][name: WoE][message: War of Emperium has ended!][channel: YOUR_CHANNEL_ID]");
    end;
}
```

***

## Anti-Bot Alerts

Notify staff when suspicious activity is detected:

```c
-	script	AntiBotAlert	-1,{
OnPCKillEvent:
    // Detect unusually fast kill rate
    if (@kill_count == 0) {
        @first_kill = gettimetick(2);
    }
    @kill_count++;

    .@elapsed = gettimetick(2) - @first_kill;
    if (@kill_count >= 50 && .@elapsed < 60) {
        ragnascript("[action: npc_message][name: Anti-Bot][message: Suspicious activity: " + strcharinfo(0) + " killed 50+ mobs in under 1 minute][channel: YOUR_CHANNEL_ID]");
        @kill_count = 0;
    }

    // Reset counter every 5 minutes
    if (.@elapsed > 300) {
        @kill_count = 0;
    }
    end;
}
```

You can also trigger alerts from your existing anti-bot systems:

```c
// Call this function when bot behavior is detected
function	script	ReportBot	{
    .@char_name$ = getarg(0);
    .@reason$ = getarg(1);
    ragnascript("[action: npc_message][name: Anti-Bot][message: " + .@char_name$ + " flagged - " + .@reason$ + "][channel: YOUR_CHANNEL_ID]");
    return;
}

// Usage from other scripts:
// callfunc("ReportBot", strcharinfo(0), "Teleport hack detected");
```

***

## Server Online Notice

Post to Discord the moment the server finishes loading — `OnInit` runs once when scripts load:

```c
-	script	ServerStatus	-1,{
OnInit:
    ragnascript("[action: npc_message][name: Server][message: :green_circle: Server is now online and accepting connections!][channel: YOUR_CHANNEL_ID]");
    end;
}
```

{% hint style="info" %}
Discord renders emoji shortcodes like `:green_circle:` and standard Markdown (`**bold**`, `*italic*`) inside the `message` text — handy for making announcements stand out.

```c
ragnascript("[action: npc_message][name: Event][message: **2x EXP** is now live for the next hour!][channel: YOUR_CHANNEL_ID]");
```

{% endhint %}

***

## MVP Kill Announcements

Announce who landed an MVP, with the boss name pulled straight from the mob database:

```c
-	script	MvpAnnouncer	-1,{
OnNPCKillEvent:
    // Only announce actual MVP mobs
    if (getmonsterinfo(killedrid, MOB_MVPEXP) > 0) {
        ragnascript("[action: npc_message][name: MVP Tracker][message: :crown: " + strcharinfo(0) + " defeated " + strmobinfo(1, killedrid) + " in " + strcharinfo(3) + "!][channel: YOUR_CHANNEL_ID]");
    }
    end;
}
```

***

## Routing to Different Channels

The `channel` field is just a variable — store your channel IDs once and route each announcement where it belongs:

```c
-	script	AnnounceRouter	-1,{
OnInit:
    // Set up your channels in one place
    .events$  = "1111111111111111111";
    .staff$   = "2222222222222222222";
    .woe$     = "3333333333333333333";
    end;

OnClock1900:
    // Goes to the events channel
    ragnascript("[action: npc_message][name: Event][message: Treasure Hunt starts in 10 minutes!][channel: " + .events$ + "]");
    end;

OnAgitStart:
    // Goes to the WoE channel
    ragnascript("[action: npc_message][name: WoE][message: The castles are now open — fight!][channel: " + .woe$ + "]");
    end;
}
```

***

## Scheduled Server Stats

Build the message from live game data and post it on a timer. Here we report the online player count every hour:

```c
-	script	PlayerCount	-1,{
OnHour00:
OnHour06:
OnHour12:
OnHour18:
    .@online = getusers(1);
    ragnascript("[action: npc_message][name: Server Stats][message: :busts_in_silhouette: There are currently **" + .@online + "** players online!][channel: YOUR_CHANNEL_ID]");
    end;
}
```

***

## Tips

**Keep it to announcements** - `npc_message` only requires `name`, `message`, and `channel`. There's no player attached, so use it for anything your server says on its own.

**Mind the brackets** - RagnaSync parses fields with `[key: value]`, so avoid putting literal `[` or `]` inside your `message` text — they can confuse the parser. Stick to plain text, emoji, and Markdown.

**One line per call** - Keep each `ragnascript("...")` on a single line, even when concatenating with `+`. Build longer text in a `.@msg$` variable first if it gets unwieldy, then pass `.@msg$` as the message.

**Rate limiting** - For scripts that fire on frequent events (like `OnPCKillEvent` above), add a cooldown so you don't flood the channel:

```c
if (gettimetick(2) - @last_msg < 60) {
    end;
}
@last_msg = gettimetick(2);
```

***

Need the basics? See [RagnaScript Overview](/ragnascript/scripts).


# Build with AI

Use Claude, ChatGPT, or any AI assistant to write npc\_message scripts for you

You don't have to write RagnaScripts by hand. AI assistants like **Claude** and **ChatGPT** are great at turning a plain-English idea ("announce to Discord when an MVP dies") into a working `npc_message` call — as long as they know how RagnaSync works.

This page focuses on `npc_message`, the action your server uses to post its own announcements to Discord (events, WoE status, boss kills, server notices). It gives you two things:

1. A **context prompt** to give the assistant first, so it understands `npc_message` and reads our docs.
2. **Example prompts** to build on.

***

## Step 1 — Give the assistant context

AI assistants don't know about RagnaSync out of the box. Give it this block at the start of your chat. It points the assistant at [docs.ragnasync.com](https://docs.ragnasync.com) so it can look up the real syntax instead of guessing.

{% hint style="info" %}
Claude and ChatGPT can both browse the web, and some setups (like Claude with direct repo access) can read your server files too. Either way, this prompt tells the assistant to read our docs before answering — that's what keeps the scripts accurate.
{% endhint %}

```
You are helping me write npc_message RagnaScripts for RagnaSync, a tool that
bridges a Ragnarok Online server (rAthena/Hercules) to Discord.

Before answering, read the documentation at https://docs.ragnasync.com so you
understand the current syntax and required fields. Focus on the npc_message
action specifically:

- The ragnascript() command and its [key: value] bracket syntax
- The npc_message action, used for server-generated announcements
- Its three fields: name (the author label shown in Discord),
  message (the text, which can include game data, emoji, and Markdown),
  and channel (the Discord channel ID)
- npc_message is unattributed — it takes no account_id or char_id

When I describe what I want, give me a complete NPC script in the
rAthena/Hercules scripting language using npc_message. Use YOUR_CHANNEL_ID as a
placeholder for the Discord channel ID, and keep each ragnascript() call on one
line. If a request would need a different action or a field the docs don't
support, tell me instead of inventing one.
```

Once the assistant confirms it has read the docs, start asking for scripts.

***

## Step 2 — Ask for what you want

With the context loaded, describe your idea in plain English. Be specific about **when** it should fire and **what** the Discord announcement should say.

### Example prompts

> Write an NPC that announces to Discord whenever the MVP "Baphomet" is killed, including which player landed the kill.

> Post a "Server is now online!" announcement to Discord every time the server boots.

> I want a daily countdown. Announce "WoE starts in 1 hour" to Discord at 7pm server time.

> Create a quest NPC. When a player turns in the final item, post an announcement to Discord congratulating them by name.

> Announce the current online player count to Discord every hour.

> When a player donates through my existing donation NPC, post a thank-you announcement to Discord.

### Tips for better results

* **Name the trigger.** "When a player logs in" → `OnPCLoginEvent`. "Every hour" → `OnHour`. The clearer the trigger, the better the script.
* **Say who the message is from.** `npc_message` shows whatever you put in the `name` field as the Discord author — tell the assistant what label to use (e.g. "from Event Manager").
* **Ask it to explain.** Add "explain each line" if you want to learn the syntax as you go.
* **Iterate.** If the first script isn't quite right, just reply with the change ("also broadcast it in-game with announce").

***

## Step 3 — Test before you trust

AI gets you most of the way, but always sanity-check:

1. Add the script to a test NPC on a **test/dev server** first.
2. Reload scripts (`@reloadsync` for RagnaSync config, `@reloadscript` for the NPC) and trigger the event.
3. Confirm the announcement lands in the right Discord channel with the right text.

{% hint style="warning" %}
Double-check the `channel` ID the assistant used. A wrong channel ID is the most common reason a script "works" but nothing shows up in Discord.
{% endhint %}

***

## Want to write them yourself?

If you'd rather understand the syntax directly, start with the [RagnaScript Overview](/ragnascript/scripts) and browse the [NPC Examples](/ragnascript/custom-messages) for ready-to-use `npc_message` scripts.


# Message Routing

Control where and how each event type sends messages to Discord

The **Message Routes** page on your RagnaSync dashboard lets you control exactly where each event type goes and how it looks in Discord. Think of routes as rules: "When *this event* happens, send it to *this channel* in *this format*."

You'll find it under **Discord > Messages** in your dashboard navigation.

***

## How It Works

Every event type (item drops, monster kills, refine logs, etc.) gets its own section on the Message Routes page. Each section can have one or more **routes** that tell RagnaSync where to send that event.

Click any route (or its **Configure** button) to open its own configuration page, where you set the channel, format, avatar, and conditions — with a live preview on the right showing how the message will look in Discord. Use **Add Route** to create a new one.

A route has five parts:

| Part                | What It Does                                           |
| ------------------- | ------------------------------------------------------ |
| **Format**          | How the message looks (Log, Embed, Narrate, or Custom) |
| **Channel**         | Which Discord channel receives the message             |
| **Avatar**          | The icon shown next to the message                     |
| **Conditions**      | Optional filters to control which events match         |
| **Active/Inactive** | Toggle the route on or off without deleting it         |

### Avatar options

| Option             | What You Get                                       |
| ------------------ | -------------------------------------------------- |
| **Character head** | The player's character sprite (needs a GRF upload) |
| **Initials**       | A coloured circle with the player's initials       |
| **Custom image**   | Your own uploaded image                            |
| **Monster sprite** | The sprite of the monster involved in the event    |
| **None**           | The default RagnaSync webhook avatar               |

{% hint style="info" %}
**Monster sprite** only appears on the four event types that involve a monster — Monster Kills, Item Drops, Homunculus Drops and Mercenary Drops. Other events have no monster to show, so the option is hidden there.

Monsters we don't have a sprite for — a custom monster, or a guardian/emperium, which the client stores as a 3D model rather than a sprite — fall back to the Poring sprite, so the post still looks like a monster event.
{% endhint %}

{% hint style="info" %}
For most event types, a route is created automatically the first time your game server sends that event. **Player Login** and **Player Logout** are the exception — since they fire on every connection, you need to add routes manually if you want Discord notifications.
{% endhint %}

***

## Message Formats

Each route can use a different format. Here's what they look like:

### Log Format

Clean, compact monospace lines — great for high-volume events.

```
💎 Item Drop:     | Marky            | Poring           | Apple                | 5.00%
```

### Rich Embed

Colorful Discord embed cards with structured fields. These are easier to scan and look more polished, and each embed includes a timestamp that automatically displays in every viewer's local timezone — no configuration needed.

### Narrate

A natural-language sentence that reads like a story.

```
Marky obtained an Apple
```

### Custom Message

Build your own format with `{{placeholder}}` syntax. On the route's **Configure** page, set the format to **Custom Message** and a template editor appears.

The editor gives you:

* **Clickable placeholder chips** — just click a field name and it gets inserted at your cursor
* **Live preview** — the preview pane shows how your message will look with sample data as you type

**Example template:**

```
:gem: {{player_name}} got {{item_name}} from {{mob_name}}! ({{rate}})
```

**Result in Discord:**

```
💎 Marky got Apple from Poring! (50.00%)
```

You can use emoji shortcodes like `:gem:`, `:trophy:`, or `:warning:` — they'll automatically convert to real emoji.

{% hint style="info" %}
Each event type has its own set of placeholders. The editor shows all available placeholders for that event type, so you don't need to memorize them.
{% endhint %}

***

## Multi-Channel Routing

You can send the same event to multiple Discord channels by adding more routes. For example, you might want:

* **Item Drops** → `#rare-drops` channel in **Rich Embed** format
* **Item Drops** → `#drop-log` channel in **Log Format** for a compact log

Just click **Add Route** on the event type and pick a different channel.

***

## Condition Filters

Conditions let you narrow down which events trigger a route. Open the route's **Configure** page and expand the **Conditions** section to set up filters.

### How Conditions Work

Each condition has three parts: a **field**, an **operator**, and a **value**.

| Operator   | Meaning       | Example                      |
| ---------- | ------------- | ---------------------------- |
| `=`        | Equals        | `mob_name = Poring`          |
| `!=`       | Not equals    | `item_name != Apple`         |
| `>`        | Greater than  | `rate_raw > 500`             |
| `<`        | Less than     | `rate_raw < 100`             |
| `>=`       | At least      | `refine_to >= 10`            |
| `<=`       | At most       | `refine_from <= 3`           |
| `contains` | Contains text | `player_name contains Admin` |
| `in`       | In list       | `mob_id in 1511,1647,1785`   |

### Condition Groups (AND/OR Logic)

* Conditions **within** the same group use **AND** logic — all must match
* Separate groups use **OR** logic — any group can match

**Example: "Only card drops from MVPs"**

* Group 1: `item_name contains Card` **AND** `rate_raw <= 100`

**Example: "Poring OR Drops card"**

* Group 1: `mob_name = Poring`
* Group 2: `item_name contains Card`

{% hint style="info" %}
The available filter fields depend on the event type. The condition editor only shows fields relevant to that event.
{% endhint %}

***

## Available Filter Fields

Here's what you can filter on for each event type:

### Item Drops (Mob, Homunculus, Mercenary)

| Field         | Description                       |
| ------------- | --------------------------------- |
| `player_name` | Player who got the drop           |
| `mob_name`    | Monster name                      |
| `mob_id`      | Monster ID                        |
| `item_name`   | Dropped item name                 |
| `item_id`     | Dropped item ID                   |
| `rate_raw`    | Drop rate as raw value (100 = 1%) |

### Monster Kills

| Field         | Description             |
| ------------- | ----------------------- |
| `player_name` | Player who got the kill |
| `mob_name`    | Monster name            |
| `mob_id`      | Monster ID              |

### Command Logs

| Field         | Description                 |
| ------------- | --------------------------- |
| `player_name` | Player who used the command |
| `command`     | The @command used           |

### Refine Logs

| Field         | Description               |
| ------------- | ------------------------- |
| `player_name` | Player who refined        |
| `item_name`   | Item being refined        |
| `item_id`     | Item ID                   |
| `refine_from` | Starting refine level     |
| `refine_to`   | Target refine level       |
| `result`      | Outcome (success/failure) |
| `zeny_cost`   | Zeny spent                |

### Shop Transactions

| Field         | Description                 |
| ------------- | --------------------------- |
| `player_name` | Player involved             |
| `type`        | Transaction type (buy/sell) |

### Vending Logs

| Field         | Description |
| ------------- | ----------- |
| `buyer_name`  | Buyer name  |
| `seller_name` | Seller name |

### Container Logs

| Field            | Description                     |
| ---------------- | ------------------------------- |
| `player_name`    | Player who opened the container |
| `container_name` | Container name                  |
| `container_id`   | Container ID                    |
| `item_name`      | Item received                   |
| `item_id`        | Item ID                         |
| `amount`         | Quantity received               |

### Player Login & Logout

| Field         | Description |
| ------------- | ----------- |
| `player_name` | Player name |

### Population Stats

| Field         | Description          |
| ------------- | -------------------- |
| `total`       | Total players online |
| `active`      | Active players       |
| `vending`     | Vending players      |
| `autotrade`   | Autotrade players    |
| `buyingstore` | Buying store players |
| `gm`          | GMs online           |

### Vending Items

| Field         | Description  |
| ------------- | ------------ |
| `player_name` | Seller name  |
| `pos`         | Map location |

***

## When a Channel Gets Deleted

If someone deletes a Discord channel that one of your routes points to, RagnaSync notices and marks it as deleted. In the route list, that route's channel name shows struck-through in red with a **"Discord channel deleted — pick a new one"** note.

Events aimed at a deleted channel are quietly skipped — no retry storms, no Discord errors. The route just sits there waiting for you to decide what to do.

You have two ways to clean it up:

* **Delete the route** — click the trash icon. This is the right move if you don't need that event type going anywhere.
* **Add a new route** — click **Add Route** on the same event and pick a live channel. Then delete the old orphaned one.

{% hint style="info" %}
We don't delete the route for you automatically. Your filters, templates, and conditions often took real thought to set up — if the deletion was a mistake, you can still repoint the route by creating a new one on a live channel and copying your setup over.
{% endhint %}

***

## Example Setups

### MVP-Only Drop Channel

Send only MVP card drops to a special announcement channel:

1. Add a route for **Item Drops** → `#mvp-drops`
2. Set format to **Custom Message**
3. Set template: `:trophy: {{player_name}} got **{{item_name}}** from {{mob_name}}!`
4. Add condition: `rate_raw <= 100`

### Separate Channels for Success/Failure Refines

Route successful refines to one channel and failures to another:

1. Route 1: **Refine Logs** → `#refine-success`, condition: `result = success`
2. Route 2: **Refine Logs** → `#refine-rip`, condition: `result = failure`

### High-Value Vending Alerts

Only log transactions from specific sellers:

1. Route for **Vending Logs** → `#vending-alerts`
2. Add condition: `seller_name = YourMerchant`

### Login/Logout Notifications

Send player connection notifications to a dedicated channel:

1. Add a route for **Player Login** → `#player-activity`
2. Set format to **Custom Message**
3. Set template: `:green_circle: {{player_name}} has logged in!`
4. Repeat for **Player Logout** with: `:red_circle: {{player_name}} has logged out.`


# Message Templates

Send pre-formatted messages from your game server to Discord using the src\_template placeholder

Message templates let your game server send its own pre-formatted messages to Discord. Instead of relying on RagnaSync's built-in formatting, your server controls the exact text that appears.

This is especially useful when your server has information that RagnaSync doesn't know about — like map names, party details, or item categories.

## How It Works

1. Your game server adds a `[template: ...]` field to its RagnaScript packet
2. RagnaSync captures the template text and makes it available as `{{src_template}}`
3. On your dashboard, set a route's format to **Custom Message** and use `{{src_template}}` as the template

That's it — whatever your server puts in the `[template: ...]` field shows up in Discord.

## Quick Setup

### Step 1: Add the Template Field to Your Server

In your game server's source code, append a `[template: ...]` field to the RagnaScript message:

```c
sprintf(buf, "[action: mob_item_drop][channel: %s][player_name: %s][mob_name: %s]"
    "[mob_id: %d][item_name: %s][item_id: %d][rate: %d]"
    "[account_id: %d][char_id: %d]"
    "[template: :gem: [Card Drop] %s obtained %s's %s at %s.]",
    channel_id, sd->status.name, mob_name,
    mob_id, item_name, item_id, rate,
    sd->status.account_id, sd->status.char_id,
    sd->status.name, mob_name, item_name, map_name);
```

The template field is **optional** — messages without it work exactly as before.

### Step 2: Configure the Route on Your Dashboard

1. Go to **Discord > Message Routes** on your RagnaSync dashboard
2. Find the event type (e.g., Mob Item Drops) and edit the route
3. Set the format to **Custom Message**
4. In the template editor, type: `{{src_template}}`
5. Save

### Step 3: That's It

Your Discord messages will now show whatever your server puts in the template field:

```
💎 [Card Drop] Seiya obtained Matyr's Matyr Card at Morocc Sphinx B3.
```

## Supported Script Types

The `[template: ...]` field works with these script types:

| Script Type          | Action           | Use Case                    |
| -------------------- | ---------------- | --------------------------- |
| Mob Item Drop        | `mob_item_drop`  | Item drop announcements     |
| Homunculus Item Drop | `hom_item_drop`  | Homunculus kill drops       |
| Mercenary Item Drop  | `merc_item_drop` | Mercenary kill drops        |
| Mob Death            | `mob_death`      | Boss/MVP kill announcements |
| @Command Log         | `at_command`     | GM command audit logging    |

## Mixing with Other Placeholders

You can combine `{{src_template}}` with other placeholders for hybrid messages:

```
{{src_template}} — dropped by {{mob_name}}
```

Or use `{{src_template}}` as just part of a bigger template:

```
**Server Alert** {{src_template}} ({{rate}} drop rate)
```

If your server doesn't send a template for a particular message, `{{src_template}}` resolves to an empty string — so your custom message degrades gracefully.

## Template Examples

### Card Drop Announcements

**Server-side (C):**

```c
sprintf(template, ":gem: [Card Drop] %s obtained %s's %s at %s.",
    sd->status.name, mob_name, item_name, map_name);
```

**Discord result:**

```
💎 [Card Drop] Seiya obtained Matyr's Matyr Card at Morocc Sphinx B3.
```

### Boss Kill with Party

**Server-side (C):**

```c
if (sd->status.party_id > 0) {
    sprintf(template, ":trophy: [%s] of Party [%s] killed %s at %s!",
        sd->status.name, party_name, mob_name, map_name);
} else {
    sprintf(template, ":trophy: [%s] has solo killed %s at %s!",
        sd->status.name, mob_name, map_name);
}
```

**Discord result:**

```
🏆 [xArrow] of Party [unliEDDGA] killed Eddga at Payon Forest F11!
```

### Conditional Templates by Rarity

**Server-side (C):**

```c
if (rate <= 10) {
    sprintf(template, ":tada: JACKPOT! %s got %s from %s!",
        sd->status.name, item_name, mob_name);
} else if (rate <= 100) {
    sprintf(template, ":gem: [Card Drop] %s obtained %s at %s.",
        sd->status.name, item_name, map_name);
} else {
    // No template — RagnaSync uses the dashboard format
    template[0] = '\0';
}
```

### GM Command Audit

**Server-side (C):**

```c
sprintf(template, ":small_orange_diamond: Command Log: %s | %s",
    sd->status.name, command);
```

**Dashboard template:** `{{src_template}}`

**Discord result:**

```
🔸 Command Log: AdminName | @warp prontera
```

## Using Emojis

You can use Discord emoji shortcodes in your templates — RagnaSync automatically converts them to real emoji. This works even inside code blocks where Discord normally wouldn't parse them.

| Shortcode                | Emoji | Good For     |
| ------------------------ | ----- | ------------ |
| `:gem:`                  | 💎    | Rare drops   |
| `:trophy:`               | 🏆    | Boss kills   |
| `:tada:`                 | 🎉    | Celebrations |
| `:star:`                 | ⭐     | Achievements |
| `:fire:`                 | 🔥    | Hot items    |
| `:skull:`                | 💀    | Deaths       |
| `:crossed_swords:`       | ⚔️    | Combat       |
| `:warning:`              | ⚠️    | Alerts       |
| `:small_orange_diamond:` | 🔸    | Compact logs |
| `:small_blue_diamond:`   | 🔹    | Info lines   |

## Tips

* The `[template: ...]` field should always be the **last field** in your RagnaScript packet
* Templates are **optional** — if you don't include one, the message uses whatever format is set on the dashboard route
* If a route's format is set to Log, Embed, or Narrate, the `{{src_template}}` placeholder won't be used — switch to **Custom Message** to use it
* Emoji shortcodes are case-sensitive: `:gem:` works, `:Gem:` doesn't
* Keep templates under \~2000 characters (Discord's message limit)

## Troubleshooting

**Template not showing up?**

* Make sure the route is set to **Custom Message** format on the dashboard
* Make sure the template field contains `{{src_template}}`
* Check that your server is sending the `[template: ...]` field in the RagnaScript packet

**Empty messages?**

* If `{{src_template}}` is the only thing in your custom template and the server didn't send a template field, the message will be empty. Add a fallback: `{{src_template}}{{player_name}} got {{item_name}}`

**Brackets showing wrong?**

* Player names with brackets like `[GM] Player` are fine — the parser handles this correctly as long as `[template: ...]` is the last field in the packet


# Discord Authentication

Link player accounts to Discord users for enhanced features

The `discord_auth` script allows players to link their Ragnarok Online accounts to their Discord accounts. Once linked, their in-game messages can show their Discord identity and unlock future Discord-integrated features.

## How It Works

1. **Get your code**: Use the `/authentication_code` slash command in Discord
2. **Enter in-game**: Talk to an NPC and enter your 8-digit code
3. **Done!**: Your accounts are now linked

## Discord Command

Players use the `/authentication_code` slash command in your Discord server to get their unique authentication code.

The command returns a **private message** (only visible to the user) containing their 8-digit code:

```
Your code: 12345678

Enter this code in-game to link your Ragnarok account to your Discord account.
```

Each Discord user gets a unique code that doesn't change, so they can use it again if needed.

## NPC Script Example

Here's a simple NPC that handles Discord authentication:

```c
prontera,150,180,4	script	Discord Link	4_M_SAGE_B,{
    mes "[Discord Link]";
    mes "Would you like to link your Discord account?";
    mes "Use ^0055FF/authentication_code^000000 in Discord to get your code.";
    next;

    input .@code$;

    if (.@code$ == "") {
        mes "[Discord Link]";
        mes "No code entered. Come back when you have your code!";
        close;
    }

    // Send the authentication request to RagnaSync
    ragnascript "[action: discord_auth][name: " + strcharinfo(PC_NAME) + "][code: " + .@code$ + "][account_id: " + getcharid(CHAR_ID_ACCOUNT) + "][char_id: " + getcharid(CHAR_ID_CHAR) + "]";

    mes "[Discord Link]";
    mes "Checking your code...";
    mes "You'll receive a whisper with the result!";
    close;
}
```

## Data Fields

| Field        | Type   | Required | Description                                  |
| ------------ | ------ | -------- | -------------------------------------------- |
| `name`       | string | Yes      | Character name (for whisper response)        |
| `code`       | string | Yes      | The 8-digit authentication code from Discord |
| `account_id` | int    | Yes      | Player's account ID                          |
| `char_id`    | int    | Yes      | Character ID                                 |

## Response Messages

After processing, RagnaSync sends a whisper to the player:

**Success:**

> Successfully linked your character to Discord user YourDiscordName!

**Invalid Code:**

> Authentication failed. Invalid code: 12345678

## Notes

* Each Discord user has a unique, persistent authentication code
* The code is shown only to the Discord user (ephemeral message)
* Players can re-link at any time using the same code
* Authentication links the player's game account to their Discord identity
* Future features can leverage this linkage for enhanced Discord integration


# Character Heads

Upload custom hair and hat sprites for your server's character renderer

## Overview

The Character Head Builder allows server administrators to upload their custom Ragnarok GRF files for character head rendering in Discord embeds. This includes hairstyles, headgear/accessories, and palettes.

***

## Required Preparation

Before uploading, you must create a GRF file containing your custom sprites using GRF Editor or similar tools.

**Download GRF Tools:** <https://irowiki.org/wiki/Tools>

Your GRF should contain only the custom sprites and configurations you want to use—you don't need to include the entire client.

***

## GRF Structure Requirements

Your GRF should contain files organized like this:

| Content Type  | GRF Path                                              |
| ------------- | ----------------------------------------------------- |
| Hair Sprites  | `data/sprite/ÀÎ°£Á·/¸Ó¸®Åë/³²/*.spr` / `¿©/*.spr`     |
| Hat Sprites   | `data/sprite/¾Ç¼¼»ç¸®/³²/*.spr` / `¿©/*.spr`          |
| Hat Config    | `data/luafiles514/lua files/datainfo/accessoryid.lub` |
| Hat Names     | `data/luafiles514/lua files/datainfo/accname.lub`     |
| Hair Palettes | `data/palette/¸Ó¸®/*.pal`                             |

***

## Upload Process

1. Login to the dashboard at <https://ragnasync.com/login> using your `discord_server_guid` found in your `channels.conf` file

{% hint style="danger" %}
**Warning:** Never share your `discord_server_guid` with anyone. This is your secure private key for accessing the dashboard.
{% endhint %}

2. Navigate to **Builder > Char Heads**
3. Select or drag your GRF file onto the upload area (max 500MB)
4. The system automatically extracts and processes:
   * Sprite files (.spr/.act) for hair and accessories
   * Palette files (.pal) for color variations
   * Lua configuration files for hat ID mappings
5. Once uploaded, changes take effect immediately—no restart required

***

## Automatic Configuration

When you upload a GRF file, the system automatically:

1. Parses `accessoryid.lub` to extract hat ID mappings
2. Parses `accname.lub` to extract sprite file names
3. Generates a hat library for rendering

No manual configuration is required.

***

## Lua Config Status

After uploading, the dashboard shows the status of your Lua config files:

| Status        | Meaning                                                                                                                                                                                            |
| ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Generated** | We found `accessoryid.lub` and `accname.lub` in your GRF and built a hat lookup table. Custom hats render by their item ID.                                                                        |
| **(missing)** | Your GRF didn't contain those Lua files. Sprites and palettes still load, but hats can only render if their item IDs happen to match the **default** library — any custom hats won't render by ID. |

A common reason for "(missing)" is a sprite-only GRF — e.g. a hat update GRF built by extracting just `data/sprite/...` paths. To fix, rebuild the GRF to also include:

* `data/luafiles514/lua files/datainfo/accessoryid.lub` (or `.lua`)
* `data/luafiles514/lua files/datainfo/accname.lub` (or `.lua`)

The older `data/lua files/datainfo/...` paths are also accepted.

***

## Character Head Rendering

Character heads are rendered at **40x40 pixels** and display:

* Hair style and color
* Head top accessory
* Head mid accessory
* Head bottom accessory
* GM indicator (yellow background + label) for group\_id > 5

***

## Preview

***

## Fallback Behavior

If no custom GRF is uploaded, the system uses a default sprite set with standard Ragnarok appearances. These default sprites may not match your server's custom hairstyles or headgear — to get accurate character representations, upload your server's GRF file.

{% hint style="info" %}
Without a custom GRF, character images will appear as generic Ragnarok characters that don't reflect your server's actual sprites.
{% endhint %}

### Per-slot fallback for unknown hat IDs

If a character has a `head_top`, `head_mid`, or `head_bottom` ID that isn't in your uploaded hat library (or in the default library when no GRF is uploaded), only that slot is randomized — the rest of the character renders from their real stored appearance. You'll see the correct hair, hair color, and any hat IDs the library *does* know about, with the unknown slot replaced by a random hat each render.

This usually means an item ID was assigned in-game that your GRF's `accessoryid.lub` / `accname.lub` doesn't cover. Re-uploading a GRF that includes those Lua files for the missing IDs will resolve it.

***

## Message Avatars

Not all Discord messages use character head avatars. The avatar shown depends on the message type:

| Message Type                           | Avatar                                          |
| -------------------------------------- | ----------------------------------------------- |
| Player chat messages                   | Character head (rendered from GRF data)         |
| NPC messages                           | Your server logo (set on the dashboard)         |
| System messages (drops, refines, etc.) | Your server logo, unless the route sets its own |

If you notice your server logo appears on some messages but not others, this is expected — player chat messages show the speaking character's head instead.

System messages are the exception you control: each **message route** picks its own avatar, so a monster kill or drop can show the monster's sprite instead of your logo. See [Message Routing](/ragnascript/message-routing#avatar-options).

***

## Discord Avatar Caching

Character heads are used as webhook avatars for in-game chat messages on Discord. Be aware that Discord caches avatar images aggressively:

* Avatar images are cached by Discord's CDN for extended periods
* After uploading new sprites, previously sent messages will retain their old avatars
* Only new messages will display the updated character heads
* Discord may take some time to refresh cached avatars even for new messages

There is no way to force Discord to clear its avatar cache for existing messages.

***

## Troubleshooting

| Issue                   | Solution                                                                                                                                                       |
| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Upload fails            | Ensure file is a valid .grf format and under 500MB                                                                                                             |
| Hats not showing        | Check Lua Config status on the dashboard. If it says "(missing)", your GRF needs `accessoryid.lub` and `accname.lub` in `data/luafiles514/lua files/datainfo/` |
| Wrong accessories       | Check that sprite names in accname.lub match actual file names                                                                                                 |
| Preview not updating    | Clear browser cache or wait a moment for processing                                                                                                            |
| Old avatars on messages | Discord caches avatars—only new messages will show updated sprites                                                                                             |


# Embed Studio

Compose rich Discord embeds, polls, and link buttons from the dashboard and post them to your server

## Overview

Embed Studio lets you hand-craft a polished Discord message — colorful embeds, polls, and link buttons — straight from your dashboard and post it to any channel in your server.

Normally only bots can send those nice styled "embed" cards. Embed Studio does it for you, so your announcements, rules, and server-info posts look clean and readable instead of being a plain wall of text. You can post a message, come back later and edit it in place, or delete it — all without touching Discord's developer tools.

***

## Opening Embed Studio

1. Log in to the dashboard at <https://ragnasync.com/login> using your `discord_server_guid` (found in your `channels.conf` file).

{% hint style="danger" %}
**Keep your `discord_server_guid` private.** It's the key to your dashboard — never share it.
{% endhint %}

2. In the sidebar, go to **Discord > Embed Studio**.
3. Click **New message** to start composing. Your saved and posted messages also live here so you can reopen them anytime.

***

## What you can build

A single message can include any combination of:

| Part                       | What it's for                                                                 |
| -------------------------- | ----------------------------------------------------------------------------- |
| **Content**                | The plain text that appears above your embeds. Supports Discord markdown.     |
| **Username & Avatar**      | Override the name and icon the message is posted under (just for this post).  |
| **Embeds** (up to 10)      | Colorful cards with a title, description, fields, images, author, and footer. |
| **Poll**                   | Let members vote — Discord tallies the results for you.                       |
| **Link buttons** (up to 5) | Tidy buttons that open a URL, e.g. "Join our Discord" or "Vote for us".       |

{% hint style="info" %}
A Discord message can have **either a poll or embeds**, not both at once. If you add a poll, keep that message poll-only.
{% endhint %}

***

## Composing a message

### Pick a channel

Choose the channel this message will be posted to from the **Channel** dropdown. If that channel has never been used by RagnaSync before, the first post sets it up automatically — just give it a few seconds.

### Write content with the formatting toolbar

Type your message in the **Message content** box. You don't need to remember Discord's markdown — select your text and use the toolbar:

| Button | Result                                   |
| ------ | ---------------------------------------- |
| **B**  | **Bold**                                 |
| *I*    | *Italic*                                 |
| U      | Underline                                |
| S      | ~~Strikethrough~~                        |
| `</>`  | `Inline code`                            |
| Link   | Turn the selection into a clickable link |

The same toolbar appears on embed descriptions and field values.

### Add an embed

Click **Add embed** to get a styled card, then fill in any of:

* **Color** — the accent bar down the left side (use the color picker)
* **Author** — a small name line at the top
* **Title** — bold heading (add a URL to make it a clickable link)
* **Description** — the main body (markdown supported)
* **Image / Thumbnail** — paste an image URL; the thumbnail sits top-right, the image spans the bottom
* **Footer** — small text at the bottom

#### Fields

Fields are labelled mini-sections inside an embed (a **name** and a **value**). Use the **Inline** toggle to place a field side-by-side with its neighbours — Discord fits up to three inline fields per row. Leave it off for a full-width field on its own line.

### Add a poll

Click **Add poll**, write your question, and add 2–10 answers. Set how long voting stays open (in hours) and whether members can pick more than one answer. Discord handles the voting and shows the results.

{% hint style="info" %}
A posted poll **can't be edited**. To change it, delete the message and post a new one.
{% endhint %}

### Add link buttons

Click **Add button** and give each one a label and a URL. These appear as buttons under your message and simply open the link when clicked — perfect for "Website", "Registration", or "Donate".

***

## Live preview

The panel on the right shows exactly how your message will look in Discord, updating as you type — markdown, embed colors, inline fields, poll options, and buttons all included. Use it to get things right before you post.

***

## Posting, editing, and deleting

* **Post to Discord** — sends the message to the chosen channel. You'll be taken back to your Embed Studio list, where it shows as **Posted**.
* **Save draft** — keeps your work without posting. Drafts show in the list so you can finish them later.
* **Edit** — open a posted message from the list, change it, and click **Update message**. The message updates in place in Discord (it'll show "(edited)").
* **Delete** — removes the message from both your list and the Discord channel.

{% hint style="info" %}
Each message remembers how it was posted, so editing and deleting always affect the right Discord message — even weeks later.
{% endhint %}

***

## Tips

* Build big "server information" posts as several embeds in one message — one per section (Rates, Features, Links).
* Put your links in an embed field using markdown links, e.g. `[Website](https://your-site.com)`, to get a tidy clickable list.
* Use the live preview to check spacing and inline fields before posting — it matches Discord closely.


# Price Check

Look up live item prices and price history from Discord

`/price-check` lets anyone in your Discord look up an item's market price — current vend listings, recent sale averages, and a 30-day price chart — without leaving Discord. It reads your server's own **Vending Logs**, so the numbers are your real marketplace, not an outside estimate.

## Usage

```
/price-check item:<name or ID>
```

Examples:

```
/price-check item:Red Potion
/price-check item:501
```

* **Item ID** (numeric) matches exactly.
* **Item name** (text) is matched on a fuzzy partial basis — if several items match, the most-traded one is used.
* The reply is **public**, so the whole channel can see the price.

## What it shows

| Section               | Source               | Details                                                 |
| --------------------- | -------------------- | ------------------------------------------------------- |
| **Currently vending** | active vend listings | Lowest asking price and number of live listings.        |
| **Recent sales**      | completed sales      | 7-day and 30-day average, price range, and number sold. |
| **Price chart**       | completed sales      | A 30-day price-over-time graph.                         |

## Requirement

Price Check is built on your **Vending Logs**, so vending logging must be enabled in `conf/channels.conf`:

```
ragnasync_vending_log_enabled: true
```

See [Vending Logs](/event-logs/economy/vending-log). Until a server has vending activity, the command will reply that there's no price data yet.

## Notes

* Prices are **your server's own** marketplace data — not a global or third-party estimate.
* The chart covers the **last 30 days**; vending history is retained, so coverage builds up the longer logging has been on.
* Item-name lookups resolve against items that have actually been traded on your server.


# Tickets

In-game support tickets with Discord integration

Let players open support tickets from in-game that create private Discord channels for your staff to respond.

***

## How It Works

```
Player opens ticket → Private Discord channel created → Staff replies → Player receives whisper
```

1. **Player sends a ticket message** using the `ticket` action
2. **RagnaSync creates a private channel** named `ticket-{playername}` in your Discord
3. **Staff type in the channel** and their messages whisper directly to the player
4. **Ticket closes** when the player logs out or staff uses `/close_ticket`

***

## Setting Up Ticket Support

### Create the Support NPC

Place this NPC where players can easily find it:

```c
prontera,156,180,4	script	Support	100,{
    mes "[Support]";
    mes "Need help? Open a ticket and staff will respond.";
    mes " ";
    mes "Your messages go directly to our Discord support team.";
    next;

    if (select("Open/Send Ticket:Cancel") == 2) close;

    mes "[Support]";
    mes "Type your message:";
    next;

    input .@message$;

    if (.@message$ == "") {
        mes "[Support]";
        mes "You didn't enter a message.";
        close;
    }

    // Send ticket message
    ragnascript("[action: ticket][account_id: " + getcharid(3) + "][char_id: " + getcharid(0) + "][name: " + strcharinfo(0) + "][message: " + .@message$ + "]");

    mes "[Support]";
    mes "Your message has been sent!";
    mes " ";
    mes "Staff will reply via whisper. Keep an eye on your chat.";
    close;
}
```

***

## What Happens in Discord

When a player sends their first ticket message:

1. A new channel `#ticket-playername` appears in your Discord server
2. The channel is created inside a category (default: **Tickets**)
3. The channel is private (only visible to staff with the right permissions)
4. Staff see a welcome message with the player's name
5. The player's message appears in the channel

{% hint style="info" %}
You can change the category name (up to 100 characters — e.g. "Support" or "Player Help") on the **Configure** page in your dashboard, under the **Ragnarok** section. If the category doesn't exist yet, RagnaSync creates it automatically.
{% endhint %}

The channel topic reads *"Type here to reply to PlayerName in-game"* — it's descriptive text only. You can edit or translate it without breaking anything; routing is handled automatically behind the scenes.

**Example:**

```
╔══════════════════════════════════╗
║     Support Ticket Opened        ║
╠══════════════════════════════════╣
║  Player: Zeny_Master             ║
║                                  ║
║  Type here to reply to the       ║
║  player in-game.                 ║
╚══════════════════════════════════╝

Zeny_Master: Hi, I lost my items after the server crash yesterday. Can you help?
```

***

## Replying to Players

Simply type in the ticket channel. Your message will be whispered to the player in-game:

| Discord                          | In-Game                                                   |
| -------------------------------- | --------------------------------------------------------- |
| `Have you checked your storage?` | Player sees: `@StaffName: Have you checked your storage?` |

The player can reply by talking to the Support NPC again - their messages appear in the same Discord channel.

***

## Closing Tickets

Tickets close automatically when:

* **Player logs out** - Channel is deleted after a brief moment
* **Staff uses `/close_ticket`** - Type this command in the ticket channel

When a ticket closes:

1. A "Ticket Closed" message appears in the channel
2. The Discord channel is deleted after 2 seconds
3. Database records are cleaned up

***

## Multiple Messages

Players can send multiple messages to the same ticket. Each message from the NPC goes to the existing ticket channel:

```c
// First message creates the ticket
ragnascript("[action: ticket][account_id: ...][char_id: ...][name: PlayerName][message: First message]");

// Second message goes to the same channel
ragnascript("[action: ticket][account_id: ...][char_id: ...][name: PlayerName][message: Follow-up message]");
```

***

## RagnaScript Format

```
[action: ticket][account_id: X][char_id: X][name: X][message: X]
```

| Field        | Required | Description                              |
| ------------ | -------- | ---------------------------------------- |
| `action`     | Yes      | Must be `ticket`                         |
| `account_id` | Yes      | Player's account ID                      |
| `char_id`    | Yes      | Player's character ID                    |
| `name`       | Yes      | Character name (used for channel naming) |
| `message`    | Yes      | The support message (max 500 characters) |

***

## Tips

**Channel naming** - Ticket channels are named `ticket-{name}` in lowercase. Special characters in names are handled automatically.

**Staff permissions** - Make sure your support staff have permission to see the ticket channels in Discord.

**Rate limiting** - Consider adding a cooldown to prevent ticket spam:

```c
if (gettimetick(2) - @last_ticket < 30) {
    mes "Please wait before sending another message.";
    close;
}
@last_ticket = gettimetick(2);
```

**Offline players** - If a player logs out and staff replies, the message won't be delivered. The ticket channel will already be closed by then.

***

## Quick Reference

| Action                     | What Happens                        |
| -------------------------- | ----------------------------------- |
| Player sends first ticket  | New `#ticket-name` channel created  |
| Player sends more messages | Messages appear in existing channel |
| Staff types in channel     | Player receives whisper in-game     |
| Player logs out            | Ticket channel deleted              |
| Staff uses `/close_ticket` | Ticket channel deleted              |

***

Need help with other features? Check out [RagnaScript Overview](/ragnascript/scripts).


# Support Command

In-game support tickets via @request command or PM @Support with Discord integration

Players can open support tickets from in-game using the `@request` command or by sending a PM to `@Support`. Tickets create private Discord channels where staff can respond in real-time.

***

## Feature Overview

* Players use `@request <message>` command or PM `@Support` to create support tickets
* Tickets are created as Discord channels named `ticket-<playername>`
* Messages are relayed between in-game and Discord bi-directionally
* Tickets can optionally auto-close when the player logs out

***

## Configuration

In `conf/channels.conf`:

```conf
// Ticket Support System (PM @Support or @request command)
ragnasync_ticket_support_system_enabled: true
ragnasync_ticket_support_system_close_on_logout: true
```

| Setting                                           | Default | Description                              |
| ------------------------------------------------- | ------- | ---------------------------------------- |
| `ragnasync_ticket_support_system_enabled`         | `false` | Enable/disable the ticket support system |
| `ragnasync_ticket_support_system_close_on_logout` | `false` | Auto-close ticket when player logs out   |

Both keys default to `false` when absent from `channels.conf` — add them explicitly to turn the feature on.

***

## How It Works

```
Player sends message → RagnaSync creates Discord channel → Staff replies → Player receives whisper
```

1. **Player uses `@request <message>` or PMs `@Support` with a message**
2. **XileRO server sends `[action: ticket]` to RagnaSync**
3. **RagnaSync creates a Discord channel named `ticket-<playername>`**
4. **Player receives whisper:** "Ticket Created on Discord. PM @Support to use. (Sent: your message)"
5. **Subsequent messages via PM @Support are relayed to the Discord ticket channel**
6. **When player logs out**, if `close_on_logout` is enabled, the ticket channel is closed

***

## Player Messages

Players will see different messages depending on the system state:

| Scenario                       | Message                                                                 |
| ------------------------------ | ----------------------------------------------------------------------- |
| Ticket created via @request    | `[RagnaSync Ticket]: <your message>`                                    |
| Ticket created via PM @Support | `Ticket Created on Discord. PM @Support to use. (Sent: <your message>)` |
| RagnaSync not connected        | `RagnaSync: Ticket Offline, Try again`                                  |
| Feature disabled               | `RagnaSync Ticket Support Disabled`                                     |

The `@request` command echoes your message back so you can confirm what was sent.

***

## In-Game Usage

Players can use either method to create or interact with tickets:

### Using @request Command

```
@request I need help with my account
```

You'll see your message echoed back:

```
[RagnaSync Ticket]: I need help with my account
```

### Using PM @Support

```
/w @Support I have a question about donations
```

You'll receive a whisper confirmation:

```
@Support: Ticket Created on Discord. PM @Support to use. (Sent: I have a question about donations)
```

Continue the conversation by PMing `@Support` again.

***

## Staff Response

When staff type in a ticket channel:

1. Their message is whispered directly to the player in-game
2. The player can reply by PMing `@Support` again
3. All subsequent messages appear in the same Discord ticket channel

***

## Tips

**Channel naming** - Ticket channels are named `ticket-{name}` in lowercase. Special characters in player names are handled automatically.

**Staff permissions** - Ensure support staff have permission to view and type in ticket channels in Discord.

**Response time** - Configure Discord notifications for ticket channels to ensure timely responses.

**Offline players** - If `close_on_logout` is disabled, staff can still leave messages in the ticket channel that the player will see when they return.

***

## Quick Reference

| Action                                   | What Happens                        |
| ---------------------------------------- | ----------------------------------- |
| Player uses @request or PMs @Support     | New `#ticket-name` channel created  |
| Player sends more messages via @Support  | Messages appear in existing channel |
| Staff types in ticket channel            | Player receives whisper in-game     |
| Player logs out (with close\_on\_logout) | Ticket channel deleted              |

***

Need help with other features? Check out [RagnaScript Overview](/ragnascript/scripts).


# Guild of the Month

Track guild performance during War of Emperium and crown your Guild of the Month

The Guild of the Month system automatically tracks what happens during your War of Emperium sessions — castle captures, player kills, guild attendance — and turns it into a scored leaderboard posted to Discord. Think of it as an automated scoreboard for your WoE events.

## How It Works

```
WoE Starts → Events are logged → WoE Ends → Scores calculated → Leaderboard posted to Discord
```

Every time WoE runs on your server, RagnaSync quietly records what happens. When the session ends, it crunches the numbers and posts a leaderboard to your chosen Discord channel. No manual work needed — just configure it once and let it run.

## What Gets Tracked

| Event                | What It Records                                       |
| -------------------- | ----------------------------------------------------- |
| **Castle Captures**  | Which guild broke in first, who captured the castle   |
| **Player Kills**     | PvP kills during WoE, tracked per guild               |
| **Attendance**       | How many guild members are present on each castle map |
| **Castle Ownership** | Which guild holds the castle when WoE ends            |
| **Hold Duration**    | How long each guild controlled the castle             |

{% hint style="info" %}
You can toggle each event type individually. If your server doesn't care about kill tracking, just turn it off — the scoring engine handles missing event types gracefully.
{% endhint %}

{% hint style="info" %}
**Quiet castles are skipped.** `@agit_start` spins up every castle on your server, but only the ones players actually contested get a leaderboard embed. If no guild attends, captures, or fights on a castle during the session, RagnaSync silently skips it — no Discord spam and no ghost "Castle Owner" points for whoever happened to be sitting on it.
{% endhint %}

## Scoring

Guilds earn points based on their performance each session. These points add up across the season to determine the Guild of the Month.

| Award           | Default Points | How It's Earned                          |
| --------------- | -------------- | ---------------------------------------- |
| Castle Owner    | 3              | Holding the castle when WoE ends         |
| Longest Defense | 3              | Longest total time controlling a castle  |
| First Break     | 2              | First guild to capture the castle        |
| Attendance      | 1              | Having enough members present during WoE |
| Most Kills      | 1              | Highest guild PvP kill count             |

All point values are fully configurable from your dashboard — adjust them to match what matters on your server.

## Seasons

Scores accumulate within a **season**. When a new season starts, the leaderboard resets and guilds compete fresh. You can manage seasons in a few ways:

| Mode         | How It Works                                               |
| ------------ | ---------------------------------------------------------- |
| **Manual**   | You start new seasons from the dashboard whenever you want |
| **Weekly**   | Season automatically resets every week                     |
| **Biweekly** | Season resets every two weeks                              |
| **Monthly**  | Season resets on the first of each month                   |

When an automatic rotation happens, RagnaSync posts a notification to your GOTM Discord channel announcing the new season.

## Advanced WOE Events

By default, every castle on your server counts toward a single GOTM leaderboard. That works great for most servers. But sometimes you want more control — maybe only *one* of the weekly WoEs should count, or you want a special Christmas event running alongside the regular GOTM. That's what **Advanced WOE Events** is for.

When you turn on advanced mode, you can create multiple **WOE Events** side by side. Each event is its own self-contained scoring scope with its own castle set, date range, scoring rules, and Discord channel.

### Enabling Advanced Mode

On the **Dashboard > Ragnarok > WoE Events** page, click **GOTM Settings** in the header and flip **Advanced WoE Events** on. The **New WoE Event** button appears once the setting is saved.

{% hint style="info" %}
Turning advanced mode off won't delete anything — your custom WoE Events are just hidden and stop scoring. The default catch-all scoring resumes. Turn advanced back on and your events come right back.
{% endhint %}

### Creating a WoE Event

Click **New WoE Event** to open the setup form. Fields are grouped into sections on a single page:

**Event**

* **Name** — what you'll call this event (e.g., "Friday GOTM", "Christmas Event")
* **Active** — an emergency off switch that bypasses dates

**Season Counter**

* **Season** — the starting season number (usually `1`). Read-only after creation; use the per-row **Bump Season** action to advance it manually.
* **Rollover** — when the season counter advances (see below)

**Active Window**

* **Starts At** / **Ends At** — optional date bounds. Leave both blank for an event that runs forever; set one or both to restrict when the event scores. Useful for one-off tournaments.

**Castles**

* Multi-select of the castles this event scores. A WoE on a castle that isn't in your event's castle list won't contribute points to this event.

**Scoring Overrides** (optional)

* Five point values that match the defaults from GOTM Settings: Castle Owner, Longest Held, First Break, Attended, Most Kills.
* Leave any field blank to inherit the default. Fill any in to override just for this event.

**Scoring Eligibility Filter** (optional)

* A rule a WoE session must pass before it scores for this event. Leave it empty and every active session scores, exactly as before. See [Scoring Eligibility Filter](#scoring-eligibility-filter) below.

**Discord Channel**

* Where this event's leaderboard embed posts. Each event can have its own dedicated channel. Leave blank to use the WoeScript default.

### Rollover Modes

Each WOE Event controls how its season counter advances:

| Mode                  | How It Works                                                                                      |
| --------------------- | ------------------------------------------------------------------------------------------------- |
| **None**              | Season counter never changes. Good for "forever" events.                                          |
| **Manual**            | You advance the season from the dashboard with the **Bump WOE Event Season** button.              |
| **Monthly Increment** | On the first of each month, `season += 1`. This is the default — matches the classic GOTM rhythm. |
| **Calendar Month**    | Season equals the current calendar month (1–12). Re-syncs naturally Jan→Dec.                      |

### Concurrent Events

A castle can belong to multiple active events. When WoE ends on a shared castle, each event that includes it produces an **independent** score row. So a capture in your regular monthly GOTM and a parallel Christmas Event both light up their own leaderboards separately.

### Bumping a Manual Season

If an event's rollover is set to **Manual**, click the **Bump Season** action on the event's row in the WoE Events list. Confirm, and its season jumps by 1. Past scores stay under the previous season for historical leaderboards.

### Scoring Eligibility Filter

Ever had one AFK player sitting on an empty castle hand their guild "Castle Owner" points week after week? The eligibility filter fixes that. It's an optional rule on each WoE Event: if a session doesn't pass, that session simply doesn't count for the event — no points, no leaderboard, no Discord post. Sessions that pass score exactly as normal.

You'll find it in the event form, in the **Scoring Eligibility Filter** section. A filter is built from **condition groups**:

* Conditions **within a group** must *all* match (AND).
* Separate groups are alternatives — *any* group matching is enough (OR).

Each condition compares one session stat against a number:

| Metric                    | What It Measures                                                                                                                                                            |
| ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Peak Guild Attendance** | The largest headcount any *single guild* had in one attendance snapshot. A 5v5 fight reads as `5`, not 10 — it's per guild, the same way the Attendance award counts heads. |
| **Captures**              | How many times the castle changed hands during the session                                                                                                                  |
| **Kills**                 | Total PvP kills during the session                                                                                                                                          |

Operators are the usual suspects: `≥`, `>`, `=`, `≤`, `<`.

A good starter filter is two groups, one condition each:

```
Group 1: Captures ≥ 1
   — OR —
Group 2: Kills ≥ 3
```

That reads as "the castle changed hands at least once, *or* there was real fighting." An uncontested castle with one idle defender fails both groups and quietly drops out of the leaderboard — while every contested castle scores exactly as before.

{% hint style="info" %}
**Not sure what thresholds fit your server?** Open the **Session History** tab on the WoE Dashboard. The stats block at the top shows the min / median / max of each metric across your recent sessions, and picking an event from the selector adds a live **PASS** / **FAIL** badge to every past session — so you can see exactly which sessions a filter would have excluded before you commit to it. Editing the filter updates the badges immediately; nothing is re-scored.
{% endhint %}

{% hint style="warning" %}
The filter only affects sessions processed **after** you save it. Historical scores are never recalculated — if a quiet castle already earned points last week, those points stand. Fix forward, not backward.
{% endhint %}

Each event has its own filter, so your strict "Friday GOTM" can demand real fights while a casual parallel event accepts everything. An event with no filter behaves exactly as it always has.

## Server Configuration

Add these settings to the `discord: { }` block in your `conf/channels.conf`:

```
// Guild of the Month (GOTM) WoE event logging
ragnasync_gotm_enabled: true
ragnasync_gotm_channel_id: "YOUR_CHANNEL_ID"
ragnasync_gotm_capture_events: true
ragnasync_gotm_player_kill_events: true
ragnasync_gotm_attendance_events: true
ragnasync_gotm_attendance_interval: 60
```

| Setting                              | Default | Description                                      |
| ------------------------------------ | ------- | ------------------------------------------------ |
| `ragnasync_gotm_enabled`             | `false` | Turn GOTM event logging on or off                |
| `ragnasync_gotm_channel_id`          | `""`    | Discord channel ID where leaderboards are posted |
| `ragnasync_gotm_capture_events`      | `true`  | Log castle capture events                        |
| `ragnasync_gotm_player_kill_events`  | `true`  | Log player kills during WoE                      |
| `ragnasync_gotm_attendance_events`   | `true`  | Take attendance snapshots during WoE             |
| `ragnasync_gotm_attendance_interval` | `60`    | Seconds between attendance snapshots             |

{% hint style="warning" %}
The `ragnasync_gotm_channel_id` setting syncs to your RagnaSync dashboard automatically when your server connects. You can also set or override it from the dashboard's WoE Events page.
{% endhint %}

## Dashboard

The GOTM UI is split across two pages under **Dashboard > Ragnarok**:

### WoE Events — manage your events

The primary page for creating and configuring events. Shows a table of all WoE Events for your client with their season, rollover mode, active state, date range, castle count, and Discord channel.

Header actions:

* **New WoE Event** — opens the create form (only visible when Advanced mode is on).
* **GOTM Settings** — modal containing the master GOTM on/off toggle, Advanced mode toggle, default point values, attendance threshold, and legacy fallback settings.
* **Start New Season** — bumps the client-level season counter (legacy scoring path only).
* **Reset Season Scores** — permanently deletes score rows for the current season.

Per-row actions:

* **Bump Season** — advances the event's season counter (only shown for Manual rollover events).
* **Edit** — opens the full event form.
* **Delete** — soft-deletes the event (never available on the Default event).

Click any row to jump to its edit page.

### WoE Dashboard — view past sessions and leaderboards

Read-only page with two tabs:

**Session History** — Browse your most recent WoE sessions as expandable cards, with a stats block up top summarizing them:

* **Min · median · max** for Peak Guild Attendance, Captures, and Kills across the listed sessions
* An **Uncontested** count — sessions with zero captures *and* zero kills

Each session card shows the **date and time**, the **castle**, and the **season**. Click one to expand it and see:

* **Duration** of the session
* Which guild **owned the castle** at the end
* **Peak Guild Attendance**, **Captures**, and **Kills**

There's also an event selector at the top of the tab. Pick a WoE Event and every session gets a live **PASS** / **FAIL** badge showing whether it would clear that event's [scoring eligibility filter](#scoring-eligibility-filter) — the easiest way to tune thresholds against your server's real history. Sessions on castles outside the event's scope (and events with no filter) show no badge.

**Leaderboard** — View season rankings with selectors for both the **WoE Event** and the **Season**:

* **WoE Event selector** — pick which event's leaderboard to view. Defaults to your Default event. Choose "— Legacy (no event) —" to see pre-advanced historical scores.
* **Season selector** — browse past seasons of the selected event.
* **Global rankings** — total scores across all castles, with last session points.
* **Per-castle breakdown** — expandable details showing how guilds performed on each individual castle.

## Discord Leaderboard

After each WoE session, RagnaSync posts an embed to your configured Discord channel that looks something like this:

```
🏰 Prontera Castle WOE Session Complete
📊 Event Highlights:
• Duration: 1h 0m
• Participating Guilds: 4
• Total Events: 87 (52 kills, 3 captures, 4 guilds attended)
• Castle Changed Hands: Yes

🏆 Award Winners:
👑 Castle Owner: DragonKnights (+3 XP)
🛡️ Longest Defense: PhoenixOrder (+3 XP)
⚔️ First Break: ShadowRealm (+2 XP)
💀 Most Kills: DragonKnights (+1 XP)
🎯 Attendance: 4 Guilds (+1 XP each)

📋 Prontera Castle Leaderboard:
🥇 DragonKnights - 28 XP (+4 this session)
🥈 PhoenixOrder - 24 XP (+3 this session)
🥉 ShadowRealm - 18 XP (+2 this session)

🌍 Season 1 Global Leaderboard (All Castles):
🥇 DragonKnights - 52 XP
🥈 PhoenixOrder - 41 XP
🥉 ShadowRealm - 33 XP
```

## Example Use Cases

### Competitive WoE Server

Run weekly seasons with high point values. Your guilds will fight hard knowing the leaderboard resets every Monday:

```
ragnasync_gotm_enabled: true
ragnasync_gotm_channel_id: "YOUR_CHANNEL_ID"
```

Then set **Season Rotation** to **Weekly** on the dashboard.

### Casual Server

Use monthly seasons and only track castle ownership — skip the kill tracking if PvP isn't the focus:

```
ragnasync_gotm_enabled: true
ragnasync_gotm_channel_id: "YOUR_CHANNEL_ID"
ragnasync_gotm_player_kill_events: false
```

### Tournament Events

Keep seasons on **Manual** and start a new one for each tournament. Reset scores between events from the dashboard.

### Only Score One WoE Per Week (advanced)

Your active guild wants the Friday castle to decide GOTM — not Tuesday or Wednesday. Turn on advanced mode, create a WOE Event named "Friday GOTM" with **Monthly Increment** rollover, and attach *only* the castle that runs on Friday. Every other WoE is still recorded in your session history, but only Friday captures feed the leaderboard.

### Parallel Special Event (advanced)

You want to run a Christmas Event alongside your regular monthly GOTM. Both should score the same castles, but the Christmas Event has bigger point rewards. Create a new WOE Event named "Christmas 2026", set its **Starts At** to Dec 20 and **Ends At** to Jan 2, attach the same castles as your default event, override the scoring (e.g., Castle Owner = 10 instead of 3), and set a different **Discord Channel** so the Christmas leaderboard posts separately.

Both events score every WoE during the window — your normal GOTM keeps running, and the Christmas leaderboard lights up in its own channel.

## Notes

* GOTM works with any WoE schedule your server defines — RagnaSync doesn't control when WoE runs, it just listens for events
* Multiple castles running simultaneously are tracked independently
* If no captures or kills happen during a session (low population), the castle owner still gets their points — unless the event has a [scoring eligibility filter](#scoring-eligibility-filter) that excludes such sessions
* Older RagnaSync client versions without the GOTM channel setting will still work — just configure the channel from the dashboard instead


# Status Page

Give your players a public page to track your server's uptime

The **Status Page** is a public web page you can hand to your players so they can check whether your Ragnarok server is online — without asking in Discord. You control whether it's on, what it shows, and what its link looks like.

You'll find it under the **Ragnarok** section in your dashboard navigation, labelled **Status Page**.

***

## Turning it on

1. Open **Status Page** in your dashboard.
2. Flip on **Enable public status page**.
3. Pick a **public URL slug** — this is the memorable part of your link. Letters, numbers and dashes only.
4. Save.

Your page is now live at:

```
https://ragnasync.com/status/your-slug
```

For example, a slug of `your-server` gives you `https://ragnasync.com/status/your-server`. Share that link anywhere — your Discord, your website, your forums.

{% hint style="info" %}
Use the **View public page** button at the top of the settings page to see exactly what your players see.
{% endhint %}

The page stands on its own — your players see **your** server, not RagnaSync. There's no RagnaSync navigation or marketing around it, just a small "Powered by RagnaSync" line at the bottom.

***

## Branding

Make the page yours under the **Branding** section:

| Option           | What it does                                                        |
| ---------------- | ------------------------------------------------------------------- |
| **Page title**   | The heading at the top. Defaults to your server name.               |
| **Tagline**      | An optional line under the title (e.g. "High-rate PvP server").     |
| **Accent color** | Colors the top strip and the population graph to match your server. |

***

## What the page shows

You decide which sections appear. Toggle any of these on or off under **Page Elements**:

| Element                       | What players see                                                                               |
| ----------------------------- | ---------------------------------------------------------------------------------------------- |
| **Live server status**        | Whether your server is up right now, refreshed automatically.                                  |
| **90-hour uptime trend**      | A row of hourly bars showing uptime over the last 90 hours, plus an overall uptime percentage. |
| **Player population graph**   | Online player counts over the last 24 hours.                                                   |
| **Incidents & announcements** | Any maintenance notices or issues you've posted.                                               |

### Live status — with or without the monitoring tool

How detailed the live status is depends on whether you've installed the **monitoring tool** on your server:

* **With the monitoring tool** — the page breaks status down per server: **Login**, **Char**, **Map** and **Web**, each shown individually.
* **Without it** — the page shows a single **Server** status based on whether your server is connected to RagnaSync.

Either way, the page works out of the box. Installing the monitoring tool just gives your players more detail.

{% hint style="info" %}
RagnaSync updates and deployments won't show up as downtime on your page. If we can't reliably observe your server during one of our own maintenance windows, that time is simply skipped rather than counted against you.
{% endhint %}

***

## Showing multiple servers

If you run more than one server on RagnaSync (for example a **Live** and a **Test** server), each one appears as its own block on the page.

Under **Visible Servers**, toggle which of your servers show up. Hide your test server and players will only ever see your live one.

***

## Posting incidents

Planning maintenance, or dealing with an outage? Keep your players in the loop:

1. On the **Status Page** settings, click **Post incident**.
2. Give it a **title** (e.g. "Scheduled maintenance tonight") and optional **details**.
3. Post it — it appears immediately under **Active incidents** on your public page.

When it's over, click **Resolve** next to the incident. Resolved incidents move to a **Past incidents** list for 14 days, then drop off automatically.


# Webhooks

How RagnaSync delivers messages to Discord — and what to do if one breaks

RagnaSync delivers messages to your Discord channels using **webhooks**. The good news: you almost never have to think about them. RagnaSync creates and manages them for you. This page explains what's happening behind the scenes, for the rare moment a channel stops receiving messages.

***

## How delivery works

When you bridge a Discord channel (by setting its `discord_channel_id` in [`channels.conf`](/getting-started/channels-conf) or your [message routes](/ragnascript/message-routing)), RagnaSync automatically creates a webhook on that channel the first time it needs to post there.

From then on, every message destined for that channel — chat, announcements, drop logs, WoE events — is delivered through its webhook. This is what lets messages appear with custom names and avatars (like a player's character) rather than as the bot.

{% hint style="info" %}
You don't create webhooks by hand. Bridging a channel is all you do — RagnaSync handles the webhook itself.
{% endhint %}

***

## Self-healing

Webhooks can break if someone deletes one in Discord, or if a channel is removed and recreated. RagnaSync is built to recover on its own:

* If a webhook goes missing or stops working, RagnaSync **recreates it automatically** and resends the messages that were waiting.
* Messages are **buffered, not dropped**, while a webhook is being recreated — so a brief hiccup doesn't lose announcements.

In practice this means a deleted webhook fixes itself within moments, with no action from you.

***

## If a channel stops receiving messages

If one channel has gone quiet while others work, run through these:

1. **Check the channel is still bridged** — confirm its `discord_channel_id` is correct in your config or [message routes](/ragnascript/message-routing).
2. **Check the bot's permissions** — the RagnaSync bot needs **Manage Webhooks** on that channel to create and use webhooks.
3. **Confirm the channel still exists** — if it was deleted and recreated, make sure the new channel's ID is the one in your config.
4. **Check your usage** — if you've hit your monthly [event limit](/account/usage-and-billing), counted messages pause until you add credits, upgrade, or the month resets.

{% hint style="warning" %}
The most common cause is a missing **Manage Webhooks** permission. If RagnaSync can't manage a webhook on the channel, it can't deliver there — fix the permission and delivery resumes on its own.
{% endhint %}

***

Still stuck? Head to [Troubleshooting](/getting-started/troubleshooting) or reach us on Discord.


# Drops

Announce rare item drops from mobs, homunculi, and mercenaries

Celebrate lucky drops in your Discord. RagnaSync can announce rare items the moment they drop, with a minimum-rate threshold so only the good stuff gets posted.

* [Mob Drops](/event-logs/drops/mob-item-drop-log) — drops from monsters a player kills directly.
* [Homunculus Drops](/event-logs/drops/hom-item-drop-log) — drops from a player's homunculus kills.
* [Mercenary Drops](/event-logs/drops/merc-item-drop-log) — drops from a player's mercenary kills.


# Mob Drops

Logs rare item drops when a player directly kills a monster

The `MobItemDropScript` handles item drop logging when a **player directly kills** a monster.

## Action

`mob_item_drop`

## When It's Used

This script is triggered when:

* A player lands the killing blow on a monster
* The monster drops an item with a drop rate at or below the configured threshold
* Mob item drop logging is enabled in the server configuration

## Why Use It

* **Track valuable drops** from normal gameplay where players are actively hunting
* **Monitor rare card drops** across the server
* **Celebrate player achievements** by announcing rare drops to Discord
* **Audit drop rates** to ensure game balance

## Server Configuration

In `conf/channels.conf`:

```
// Player item drop logging
ragnasync_mob_item_drop_log_enabled: true
ragnasync_mob_item_drop_log_channel_id: "YOUR_CHANNEL_ID"
ragnasync_mob_item_drop_log_min_rate: 100
ragnasync_mob_item_drop_log_types: "card,weapon,armor"
```

| Setting      | Description                                   | Default |
| ------------ | --------------------------------------------- | ------- |
| `enabled`    | Enable/disable this logging type              | `false` |
| `channel_id` | Discord channel ID to send messages to        | -       |
| `min_rate`   | Minimum drop rate to log (500 = 5%, 100 = 1%) | `500`   |
| `types`      | Item categories to log — see below            | `all`   |

### Valid `types` values

This filter matches against **item categories**, not the mob class that dropped them. Comma-separated, case-insensitive.

| Token        | Filters drops of                   |
| ------------ | ---------------------------------- |
| `all`        | Every item category (default)      |
| `healing`    | Healing items                      |
| `usable`     | Consumables / usable items         |
| `etc`        | Miscellaneous / crafting materials |
| `armor`      | Armor pieces                       |
| `weapon`     | Weapons                            |
| `card`       | Monster cards                      |
| `petegg`     | Pet eggs                           |
| `petarmor`   | Pet equipment                      |
| `ammo`       | Arrows, bullets, other ammunition  |
| `shadowgear` | Shadow equipment                   |
| `cash`       | Cash shop items                    |

{% hint style="warning" %}
`"mvp,miniboss"` is a common mistake here — those are mob classes, not item categories, so every token gets rejected. If your filter produces `[Warning]: Unknown item type '...'` lines at map-server startup, check that your tokens match the table above.
{% endhint %}

{% hint style="info" %}
There's no built-in way to filter by the **mob class** that dropped the item (e.g. "only log drops from MVPs"). Use `min_rate` to narrow down rare drops, or route messages through the dashboard for mob-specific alerts.
{% endhint %}

**Drop Rate Values:**

| Value   | Rate |
| ------- | ---- |
| `10000` | 100% |
| `1000`  | 10%  |
| `500`   | 5%   |
| `100`   | 1%   |
| `10`    | 0.1% |

## Message Format

The script sends formatted messages to Discord using monospace code blocks:

**With drop rate (≤100%):**

```
💎 Item Drop:     | PlayerName       | Poring           | Apple                | 5.00%   |
```

**Without drop rate (>100%):**

```
💎 Item Drop:     | PlayerName       | Poring           | Apple                |
```

The columns are: Player, Monster, Item, and Drop Rate (hidden for common drops over 100%).

Each drop type uses a unique emoji:

* 💎 Mob drops (player kills)
* ⚔️ Mercenary drops
* 🐾 Homunculus drops

## Data Fields

| Field         | Type   | Description                               |
| ------------- | ------ | ----------------------------------------- |
| `player_name` | string | Name of the player who killed the monster |
| `mob_name`    | string | Name of the monster that was killed       |
| `mob_id`      | int    | Monster ID                                |
| `item_name`   | string | Name of the dropped item                  |
| `item_id`     | int    | Item ID                                   |
| `rate`        | float  | Drop rate (e.g., 500 = 5.00%)             |
| `account_id`  | int    | Player's account ID                       |
| `char_id`     | int    | Player's character ID                     |
| `channel`     | string | Discord channel ID                        |

## Example Use Cases

### Card Drop Alerts Only

Set a very low threshold to only log card drops:

```
ragnasync_mob_item_drop_log_min_rate: 2  // Only 0.02% or rarer
```

### All Rare Drops

Log anything 10% or rarer:

```
ragnasync_mob_item_drop_log_min_rate: 1000
```

## Custom Messages

Want to customize how these messages look? Head to the **Message Routes** page on your dashboard, set the format to **Custom Message**, and compose your own template using `{{placeholder}}` syntax.

**Example template:**

```
:gem: {{player_name}} got {{item_name}} from {{mob_name}}! ({{rate}})
```

**Available placeholders for this event:**

| Placeholder        | Example                                        |
| ------------------ | ---------------------------------------------- |
| `{{player_name}}`  | Marky                                          |
| `{{mob_name}}`     | Poring                                         |
| `{{mob_id}}`       | 1002                                           |
| `{{item_name}}`    | Apple                                          |
| `{{item_id}}`      | 512                                            |
| `{{rate}}`         | 50.00%                                         |
| `{{rate_raw}}`     | 5000                                           |
| `{{src_template}}` | :gem: \[Card Drop] Marky obtained Poring Card! |

{% hint style="info" %}
`{{src_template}}` contains the pre-formatted message from your game server's `[template: ...]` field. If your server doesn't send one, it resolves to empty. See [Message Templates](/ragnascript/message-templates) for details.
{% endhint %}

See [Message Routing](/ragnascript/message-routing) for more about formats, conditions, and multi-channel routing.


# Homunculus Drops

Logs rare item drops when a player's homunculus kills a monster

Logs item drops when a **player's homunculus** lands the killing blow on a monster.

**Action:** `hom_item_drop`

## Why Use It

* **Monitor AFK homunculus farming** separately from direct player kills
* **Track Alchemist/Geneticist hunting patterns**
* **Distinguish automated farming** from active gameplay

## Configuration

```
ragnasync_hom_item_drop_log_enabled: true
ragnasync_hom_item_drop_log_channel_id: "YOUR_CHANNEL_ID"
ragnasync_hom_item_drop_log_min_rate: 500
ragnasync_hom_item_drop_log_types: "all"
```

| Setting      | Description                                                                                                                        | Default |
| ------------ | ---------------------------------------------------------------------------------------------------------------------------------- | ------- |
| `enabled`    | Enable/disable logging                                                                                                             | `false` |
| `channel_id` | Discord channel for drop logs                                                                                                      | -       |
| `min_rate`   | Minimum drop rate to log (500 = 5%)                                                                                                | `500`   |
| `types`      | Item categories — `all`, `healing`, `usable`, `etc`, `armor`, `weapon`, `card`, `petegg`, `petarmor`, `ammo`, `shadowgear`, `cash` | `all`   |

Tokens are comma-separated and case-insensitive. See [Item Drop Types](/getting-started/channels-conf#item-drop-types) for the full list with descriptions.

{% hint style="warning" %}
This filter is **item categories**, not mob classes. `"mvp,miniboss"` won't work — those aren't item types and every token will be rejected (watch for `[Warning]: Unknown item type '...'` at map-server startup).
{% endhint %}

For data fields and more details, see [Mob Item Drops](/event-logs/drops/mob-item-drop-log).

## Custom Messages

Want to customize how these messages look? Head to the **Message Routes** page on your dashboard, set the format to **Custom Message**, and compose your own template using `{{placeholder}}` syntax.

**Example template:**

```
:paw_prints: {{player_name}}'s homunculus got {{item_name}} from {{mob_name}}! ({{rate}})
```

**Available placeholders for this event:**

| Placeholder        | Example                                        |
| ------------------ | ---------------------------------------------- |
| `{{player_name}}`  | Marky                                          |
| `{{mob_name}}`     | Poring                                         |
| `{{mob_id}}`       | 1002                                           |
| `{{item_name}}`    | Apple                                          |
| `{{item_id}}`      | 512                                            |
| `{{rate}}`         | 50.00%                                         |
| `{{rate_raw}}`     | 5000                                           |
| `{{src_template}}` | :gem: \[Card Drop] Marky obtained Poring Card! |

{% hint style="info" %}
`{{src_template}}` contains the pre-formatted message from your game server's `[template: ...]` field. If your server doesn't send one, it resolves to empty. See [Message Templates](/ragnascript/message-templates) for details.
{% endhint %}

See [Message Routing](/ragnascript/message-routing) for more about formats, conditions, and multi-channel routing.


# Mercenary Drops

Logs rare item drops when a player's mercenary kills a monster

Logs item drops when a **player's mercenary** lands the killing blow on a monster.

**Action:** `merc_item_drop`

## Why Use It

* **Track mercenary-assisted farming** separately from direct player kills
* **Monitor mercenary usage patterns** across player types
* **Distinguish mercenary farming** from homunculus and player kills

## Configuration

```
ragnasync_merc_item_drop_log_enabled: true
ragnasync_merc_item_drop_log_channel_id: "YOUR_CHANNEL_ID"
ragnasync_merc_item_drop_log_min_rate: 500
ragnasync_merc_item_drop_log_types: "all"
```

| Setting      | Description                                                                                                                        | Default |
| ------------ | ---------------------------------------------------------------------------------------------------------------------------------- | ------- |
| `enabled`    | Enable/disable logging                                                                                                             | `false` |
| `channel_id` | Discord channel for drop logs                                                                                                      | -       |
| `min_rate`   | Minimum drop rate to log (500 = 5%)                                                                                                | `500`   |
| `types`      | Item categories — `all`, `healing`, `usable`, `etc`, `armor`, `weapon`, `card`, `petegg`, `petarmor`, `ammo`, `shadowgear`, `cash` | `all`   |

Tokens are comma-separated and case-insensitive. See [Item Drop Types](/getting-started/channels-conf#item-drop-types) for the full list with descriptions.

{% hint style="warning" %}
This filter is **item categories**, not mob classes. `"mvp,miniboss"` won't work — those aren't item types and every token will be rejected (watch for `[Warning]: Unknown item type '...'` at map-server startup).
{% endhint %}

For data fields and more details, see [Mob Item Drops](/event-logs/drops/mob-item-drop-log).

## Custom Messages

Want to customize how these messages look? Head to the **Message Routes** page on your dashboard, set the format to **Custom Message**, and compose your own template using `{{placeholder}}` syntax.

**Example template:**

```
:crossed_swords: {{player_name}}'s mercenary got {{item_name}} from {{mob_name}}! ({{rate}})
```

**Available placeholders for this event:**

| Placeholder        | Example                                        |
| ------------------ | ---------------------------------------------- |
| `{{player_name}}`  | Marky                                          |
| `{{mob_name}}`     | Poring                                         |
| `{{mob_id}}`       | 1002                                           |
| `{{item_name}}`    | Apple                                          |
| `{{item_id}}`      | 512                                            |
| `{{rate}}`         | 50.00%                                         |
| `{{rate_raw}}`     | 5000                                           |
| `{{src_template}}` | :gem: \[Card Drop] Marky obtained Poring Card! |

{% hint style="info" %}
`{{src_template}}` contains the pre-formatted message from your game server's `[template: ...]` field. If your server doesn't send one, it resolves to empty. See [Message Templates](/ragnascript/message-templates) for details.
{% endhint %}

See [Message Routing](/ragnascript/message-routing) for more about formats, conditions, and multi-channel routing.


# Economy

Log shop, vending, and refine activity to Discord

Keep an eye on your server's economy. These logs post buying, selling, vending and refining activity to Discord.

* [Shop Logs](/event-logs/economy/shop-log) — NPC shop buy and sell transactions.
* [Vending Logs](/event-logs/economy/vending-log) — player-to-player vending sales.
* [Vending Items](/event-logs/economy/vending-items) — items players put up for vending.
* [Refine Logs](/event-logs/economy/refine-log) — equipment refine attempts and their results.


# Shop Logs

Logs NPC shop buy/sell transactions to Discord

The Shop Log feature tracks when players buy from or sell to NPC shops. This helps you understand your server's economy and monitor for unusual trading patterns.

## Action

`shop_log`

## When It's Used

This log is triggered when a player:

* **Buys items** from any NPC shop
* **Sells items** to any NPC shop

All items in a single transaction are batched together in one message.

## Why Use It

* **Monitor economy** by tracking zeny flow through NPC shops
* **Detect abuse** like item duplication or price exploits
* **Track popular items** to understand what players need
* **Audit transactions** for server administration

## Server Configuration

In `conf/channels.conf`:

```
// NPC shop transaction logging
ragnasync_shop_log_enabled: true
ragnasync_shop_log_channel_id: "YOUR_CHANNEL_ID"
ragnasync_shop_log_buy_enabled: true
ragnasync_shop_log_sell_enabled: true
ragnasync_shop_log_min_zeny: 1000000
ragnasync_shop_log_group_min: 0
ragnasync_shop_log_group_max: 0
```

| Setting        | Description                                 | Default |
| -------------- | ------------------------------------------- | ------- |
| `enabled`      | Enable/disable shop logging                 | `false` |
| `channel_id`   | Discord channel ID for shop logs            | -       |
| `buy_enabled`  | Log buy transactions                        | `true`  |
| `sell_enabled` | Log sell transactions                       | `true`  |
| `min_zeny`     | Minimum zeny threshold to log (0 = log all) | `0`     |
| `group_min`    | Minimum GM group level to log               | `0`     |
| `group_max`    | Maximum GM group level to log               | `99`    |

**Tip:** Set `min_zeny: 1000000` to only log transactions over 1 million zeny.

## Message Format

The default message shows player, items, quantities, and total prices:

```
🛒 Shop [Buy]:     | Knight           | Red Potion           x100   | 5,000z
🛒 Shop [Buy]:     | Knight           | Orange Potion        x50    | 10,000z
```

Multiple items in one transaction are grouped together.

## Data Fields

| Field         | Type   | Description                          |
| ------------- | ------ | ------------------------------------ |
| `type`        | string | Transaction type: `buy` or `sell`    |
| `player_name` | string | Character name                       |
| `list`        | string | Batched item data (see format below) |
| `pos`         | string | Player location: \`map               |
| `npc_id`      | int    | NPC ID (optional)                    |
| `account_id`  | int    | Player's account ID                  |
| `char_id`     | int    | Player's character ID                |
| `channel`     | string | Discord channel ID                   |

### List Format

Items are sent as a comma-separated list with pipe-delimited values:

```
item_id|item_name|amount|price,item_id|item_name|amount|price,...
```

**Example:**

```
501|Red Potion|100|5000,502|Orange Potion|50|10000
```

## Example Use Cases

### Economy Monitoring Channel

Track all NPC transactions in an admin channel:

```
ragnasync_shop_log_channel_id: "1234567890"  // #economy-logs
```

### Detect Unusual Activity

Large purchases from NPCs might indicate:

* Item price exploits
* Automated bots
* Economy manipulation

### Track Popular Items

See which items players buy most frequently to balance your server economy.

## Custom Messages

Want to customize how these messages look? Head to the **Message Routes** page on your dashboard, set the format to **Custom Message**, and compose your own template using `{{placeholder}}` syntax.

**Example template:**

```
:shopping_cart: {{player_name}} {{type}} items from the shop
```

**Available placeholders for this event:**

| Placeholder       | Example |
| ----------------- | ------- |
| `{{player_name}}` | Marky   |
| `{{type}}`        | buy     |

See [Message Routing](/ragnascript/message-routing) for more about formats, conditions, and multi-channel routing.

## Notes

* All items in a single shop transaction are sent together
* The `price` field shows the total cost (unit price × quantity)
* Position tracking (`pos`) helps identify which NPC was used
* Both buying and selling are tracked with the `type` field


# Vending Logs

Logs player-to-player vending transactions to Discord

The Vending Log feature tracks player-to-player shop transactions. When someone buys items from a merchant's vending shop, the details are logged to Discord.

## Action

`vending_log`

## When It's Used

This log is triggered when:

* A player **purchases items** from another player's vending shop
* The transaction completes successfully

All items purchased in a single transaction are batched together.

## Why Use It

* **Track your server's player economy** in real-time
* **Monitor high-value trades** for potential scams
* **Celebrate big sales** in your community
* **Audit suspicious transactions** between accounts
* **Understand market prices** for popular items

## Server Configuration

In `conf/channels.conf`:

```
// Player vending transaction logging
ragnasync_vending_log_enabled: true
ragnasync_vending_log_channel_id: "YOUR_CHANNEL_ID"
ragnasync_vending_log_min_zeny: 100000
ragnasync_vending_log_group_min: 0
ragnasync_vending_log_group_max: 99
```

| Setting      | Description                                 | Default |
| ------------ | ------------------------------------------- | ------- |
| `enabled`    | Enable/disable vending logging              | `false` |
| `channel_id` | Discord channel ID for vending logs         | -       |
| `min_zeny`   | Minimum zeny threshold to log (0 = log all) | `0`     |
| `group_min`  | Minimum GM group level to log               | `0`     |
| `group_max`  | Maximum GM group level to log               | `99`    |

**Note:** This setting also controls `vending_item` messages when players open shops.

## Message Format

The default message shows buyer, seller, items, and prices including tax:

```
🏪 Vending:       | Buyer        -> Merchant     | Red Potion           x10    | 20,000z (tax 2,000z)
🏪 Vending:       | Buyer        -> Merchant     | Blue Potion          x5     | 50,000z (tax 5,000z)
```

## Data Fields

| Field               | Type   | Description                          |
| ------------------- | ------ | ------------------------------------ |
| `buyer_name`        | string | Character name of the buyer          |
| `seller_name`       | string | Character name of the seller         |
| `list`              | string | Batched item data (see format below) |
| `pos`               | string | Shop location: \`map                 |
| `buyer_account_id`  | int    | Buyer's account ID                   |
| `buyer_char_id`     | int    | Buyer's character ID                 |
| `seller_account_id` | int    | Seller's account ID                  |
| `seller_char_id`    | int    | Seller's character ID                |
| `channel`           | string | Discord channel ID                   |

### List Format

Items are sent as a comma-separated list with pipe-delimited values:

```
item_id|item_name|amount|price|tax,item_id|item_name|amount|price|tax,...
```

**Example:**

```
512|Apple|10|20000|2000,513|Banana|5|50000|5000
```

## Example Use Cases

### Public Trading Channel

Let everyone see what's being traded on your server:

```
ragnasync_vending_log_channel_id: "1234567890"  // #marketplace
```

### Monitor High-Value Trades

Watch for expensive items changing hands to prevent scams.

### Track Economy Health

See what items players are actually buying and at what prices.

### Celebrate Big Sales

When someone sells an MVP card, the whole community can see it!

## Custom Messages

Want to customize how these messages look? Head to the **Message Routes** page on your dashboard, set the format to **Custom Message**, and compose your own template using `{{placeholder}}` syntax.

**Example template:**

```
:moneybag: {{buyer_name}} bought items from {{seller_name}}!
```

**Available placeholders for this event:**

| Placeholder       | Example |
| ----------------- | ------- |
| `{{buyer_name}}`  | Marky   |
| `{{seller_name}}` | Vendor  |

See [Message Routing](/ragnascript/message-routing) for more about formats, conditions, and multi-channel routing.

## Notes

* Both buyer and seller information is tracked
* Tax amount is calculated based on your server's vending tax rate
* Position shows where the merchant's shop was located
* All items from a single purchase are grouped together
* Use alongside Vending Items to see complete shop activity


# Vending Items

Logs when players open vending shops with their item listings

The Vending Items feature logs when a player opens a vending shop, showing all the items they're selling and their prices. This is a snapshot of what's available in the shop at the moment it opens.

## Action

`vending_item`

## When It's Used

This log is triggered when:

* A player **opens a vending shop** (uses the Vending skill)
* The shop setup is complete with items and prices

This happens once when the shop opens, not on each sale.

## Why Use It

* **Advertise player shops** to your Discord community
* **Track market prices** for popular items
* **Help players find deals** without logging in
* **Monitor merchant activity** on your server
* **Create a marketplace preview** channel

## Server Configuration

Vending item logging uses the same settings as vending transaction logging. In `conf/channels.conf`:

```
// Player vending logging (includes shop openings)
ragnasync_vending_log_enabled: true
ragnasync_vending_log_channel_id: "YOUR_CHANNEL_ID"
ragnasync_vending_log_min_zeny: 0
ragnasync_vending_log_group_min: 0
ragnasync_vending_log_group_max: 99
```

| Setting      | Description                                           | Default |
| ------------ | ----------------------------------------------------- | ------- |
| `enabled`    | Enable/disable vending logging                        | `false` |
| `channel_id` | Discord channel ID for vending logs and shop listings | -       |
| `min_zeny`   | Minimum zeny threshold (doesn't affect shop openings) | `0`     |
| `group_min`  | Minimum GM group level to log                         | `0`     |
| `group_max`  | Maximum GM group level to log                         | `99`    |

**Note:** Shop opening messages are always sent when vending logging is enabled, regardless of `min_zeny`.

## Message Format

The default message lists all items in the newly opened shop:

```
🏪 Vending Item:  | MerchantName     | Red Potion           | 2,000z
🏪 Vending Item:  | MerchantName     | Blue Potion          | 5,000z
🏪 Vending Item:  | MerchantName     | Lord Kaho's Horn     | 5,000,000z
```

## Data Fields

| Field         | Type   | Description                             |
| ------------- | ------ | --------------------------------------- |
| `player_name` | string | Name of the merchant character          |
| `list`        | string | All items being sold (see format below) |
| `pos`         | string | Shop location: \`map                    |
| `account_id`  | int    | Merchant's account ID                   |
| `char_id`     | int    | Merchant's character ID                 |
| `channel`     | string | Discord channel ID                      |

### List Format

Items are sent as a comma-separated list with pipe-delimited values:

```
item_id|item_name|price,item_id|item_name|price,...
```

**Example:**

```
512|Apple|2000,5013|Lord Kaho Horn|5000000,2301|Cotton Shirt|50000
```

The price is per unit, not total.

## Example Use Cases

### Public Marketplace Channel

Let players browse shop listings from Discord:

```
ragnasync_vending_log_channel_id: "1234567890"  // #marketplace
```

### Price Tracking

Monitor how items are being priced across your server.

### Shop Directory

Create a searchable channel of active vending shops with their locations.

### Deal Alerts

Players can watch the channel for items they're looking for at good prices.

## Custom Messages

Want to customize how these messages look? Head to the **Message Routes** page on your dashboard, set the format to **Custom Message**, and compose your own template using `{{placeholder}}` syntax.

**Example template:**

```
:label: {{player_name}} is selling {{item_name}} for {{price}}z
```

**Available placeholders for this event:**

| Placeholder       | Example            |
| ----------------- | ------------------ |
| `{{player_name}}` | Marky              |
| `{{item_name}}`   | Apple              |
| `{{price}}`       | 50000              |
| `{{pos}}`         | prontera\|150\|180 |

See [Message Routing](/ragnascript/message-routing) for more about formats, conditions, and multi-channel routing.

## Notes

* Sent once when the shop opens, not on each sale
* Maximum 12 items per shop (game limit)
* Price shown is per unit
* Use with Vending Logs to track actual sales
* Position helps players find the shop in-game


# Refine Logs

Logs equipment refining attempts to Discord

The Refine Log feature tracks equipment refining attempts, including successes, failures, and downgrades. Perfect for celebrating those +10s or commiserating over broken gear.

## Action

`refine_log`

## When It's Used

This log is triggered when a player:

* **Successfully refines** equipment (refine level increases)
* **Fails a refine** (equipment breaks or refine fails)
* **Downgrades** equipment (refine level decreases)

## Why Use It

* **Celebrate lucky refines** with your community
* **Share the pain** of broken equipment
* **Track refine statistics** for your server
* **Monitor for refine exploits** or unusual patterns
* **Create engaging community moments**

## Server Configuration

In `conf/channels.conf`:

```
// Refine attempt logging
ragnasync_refine_log_enabled: true
ragnasync_refine_log_channel_id: "YOUR_CHANNEL_ID"
ragnasync_refine_log_min_level: 7
ragnasync_refine_log_group_min: 0
ragnasync_refine_log_group_max: 0
```

| Setting      | Description                               | Default |
| ------------ | ----------------------------------------- | ------- |
| `enabled`    | Enable/disable refine logging             | `false` |
| `channel_id` | Discord channel ID for refine logs        | -       |
| `min_level`  | Minimum refine level to log (0 = log all) | `0`     |
| `group_min`  | Minimum GM group level to log             | `0`     |
| `group_max`  | Maximum GM group level to log             | `99`    |

**Tip:** Set `min_level: 7` to only log high-refine attempts where the real excitement (and heartbreak) happens!

## Message Format

The default message shows the refine result with appropriate emoji:

```
✅ Refine [Success]:  | Knight           | Valkyrie Armor       +6 -> +7 | 100,000z
❌ Refine [Fail]:     | Mage             | Staff of Destruction +7 -> +0 | 200,000z
⬇️ Refine [Downgrade]: | Archer          | Composite Bow        +9 -> +8 | 150,000z
```

## Data Fields

| Field         | Type   | Description                               |
| ------------- | ------ | ----------------------------------------- |
| `player_name` | string | Character name                            |
| `item_name`   | string | Name of the item being refined            |
| `item_id`     | int    | Item database ID                          |
| `refine_from` | int    | Refine level before attempt               |
| `refine_to`   | int    | Refine level after attempt                |
| `result`      | string | Result: `success`, `fail`, or `downgrade` |
| `material_id` | int    | Refine material item ID                   |
| `zeny_cost`   | int    | Zeny cost of the refine                   |
| `account_id`  | int    | Player's account ID                       |
| `char_id`     | int    | Player's character ID                     |
| `channel`     | string | Discord channel ID                        |

## Example Use Cases

### Public Refine Channel

Share the excitement (and despair) of refining:

```
ragnasync_refine_log_channel_id: "1234567890"  // #refine-results
```

### High Refine Celebrations

Everyone can cheer when someone hits +10!

### RNG Tracking

Monitor refine success rates to ensure your server rates are working correctly.

### Economy Monitoring

Track zeny sink from refining attempts.

## Custom Messages

Want to customize how these messages look? Head to the **Message Routes** page on your dashboard, set the format to **Custom Message**, and compose your own template using `{{placeholder}}` syntax.

**Example template:**

```
:slot_machine: {{player_name}} went for +{{refine_to}} on {{item_name}}... {{result}}! ({{zeny_cost}}z)
```

**Available placeholders for this event:**

| Placeholder       | Example |
| ----------------- | ------- |
| `{{player_name}}` | Marky   |
| `{{item_name}}`   | Knife   |
| `{{item_id}}`     | 1201    |
| `{{refine_from}}` | 6       |
| `{{refine_to}}`   | 7       |
| `{{result}}`      | success |
| `{{zeny_cost}}`   | 10000   |

See [Message Routing](/ragnascript/message-routing) for more about formats, conditions, and multi-channel routing.

## Result Types

| Result      | Description       | What Happens                       |
| ----------- | ----------------- | ---------------------------------- |
| `success`   | Refine succeeded  | Level increases by 1               |
| `fail`      | Refine failed     | Equipment breaks (goes to +0)      |
| `downgrade` | Refine downgraded | Level decreases by 1 (safe refine) |

## Notes

* Each refine attempt is logged individually
* Material ID helps track which ores are being used
* Zeny cost is the amount charged by the NPC
* Great for creating community engagement around RNG moments


# Players

Track player logins and server population over time

See who's online and how your population trends. These pages track player activity on your server.

* [Login & Logout](/event-logs/players/login-logout) — player login and logout events.
* [Population](/event-logs/players/population) — periodic online-player statistics.


# Login & Logout

Tracks player login and logout events for statistics

The `LoginScript` and `LogoutScript` handle player connection tracking, updating character online status and login statistics.

## Actions

* `login` - Player connects to the game
* `logout` - Player disconnects from the game

## When They're Used

These scripts are triggered automatically when:

* **Login**: A player successfully enters the game with a character
* **Logout**: A player disconnects from the game (normal or abnormal)

## Why Use Them

* **Track online status** to know who's currently playing
* **Login statistics** for player activity analysis
* **First/last seen** timestamps for player history
* **Online player counts** for dashboard displays
* **Activity patterns** to understand player behavior

## Server Configuration

Login and logout tracking is automatic when RagnaSync is connected. No additional configuration is required.

These scripts are **not counted toward usage limits** as they are triggered by player connections, not user actions.

## Data Tracked

### On Login

| Field            | Description             |
| ---------------- | ----------------------- |
| `is_online`      | Set to `true`           |
| `login_count`    | Incremented by 1        |
| `last_login_at`  | Current timestamp       |
| `first_login_at` | Set on first ever login |

### On Logout

| Field            | Description       |
| ---------------- | ----------------- |
| `is_online`      | Set to `false`    |
| `last_logout_at` | Current timestamp |

## Data Fields (Both Scripts)

| Field        | Type   | Description           |
| ------------ | ------ | --------------------- |
| `name`       | string | Character name        |
| `account_id` | int    | Player's account ID   |
| `char_id`    | int    | Player's character ID |

## Use Cases

### Online Player Dashboard

Query characters where `is_online = true` to show current players.

### Activity Reports

Analyze `login_count` and `last_login_at` to identify:

* Active players
* Returning players
* Inactive accounts

### Player History

Track first login dates to identify new players vs veterans.

## Discord Notifications

By default, login and logout events only track data — no Discord messages are sent. But you can optionally send notifications to Discord by adding a route on the **Message Routes** page.

Since these events fire on every player connection, routes are **not auto-created**. You need to add one manually if you want notifications.

### Setting It Up

1. Go to **Discord > Message Routes** in your dashboard
2. Find **Player Login** or **Player Logout**
3. Click **Add Route**
4. Pick your channel and format

### Custom Messages

When using the **Custom Message** format, you can use the `{{player_name}}` placeholder:

```
:green_circle: {{player_name}} has logged in!
```

```
:red_circle: {{player_name}} has logged out.
```

See [Message Routing](/ragnascript/message-routing) for more details on formats, conditions, and custom messages.

## Notes

* Login/logout tracking is automatic when RagnaSync is connected
* Discord notifications are opt-in — add a route to enable them
* Does not count toward RagnaSync usage limits
* Data can be used for player activity dashboards and reports


# Population

Sends periodic server population statistics to Discord

The Population feature sends periodic snapshots of your server's online player count to Discord. This helps your community see how active the server is at a glance.

## Action

`population`

## When It's Used

This log is sent:

* **Periodically** at a configurable interval (minimum 60 seconds)
* **Automatically** without any player action
* Contains a breakdown of player types

## Why Use It

* **Show server activity** to your Discord community
* **Track population trends** over time
* **Attract new players** by showing an active server
* **Monitor peak hours** for events and maintenance
* **Distinguish active vs AFK** player counts

## Server Configuration

In `conf/channels.conf`:

```
// Population statistics logging
ragnasync_population_log_enabled: true
ragnasync_population_log_channel_id: "YOUR_CHANNEL_ID"
ragnasync_population_log_interval: 1800
```

| Setting                               | Description                             | Default         |
| ------------------------------------- | --------------------------------------- | --------------- |
| `ragnasync_population_log_enabled`    | Enable/disable population logging       | `false`         |
| `ragnasync_population_log_channel_id` | Discord channel ID for population stats | -               |
| `ragnasync_population_log_interval`   | Seconds between updates (minimum 60)    | `3600` (1 hour) |

## Message Format

The default message shows a complete player breakdown:

```
👥 Population:    | Total: 150   | Active: 95   | Vending: 30   | Autotrade: 20   | Buyingstore: 3
```

## Data Fields

| Field         | Type   | Description                                        |
| ------------- | ------ | -------------------------------------------------- |
| `total`       | int    | Total online players (all types combined)          |
| `active`      | int    | Players actively playing (not vending/autotrading) |
| `vending`     | int    | Players with manual vending shops open             |
| `autotrade`   | int    | Players in autotrade mode (AFK vending/buying)     |
| `buyingstore` | int    | Players with manual buying stores open             |
| `channel`     | string | Discord channel ID                                 |

## Player Categories Explained

| Category        | Description                       |
| --------------- | --------------------------------- |
| **Total**       | Everyone logged in                |
| **Active**      | Playing the game, not in a shop   |
| **Vending**     | Running a player shop (manually)  |
| **Autotrade**   | AFK with @autotrade active        |
| **Buyingstore** | Running a buying store (manually) |

## Example Use Cases

### Server Status Channel

Create a dedicated channel for population updates:

```
ragnasync_population_log_channel_id: "1234567890"  // #server-status
ragnasync_population_log_interval: 1800            // Every 30 minutes
```

### Peak Hour Tracking

Use shorter intervals during events to track participation:

```
ragnasync_population_log_interval: 300  // Every 5 minutes during events
```

### Recruitment

Show prospective players that your server is active with regular updates.

## Custom Messages

Want to customize how these messages look? Head to the **Message Routes** page on your dashboard, set the format to **Custom Message**, and compose your own template using `{{placeholder}}` syntax.

**Example template:**

```
:globe_with_meridians: Online: {{total}} players ({{active}} active, {{vending}} vending)
```

**Available placeholders for this event:**

| Placeholder       | Example |
| ----------------- | ------- |
| `{{total}}`       | 150     |
| `{{active}}`      | 95      |
| `{{vending}}`     | 30      |
| `{{autotrade}}`   | 20      |
| `{{buyingstore}}` | 3       |

See [Message Routing](/ragnascript/message-routing) for more about formats, conditions, and multi-channel routing.

## Interval Recommendations

| Interval          | Use Case                                |
| ----------------- | --------------------------------------- |
| 60-300s           | During events, high activity monitoring |
| 1800s (30 min)    | Active community updates                |
| 3600s (1 hour)    | Standard server status                  |
| 86400s (24 hours) | Daily summary                           |

## Notes

* Minimum interval is 60 seconds to prevent spam
* Population counts are calculated server-side at the time of sending
* Great for integration with Discord bot status displays
* Consider using with a dedicated "server-status" channel


# Staff

Mirror staff commands and player requests to Discord

Give your team visibility in Discord. These pages mirror GM activity and player support requests to your staff channels.

* [@Commands](/event-logs/staff/at-command-log) — GM `@commands` logged for audit trails.
* [Ticket Support Command](/features/ticket-system/ticket-support) — player `@request` messages sent to staff.


# @Commands

Logs @commands executed by staff members to Discord

The `AtCommandScript` handles logging of @commands (GM commands) executed in-game to Discord for audit purposes.

## Action

`at_command`

## When It's Used

This script is triggered when:

* A player executes any @command (e.g., `@warp`, `@item`, `@ban`)
* The player's group ID meets or exceeds the configured minimum
* @command logging is enabled in the server configuration

## Why Use It

* **Audit GM activity** to ensure staff members are following guidelines
* **Track abuse** by logging all privileged commands
* **Maintain accountability** with permanent Discord logs
* **Monitor server administration** in real-time

## Server Configuration

In `conf/channels.conf`:

```
// @Command logging to Discord
ragnasync_at_command_log_enabled: true
ragnasync_at_command_log_channel_id: "YOUR_CHANNEL_ID"
ragnasync_at_command_log_group_min: 1
ragnasync_at_command_log_group_max: 99
```

| Setting      | Description                         | Default |
| ------------ | ----------------------------------- | ------- |
| `enabled`    | Enable/disable @command logging     | `false` |
| `channel_id` | Discord channel ID for command logs | -       |
| `group_min`  | Minimum GM group level to log       | `1`     |
| `group_max`  | Maximum GM group level to log       | `99`    |

**Tip:** Set `group_min: 99` to only log admin commands, or `group_min: 1` to log all staff activity.

## Message Format

The script sends formatted messages to Discord:

```
🔸 Command Log:   | 2000001 | 150001  | AdminName        | @warp prontera  | 14:30:25 |
```

Format: `Account ID | Char ID | Character Name | Command | Time`

## Data Fields

| Field        | Type   | Description                                 |
| ------------ | ------ | ------------------------------------------- |
| `name`       | string | Name of the character executing the command |
| `command`    | string | The full @command with arguments            |
| `account_id` | int    | Player's account ID                         |
| `char_id`    | int    | Player's character ID                       |
| `pos`        | string | Player's location: \`map                    |
| `channel`    | string | Discord channel ID                          |

## Example Use Cases

### Log All Staff Commands

Set a low group threshold to log all staff activity:

```
ragnasync_at_command_log_group_min: 1  // Log all staff groups
```

### Admin-Only Logging

Only log commands from administrators:

```
ragnasync_at_command_log_group_min: 99  // Admin group only
```

### Private Audit Channel

Send logs to a restricted channel only visible to senior staff:

```
ragnasync_at_command_log_channel_id: "1234567890123456789"  // #admin-audit
```

## Custom Messages

Want to customize how these messages look? Head to the **Message Routes** page on your dashboard, set the format to **Custom Message**, and compose your own template using `{{placeholder}}` syntax.

**Example template:**

```
:warning: {{player_name}} used {{command}}
```

**Available placeholders for this event:**

| Placeholder        | Example                                                 |
| ------------------ | ------------------------------------------------------- |
| `{{player_name}}`  | Marky                                                   |
| `{{command}}`      | @storage                                                |
| `{{src_template}}` | :small\_orange\_diamond: Command Log: Marky \| @storage |

{% hint style="info" %}
`{{src_template}}` contains the pre-formatted message from your game server's `[template: ...]` field. If your server doesn't send one, it resolves to empty. See [Message Templates](/ragnascript/message-templates) for details.
{% endhint %}

See [Message Routing](/ragnascript/message-routing) for more about formats, conditions, and multi-channel routing.

## Notes

* Commands are logged with full arguments for complete audit trails
* Logs include timestamps for tracking when commands were executed
* Use with @request logging for comprehensive staff monitoring


# Mob Deaths

Logs monster kills (MVPs, mini-bosses) to Discord

Announce MVP and boss kills to your Discord server. Great for tracking who's hunting bosses and celebrating big kills with your community.

## Action

`mob_death`

## When It's Used

This script triggers automatically when:

* A player kills a monster matching the configured type
* Mob death logging is enabled in channels.conf

## Why Use It

* **Track MVP hunters** - See who's farming bosses
* **Community engagement** - Celebrate big kills together
* **Server activity** - Know when MVPs are being hunted
* **Detect issues** - Spot suspicious kill patterns

## Server Configuration

In `conf/channels.conf`, add these settings in the `discord: { }` block:

```
// Mob death logging
ragnasync_mob_death_log_enabled: true
ragnasync_mob_death_log_channel_id: "1234567890123456789"
ragnasync_mob_death_log_types: "mvp,miniboss"
```

| Setting                              | Type   | Default | Description                             |
| ------------------------------------ | ------ | ------- | --------------------------------------- |
| `ragnasync_mob_death_log_enabled`    | bool   | `false` | Enable mob death logging                |
| `ragnasync_mob_death_log_channel_id` | string | `""`    | Discord channel for announcements       |
| `ragnasync_mob_death_log_types`      | string | `"all"` | Which monsters to log (comma-separated) |

### Monster Types

This filter takes a **comma-separated, case-insensitive** list of mob classes.

| Token      | What Gets Logged                                                         |
| ---------- | ------------------------------------------------------------------------ |
| `all`      | Every mob class (default)                                                |
| `normal`   | Regular mobs — **very spammy**, logs every kill across your whole server |
| `miniboss` | Mini-boss monsters                                                       |
| `mvp`      | MVPs                                                                     |

**Multiple types:** `"mvp,miniboss"`, `"mvp,miniboss,normal"`, etc.

{% hint style="warning" %}
Unknown tokens (e.g. item categories like `"card"` or `"weapon"`) are rejected with a `[Warning]: Unknown mob type '...'` line at map-server startup. If **every** token in your list is invalid, the filter falls back to "log everything" — so a typo or a wrong token list can silently produce a firehose of logs. Check your map-server startup log if a new filter seems to be ignoring you.
{% endhint %}

## Message Format

When a player kills an MVP, Discord shows a formatted log:

```
🔹 Monster Log:   | 2000001 | 150001  | PlayerName       | Baphomet        | 14:30:25 |
```

Format: `Account ID | Char ID | Character Name | Monster Name | Time`

## Data Fields

| Field         | Type   | Description                      |
| ------------- | ------ | -------------------------------- |
| `player_name` | string | Character who killed the monster |
| `mob_name`    | string | Monster name                     |
| `mob_id`      | int    | Monster ID                       |
| `account_id`  | int    | Player's account ID              |
| `char_id`     | int    | Player's character ID            |
| `channel`     | string | Discord channel ID               |

## Examples

### MVP-Only Announcements

Log only MVP kills to a public channel:

```
ragnasync_mob_death_log_enabled: true
ragnasync_mob_death_log_channel_id: "1234567890123456789"
ragnasync_mob_death_log_types: "mvp"
```

### Include Mini-Bosses

For more comprehensive boss tracking:

```
ragnasync_mob_death_log_enabled: true
ragnasync_mob_death_log_channel_id: "1234567890123456789"
ragnasync_mob_death_log_types: "mvp,miniboss"
```

### Staff Monitoring Channel

Send to a private channel for staff review:

```
ragnasync_mob_death_log_enabled: true
ragnasync_mob_death_log_channel_id: "9999999999999999999"  // #staff-logs
ragnasync_mob_death_log_types: "mvp"
```

## Custom Messages

Want to customize how these messages look? Head to the **Message Routes** page on your dashboard, set the format to **Custom Message**, and compose your own template using `{{placeholder}}` syntax.

**Example template:**

```
:trophy: {{player_name}} defeated {{mob_name}}!
```

**Available placeholders for this event:**

| Placeholder        | Example                          |
| ------------------ | -------------------------------- |
| `{{player_name}}`  | Marky                            |
| `{{mob_name}}`     | Poring                           |
| `{{mob_id}}`       | 1002                             |
| `{{src_template}}` | :trophy: \[Marky] killed Poring! |

{% hint style="info" %}
`{{src_template}}` contains the pre-formatted message from your game server's `[template: ...]` field. If your server doesn't send one, it resolves to empty. See [Message Templates](/ragnascript/message-templates) for details.
{% endhint %}

See [Message Routing](/ragnascript/message-routing) for more about formats, conditions, and multi-channel routing.

## Related

* [Mob Item Drop Log](/event-logs/drops/mob-item-drop-log) - Log rare drops from monster kills
* [Homunculus Item Drop Log](/event-logs/drops/hom-item-drop-log) - Log drops from homunculus kills
* [Mercenary Item Drop Log](/event-logs/drops/merc-item-drop-log) - Log drops from mercenary kills


# Container Logs

Logs container item openings like Card Albums and Gift Boxes to Discord

The Container Log feature tracks when players open special containers like Card Albums, Gift Boxes, and similar items. This is perfect for celebrating rare finds or monitoring item distribution on your server.

## Action

`container_log`

## When It's Used

This log is triggered when a player opens:

* **Old Card Album** - Random card reward
* **Old Blue Box** - Random equipment
* **Old Purple Box** - Random valuable item
* **Gift Box** - Random item
* **Any custom container** you've configured

## Why Use It

* **Celebrate rare drops** when someone pulls a great card
* **Track item distribution** to understand your server's economy
* **Monitor for suspicious activity** like mass-opening containers
* **Create excitement** in your community Discord

## Server Configuration

In `conf/channels.conf`:

```
// Container opening logging
ragnasync_container_log_enabled: true
ragnasync_container_log_channel_id: "YOUR_CHANNEL_ID"
ragnasync_container_log_types: "card_album,giftbox"
ragnasync_container_log_group_min: 0
ragnasync_container_log_group_max: 0
```

| Setting      | Description                           | Default |
| ------------ | ------------------------------------- | ------- |
| `enabled`    | Enable/disable container logging      | `false` |
| `channel_id` | Discord channel ID for container logs | -       |
| `types`      | Container types to log — see below    | `all`   |
| `group_min`  | Minimum GM group level to log         | `0`     |
| `group_max`  | Maximum GM group level to log         | `99`    |

**Tip:** Set `group_min: 0` and `group_max: 0` to only log regular players (exclude GMs).

### Valid `types` values

Comma-separated, case-insensitive.

| Token        | Filters openings of                            |
| ------------ | ---------------------------------------------- |
| `all`        | Every container type (default)                 |
| `card_album` | Card Albums (Old Card Album, variants)         |
| `giftbox`    | Gift boxes — note: **one word, no underscore** |
| `misc_box`   | Other miscellaneous container items            |

{% hint style="warning" %}
A common typo is `"gift_box"` (with underscore) — the parser expects `"giftbox"`. Unknown tokens are rejected with a `[Warning]: Unknown container type '...'` line at map-server startup, and if every token is invalid the filter falls back to "log everything."
{% endhint %}

## Message Format

The default message format shows who opened what and what they got:

```
📦 Container Log: | PlayerName       | Old Card Album       | Thara Frog Card      x1
```

## Data Fields

| Field          | Type   | Description                                    |
| -------------- | ------ | ---------------------------------------------- |
| `player_name`  | string | Name of the character who opened the container |
| `name`         | string | Name of the container (e.g., "Old Card Album") |
| `container_id` | int    | Item ID of the container                       |
| `item_name`    | string | Name of the item received                      |
| `item_id`      | int    | Item ID of what was received                   |
| `amount`       | int    | Quantity received                              |
| `account_id`   | int    | Player's account ID                            |
| `char_id`      | int    | Player's character ID                          |
| `channel`      | string | Discord channel ID                             |

## Example Use Cases

### Celebrate Rare Finds

Send container logs to a public channel so everyone can see lucky pulls:

```
ragnasync_container_log_channel_id: "1234567890"  // #lucky-drops
```

### Track Card Album Results

Monitor what cards are being pulled from albums to ensure rates are working correctly.

### Custom Event Boxes

If you create custom event containers, players can see what rewards others are getting, building excitement.

## Custom Messages

Want to customize how these messages look? Head to the **Message Routes** page on your dashboard, set the format to **Custom Message**, and compose your own template using `{{placeholder}}` syntax.

**Example template:**

```
:gift: {{player_name}} opened a {{container_name}} and got {{item_name}} x{{amount}}!
```

**Available placeholders for this event:**

| Placeholder          | Example       |
| -------------------- | ------------- |
| `{{player_name}}`    | Marky         |
| `{{container_name}}` | Kafra Storage |
| `{{container_id}}`   | 1             |
| `{{item_name}}`      | Apple         |
| `{{item_id}}`        | 512           |
| `{{amount}}`         | 5             |

See [Message Routing](/ragnascript/message-routing) for more about formats, conditions, and multi-channel routing.

## Notes

* Each container opening is logged individually
* Works with any item that uses the random reward system
* Perfect for building community engagement around rare drops


# Server Monitoring

Track your Ragnarok server's health — uptime, crashes, errors and warnings

RagnaSync can keep an eye on your Ragnarok server for you. Once monitoring is running, RagnaSync knows which of your sub-servers are up, captures crashes, and collects the errors and warnings your server prints to its console — all visible from your dashboard, and (optionally) reflected on your public [Status Page](/features/status-page).

Think of it as a health monitor that's always watching, so you don't have to SSH in to find out something broke.

***

## What it watches

| Signal                | What you get                                                                                                                                |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| **Per-server status** | The live state of each sub-server — **Login**, **Char**, **Map** and **Web** — reported as started, stopped, restarting, or crashed.        |
| **Crashes**           | Full crash reports (backtrace, threads, registers) whenever a server segfaults. See [Crash Logs](/monitoring/server-monitoring/crash-logs). |
| **Console errors**    | Lines your server logs at error level, collected so you can spot problems over time.                                                        |
| **Console warnings**  | Warning-level console output, tracked the same way.                                                                                         |

Each sub-server reports independently, so you can tell at a glance whether it was the map-server that went down while login and char stayed up.

***

## Turning it on

Monitoring is built into the **RagnaSync launcher** — the same `ragnasync.sh` (Linux) or `ragnasync.bat` (Windows) script you use for [crash reporting](/monitoring/server-monitoring/crash-logs). When you run your servers through the launcher instead of starting them directly, it watches each process and reports status, crashes, and console output back to RagnaSync automatically.

There's nothing extra to toggle. As soon as the launcher starts reporting, your dashboard lights up:

1. Make sure your `channels.conf` has your `ragnasync_client_guid` (set during install — this is how RagnaSync knows the reports are yours).
2. Start your servers through the launcher. See [Crash Logs → Setup](/monitoring/server-monitoring/crash-logs#setup) for the exact command on your platform.
3. Open your dashboard — the **Monitoring** page appears under the **Ragnarok** section once the first reports arrive.

{% hint style="info" %}
The launcher installs its own dependencies (like GDB and curl) the first time it runs, so you don't need to set anything up by hand.
{% endhint %}

***

## The Monitoring dashboard

Under **Ragnarok → Monitoring** in your dashboard, you'll find a single view of everything the monitor has collected:

* **Crash, error and warning counts** for the period you pick.
* **A trend chart** showing when those happened, so a spike is easy to spot.
* **A period selector** — Last Hour, Last 24 Hours, Last 7 / 14 / 30 Days.

This is the place to check after a rough night: did the map-server crash three times at 2am, or was it just a wave of warnings? The trend chart makes the pattern obvious.

{% hint style="info" %}
The **Monitoring** menu item only shows up once there's something to show. If you don't see it yet, your servers haven't reported in — double-check they're running through the launcher.
{% endhint %}

***

## On your public Status Page

The per-server status the monitor reports also powers the detail on your public [Status Page](/features/status-page):

* **With monitoring running** — your status page breaks uptime down per sub-server: **Login**, **Char**, **Map** and **Web**, each shown on its own.
* **Without it** — the page falls back to a single **Server** up/down indicator based on whether your server is connected to RagnaSync.

So installing the monitor doesn't just help you — it gives your players a more detailed picture of what's up and what's down.

***

## Crash logs

Crash capture is the deepest part of monitoring and has its own page covering setup, what's in a report, and how to read it:

➡️ [Crash Logs](/monitoring/server-monitoring/crash-logs)

***

## Notes

* Monitoring is **read-only** — it observes and reports, it never changes how your server runs.
* Only you can see your server's monitoring data; it's scoped to your guild.
* Normal shutdowns are reported as stopped, not crashed — a crash is specifically an abnormal exit (SIGSEGV, SIGABRT, etc.).


# Console Logs

See the errors and warnings your Ragnarok server prints to its console

When the [monitoring tool](/monitoring/server-monitoring) is running, RagnaSync collects the **errors** and **warnings** your Ragnarok server prints to its console and gathers them in your dashboard under **Ragnarok → Console**. Instead of scrolling a live terminal, you get a tidy, searchable list of what's actually going wrong.

***

## What you see

Each row is one distinct problem, with:

| Column                     | What it tells you                                           |
| -------------------------- | ----------------------------------------------------------- |
| **Type**                   | Whether it's an **Error** or a **Warning**                  |
| **Title**                  | The console message itself                                  |
| **Occurrences**            | How many times this same message has happened               |
| **First Seen / Last Seen** | When it first appeared and the most recent time it happened |
| **Server**                 | Which sub-server it came from                               |

{% hint style="info" %}
Repeated identical messages are **grouped into one row** with an occurrence count, rather than listed hundreds of times. A warning that fires every minute shows up once — with a climbing count — so a real problem doesn't get buried.
{% endhint %}

***

## Reading the list

* **Filter by type** to focus on **Errors** (things that broke) or **Warnings** (things that might).
* **Filter by server** to narrow down to login, char, map or web.
* **Unread markers** highlight messages you haven't looked at yet. Mark individual entries — or everything — as read once you've reviewed them, so new problems stand out next time.

A jump in the occurrence count on a single row is usually your best early signal that something started going wrong.

***

## Where it fits

Console Logs is one part of [Server Monitoring](/monitoring/server-monitoring), alongside:

* [**Crash Logs**](/monitoring/server-monitoring/crash-logs) — full reports when a server actually crashes.
* [**Issues & Notifications**](/monitoring/server-monitoring/issues-notifications) — alerts when these errors spike or new problems appear.

All three appear once your servers are running through the [RagnaSync launcher](/monitoring/server-monitoring#turning-it-on).


# Crash Logs

Automatic crash reporting for your Ragnarok server

RagnaSync can automatically capture and report server crashes, helping you diagnose and fix issues quickly. When your server crashes, detailed debugging information is sent to your RagnaSync dashboard.

## How It Works

When you run your server through the RagnaSync launcher scripts (`ragnasync.sh` on Linux or `ragnasync.bat` on Windows), your server runs under GDB (GNU Debugger). If a crash occurs, the launcher:

1. **Captures the crash** with full debugging information
2. **Generates a report** with backtrace, thread info, and variables
3. **Sends it to RagnaSync** automatically
4. **Notifies you** via your configured channels

## Setup

The launcher starts **all** your sub-servers (login, char, map) under crash capture for you — you don't start them one at a time. Use the `start`, `stop`, `restart`, and `status` commands instead of running the server binaries directly.

### Linux

Use the provided `ragnasync.sh` script:

```bash
# Instead of starting ./login-server, ./char-server, ./map-server yourself:
./ragnasync.sh start     # start all servers under crash capture
./ragnasync.sh status    # see which servers are running
./ragnasync.sh stop      # stop all servers
./ragnasync.sh restart   # stop, then start again
```

### Windows

Use the provided `ragnasync.bat` script the same way:

```batch
ragnasync.bat start
```

The first run installs its dependencies (GDB, curl) automatically.

## What's Captured

When a crash occurs, the report includes:

| Information         | Description                                |
| ------------------- | ------------------------------------------ |
| **Server Type**     | Which server crashed (login, char, or map) |
| **Hostname**        | Machine where the crash occurred           |
| **Timestamp**       | Exact time of the crash (UTC)              |
| **Backtrace**       | Full stack trace showing where it crashed  |
| **Thread Info**     | State of all running threads               |
| **Registers**       | CPU register values at crash time          |
| **Local Variables** | Variable values in the crashing function   |

## Example Crash Report

```
========== RAGNASYNC CRASH REPORT ==========
Server: map-server
Time: 2026-01-27 12:00:00

---------- Backtrace ----------
#0  0x00007f... in npc_click () at npc.cpp:123
#1  0x00007f... in clif_parse_NpcClicked () at clif.cpp:456
#2  0x00007f... in parse_char () at clif_parse.cpp:789
...

---------- All Threads ----------
Thread 1 (Main Thread):
#0  0x00007f... in npc_click () at npc.cpp:123
...

---------- Registers ----------
rax            0x0
rbx            0x7f...
...

---------- Local Variables ----------
sd = 0x7f... (player session data)
nd = 0x0 (null - this caused the crash!)
...

========== END CRASH REPORT ==========
```

## Viewing Crash Logs

Crash logs are available in your RagnaSync dashboard:

1. Log into your RagnaSync account
2. Navigate to your server's dashboard
3. Click on **Crash Logs** in the sidebar
4. View recent crashes with full details

## Discord Notifications

There's no `channels.conf` setting for this — crash logs aren't sent to Discord. They show up automatically in your dashboard's **Crash Logs** page as soon as they're uploaded, with no configuration needed.

## Troubleshooting Tips

### GDB Not Found

If you get an error about GDB not being installed:

**Linux (Ubuntu/Debian):**

```bash
sudo apt-get install gdb
```

**Linux (CentOS/RHEL):**

```bash
sudo yum install gdb
```

### Permissions Issues

Make sure the launcher script is executable:

```bash
chmod +x ragnasync.sh
```

### Server Won't Start

If your server doesn't start through the launcher:

1. Check that your normal server starts without the launcher
2. Verify GDB is installed correctly
3. Check the RagnaSync error logs

## Benefits

* **Fast Debugging** - See exactly where and why crashes happen
* **Automatic Capture** - No manual steps needed when crashes occur
* **Remote Monitoring** - Get notified even when you're away
* **Historical Data** - Track crash patterns over time
* **Detailed Context** - Full stack traces and variable values

## Notes

* Crash logs are only sent for crashes (SIGSEGV, SIGABRT, etc.)
* Normal server shutdowns are not reported as crashes
* Reports are sent securely to RagnaSync servers
* Only you have access to your server's crash data


# Issues & Notifications

Get alerted when something affects your Discord bot or Ragnarok server

RagnaSync keeps an eye on your setup and tells you when something needs attention — a connection problem with your Discord bot, a spike of errors on your server, or you nearing your usage limit. You'll find these under **RagnaSync → Notifications** and **Ragnarok → Issues**.

***

## Notifications

The **Notifications** page is your inbox for anything affecting your Discord bot or Ragnarok client. The menu item carries a badge with your unread count — and it turns **red** when something critical is waiting.

* **Unread / Read filters** let you focus on what's new.
* **Mark as read** (one at a time, or all at once) clears items you've dealt with.

Typical notifications include usage warnings (80% / 95% / 100% of your limit — see [Usage & Billing](/account/usage-and-billing)), billing events, and connection or configuration problems.

***

## Your alerts channel in Discord

RagnaSync also posts its alerts straight into your Discord server, in a private **`#ragnasync-alerts`** channel that only your admins can see — usage warnings, your server going offline, message-delivery problems. It creates the channel for you the first time it has something to tell you, and if you delete it, it simply makes a new one the next time.

To create that channel the bot needs the **Manage Channels** permission. If you added the bot a while ago, it may not have it yet — just [re-add the bot with the install link](/getting-started/installation#add-the-ragnasync-discord-bot) and approve the permissions. Your settings stay exactly as they are.

Until the channel exists, RagnaSync sends those alerts to the **server owner by direct message** instead, so nothing is missed.

{% hint style="info" %}
Blocked DMs from server members? Then those direct messages can't reach you — granting **Manage Channels** is the reliable fix.
{% endhint %}

***

## Issues

When the [monitoring tool](/monitoring/server-monitoring) is running, RagnaSync turns recurring server problems into **Issues** under **Ragnarok → Issues** — each one a distinct problem it has detected, tracked over time rather than as a wall of repeated log lines.

An issue keeps a running **occurrence count** and a **priority**, so a problem that's happening more and more, or has been escalated to high priority, is easy to spot and act on. This is the actionable layer on top of your raw [Console Logs](/monitoring/server-monitoring/console-logs).

***

## Email alerts

Don't want to keep checking the dashboard? Turn on **email alerts** from the **Notifications** page (available once monitoring is configured). Alerts go to your **billing email address**, and you choose exactly what triggers one:

| Alert                          | Sends an email when…                                                                         |
| ------------------------------ | -------------------------------------------------------------------------------------------- |
| **New issue detected**         | A brand-new problem appears                                                                  |
| **Issue reopened**             | A previously-resolved issue comes back                                                       |
| **Occurrence spike**           | An issue's occurrence count crosses a threshold you set (and again at 10, 20, 50, 100, 500…) |
| **Priority escalated to High** | An issue is bumped up to high priority                                                       |

{% hint style="info" %}
Alert emails go to the address on your **Subscription** page. If you haven't set one, add it there first — otherwise there's nowhere to send them.
{% endhint %}

***

## Where it fits

Issues & Notifications is the alerting layer of [Server Monitoring](/monitoring/server-monitoring). It works alongside [Console Logs](/monitoring/server-monitoring/console-logs) (the raw errors and warnings) and [Crash Logs](/monitoring/server-monitoring/crash-logs) (full crash reports).


# Pricing

Simple pricing plans that grow with your server

Choose the plan that fits your server's needs. All plans include unlimited Discord servers and channels.

***

## Plans

### Free

**€0.00/month**

Perfect for small servers and testing. Get started with all RagnaSync features at no cost.

| Feature            | Included     |
| ------------------ | ------------ |
| RagnaScript Events | 50,000/month |
| Discord Servers    | Unlimited    |
| Channels           | Unlimited    |

***

### Pro (Most Popular)

**€4.99/month**

For growing servers with active communities. All features with increased limits.

| Feature            | Included      |
| ------------------ | ------------- |
| RagnaScript Events | 300,000/month |
| Discord Servers    | Unlimited     |
| Channels           | Unlimited     |

***

### Unlimited (Best Value)

**€9.99/month**

No limits. Never worry about usage again.

| Feature            | Included  |
| ------------------ | --------- |
| RagnaScript Events | Unlimited |
| Discord Servers    | Unlimited |
| Channels           | Unlimited |

***

## What's a RagnaScript Event?

Each time your server sends a message to Discord through RagnaSync, it counts as one event. This includes:

* Channel chat messages
* Drop notifications (player, homunculus, mercenary)
* MVP/Boss kill announcements
* @command logs
* @request support messages
* Custom NPC script messages
* WoE events (captures, kills, attendance)

{% hint style="success" %}
**These features are FREE and don't count toward your limit:**

* Login/logout tracking
* Character updates
* System heartbeats
  {% endhint %}

{% hint style="info" %}
**Most servers stay well within the Free tier.** The 50,000 messages & events/month limit is generous for small to medium communities.
{% endhint %}

***

## All Plans Include

Every RagnaSync plan comes with:

* Professional installation (handled by our team)
* All RagnaScript features
* Channel chat bridging (game to Discord)
* Drop notifications
* MVP/Boss kill logging
* @command logging
* Login/logout tracking
* WoE integration
* Custom NPC scripts support
* Discord bot access

***

## Guarantees

**7-Day Refund** - Not satisfied? Full refund within 7 days, no questions asked.

**Free Installation** - Our team handles the technical installation at no extra cost.

***

## Feature Comparison

Want to see the complete feature list and upcoming roadmap?

[View Feature Comparison & Roadmap](https://discord.gg/FQjjeaMZK2)

***

## Ready to Get Started?

[Contact Us on Discord](https://discord.gg/FQjjeaMZK2)


# Usage & Billing

How event usage is counted, and how to manage your plan and credits

RagnaSync meters how many **events** your server sends to Discord each month and checks them against your plan. This page explains how that counting works, where to watch it, and how to manage your subscription and top up with credits.

New to the plans themselves? Start with [Pricing](/account/pricing) for the tiers and prices.

***

## What counts as an event

Every message RagnaSync delivers to Discord on your behalf is **one event**. That includes channel chat, drop and kill announcements, `@command` and `@request` logs, custom NPC messages, WoE events, and server-status reports from the [monitoring tool](/monitoring/server-monitoring).

A few housekeeping actions are **free** and never count toward your limit:

| Free (uncounted)                     | Why                                         |
| ------------------------------------ | ------------------------------------------- |
| Login / logout tracking              | Background bookkeeping, not an announcement |
| Character updates                    | Keeps avatars/names fresh                   |
| System heartbeats & connection pings | Health signals, not messages                |
| WoE setup handshakes                 | Preparation, not the scored events          |

{% hint style="info" %}
Counting is **eventually consistent** — usage is tallied continuously and reconciled a few times an hour, so the number you see is accurate to within a short window rather than to the exact second.
{% endhint %}

***

## Your monthly limit

Your limit comes from your plan:

| Plan      | Monthly events |
| --------- | -------------- |
| Free      | 50,000         |
| Pro       | 300,000        |
| Unlimited | No limit       |

The counter **resets at the start of each month** — a new month starts you fresh at zero. On the Unlimited plan there's nothing to track; events never gate.

### When you get close

On a paid plan, RagnaSync warns you before you run out:

| At       | What happens                                                                                    |
| -------- | ----------------------------------------------------------------------------------------------- |
| **80%**  | "Approaching limit" notification                                                                |
| **95%**  | "Critical usage" notification                                                                   |
| **100%** | "Limit reached" — counted events are paused until you add credits, upgrade, or the month resets |

These arrive both as dashboard [notifications](/monitoring/server-monitoring/issues-notifications) and as emails to your billing address (no more than one of each per day, so you're never spammed). Free events keep flowing even at the limit — only counted events pause.

***

## Topping up with credits

Need a bit more headroom without changing plans? Buy a **credit pack**. Credits are added on top of your monthly limit and, unlike the monthly allowance, **don't expire at month's end** — they sit in your balance until used.

| Pack               | Credits | Price  |
| ------------------ | ------- | ------ |
| 50K Event Credits  | 50,000  | €1.99  |
| 150K Event Credits | 150,000 | €4.99  |
| 500K Event Credits | 500,000 | €14.99 |

Buy them from the **Subscription** page (see below). Your available total is always *monthly limit + remaining credits*.

***

## Managing your subscription

Open **RagnaSync → Subscription** in your dashboard to:

* **Upgrade or downgrade** between Free, Pro and Unlimited.
* **Buy credit packs.**
* **Update payment details** or open the billing portal.
* **Cancel or resume** — cancelling keeps your paid plan until the end of the current period, then drops you to Free automatically.

{% hint style="info" %}
Make sure your **billing email** is set on this page — it's where usage warnings and issue alerts are sent.
{% endhint %}

***

## Usage history

**RagnaSync → Usage History** lists individual recorded events so you can see what's consuming your allowance. Each row is tagged by source:

| Source       | Where it came from                                    |
| ------------ | ----------------------------------------------------- |
| **Ragnarok** | A script running on your game server                  |
| **Discord**  | A message relayed from Discord into the game          |
| **API**      | A direct API call, such as a monitoring/status report |

If a month's usage looks higher than expected, this is the place to find out which feature is driving it.


