Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
cf2c126
feat(bots): server-side bot AI foundation (phase 1)
nolt Jun 27, 2026
603b29e
feat(bots): persistent bot population with generation and reset (phas…
nolt Jun 27, 2026
2dbbcbf
feat(bots): cross-map warp to level-appropriate maps (phase 4)
nolt Jun 27, 2026
ac905fc
feat(bots): equip + distribute stats, hunt survivable monsters (phase 5)
nolt Jun 27, 2026
d286363
fix(bots): keep bots alive and moving with healing, survivable gear a…
nolt Jun 28, 2026
3045e06
fix(bots): exempt bots from the offline PC-Cafe Zen fee
nolt Jul 2, 2026
3dbd4ec
bugfix: replace duplicate GUID
eduardosmaniotto Jul 7, 2026
ad0c5a1
Merge branch 'bots' (server-side AI bot players, phases 1-5)
nolt Jul 8, 2026
271c10d
fix(bots): adapt to master offline API after merge
nolt Jul 7, 2026
2c36430
bots: scale to 250 and add class/level-appropriate skills
nolt Jul 7, 2026
1270e37
bots: home in on nearest live monster so they actually hunt
nolt Jul 8, 2026
5ca0430
bots: class buffs and heals, equipment progression, smarter and safer…
nolt Jul 8, 2026
dfa7b52
bots: don't unlock character classes for bot accounts
nolt Jul 8, 2026
3483107
bots: durability fixes and more human behavior
nolt Jul 8, 2026
af06203
bots: hunt in small parties like real player groups
nolt Jul 8, 2026
b3ce055
bots: presence rotation over the day
nolt Jul 8, 2026
8b16367
bots: town economy - sell junk loot, buy potions with zen
nolt Jul 8, 2026
4fa5f37
bots: level range up to 250 with class evolution at 200
nolt Jul 8, 2026
059e0f8
bots: satisfy StyleCop member ordering and documentation rules
nolt Jul 8, 2026
3e137fa
Fix bot starter weapon selection picking ammunition instead of a bow
nolt Jul 8, 2026
a246780
Judge bot target safety by real monster stats instead of level
nolt Jul 8, 2026
ab08db1
Fix offline handler tests after bot AI changes
nolt Jul 8, 2026
a5ab05f
Address static analysis findings in bot code
nolt Jul 8, 2026
8739d83
Extend bot self-defense grudge to five minutes
nolt Jul 8, 2026
4372081
Let bots fight back at players with their strongest skill
nolt Jul 8, 2026
469ab24
Make bots avenge their death against player killers
nolt Jul 8, 2026
44858f4
Make bots reset-aware on servers with the reset feature
nolt Jul 8, 2026
c9098f0
Gate bot map access by the warp list like for players
nolt Jul 8, 2026
97f88c8
Model bot stat builds on real player metas
nolt Jul 8, 2026
582a07b
Let bots attack players only when it is free of PK consequences
nolt Jul 9, 2026
a4e181e
Let bots upgrade their gear with looted jewels
nolt Jul 9, 2026
68c73a7
Let bots accept party invitations from players
nolt Jul 9, 2026
1661356
Correct a wrong claim in the jewel handler doc
nolt Jul 9, 2026
ffbf727
Correct stale and imprecise claims in bot code comments
nolt Jul 9, 2026
a98ca74
Stop bots from risking Souls above +6 on items without luck
nolt Jul 9, 2026
907dbdd
Add the master (third generation) stage for bots
nolt Jul 10, 2026
40f09c1
Add the missing Designer file for the AddAccountIsBot migration
nolt Jul 11, 2026
db0e796
Grant bots their wings at the classic level milestones
nolt Jul 13, 2026
dc051cc
Let party bots follow their leader into the mini game events
nolt Jul 13, 2026
fce4773
Fix the bot trade dead ends, treasure hoarding and travel churn
nolt Jul 13, 2026
013dddf
Fix the bot equipment loop, its stuck casters and the stop leaks
nolt Jul 13, 2026
a77fed1
Let a player recruit a bot which hunts in a bot party
nolt Jul 14, 2026
f97c7ea
Split the bot population over the game servers
nolt Jul 14, 2026
5576a68
Merge remote-tracking branch 'upstream/master' into feature-bots
nolt Jul 14, 2026
209b214
Remove dead code from two bot tests
nolt Jul 14, 2026
2b2ef4b
Use a lambda for the capacity callback in a bot test
nolt Jul 14, 2026
0bcc69f
Restart a bot whose AI keeps failing
nolt Jul 14, 2026
9165963
Keep master points off the weapons a bot does not use
nolt Jul 14, 2026
98e0b59
Document the bot feature
nolt Jul 14, 2026
48869d6
Hunt the monsters which pay a mastered bot
nolt Jul 14, 2026
be6f6f6
Spend the points a bot is owed when it enters the world
nolt Jul 14, 2026
4bad386
Keep master points off the bonuses which only work in PvP
nolt Jul 14, 2026
a81258b
Fix the markdown of the mastered-bot hunting paragraph
nolt Jul 14, 2026
93cd0bd
Hash bot passwords with a minimal BCrypt work factor
nolt Jul 15, 2026
c90ae98
Explain the deliberate local time in the presence rotation
nolt Jul 15, 2026
7e888b0
Abort the bot's travel path wait on shutdown
nolt Jul 15, 2026
2f3e0b0
Remove a stray duplicate summary tag in the bot feature plugin
nolt Jul 16, 2026
3ffd86a
Name the bot startup phases with an enum instead of raw 0/1/2
nolt Jul 16, 2026
b33d80b
Key the offline monster stats cache by monster number
nolt Jul 16, 2026
520ce4d
Fold the bot's last aggressor into an immutable record
nolt Jul 16, 2026
c139cb4
Refresh the stale proof-of-concept wording in the bot configuration
nolt Jul 16, 2026
1686b52
Explain the one-shot fault-restart latch in the bot player
nolt Jul 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
257 changes: 257 additions & 0 deletions docs/Bots.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,257 @@
# Server-side AI Bots

Bots are persistent, autonomous characters which populate a server like real
players: they hunt with the skills of their class, level up, spend their points,
keep their buffs up, pick up and wear better gear, restock in town, group up,
defend themselves, and come and go over the day. A player who meets one should
not be able to tell it apart from a quiet human player.

They are driven entirely by the server. No game client is involved and no
packets are exchanged: a bot is a connection-less `OfflinePlayer` — the same
class which keeps a character playing after its owner logs out — with a
navigator on top which gives it a life of its own.

The feature is disabled by default. Enabling it is always a deliberate act of
the server admin.

## How it works

**Bots are ordinary accounts.** Each one is a regular `Account` with the `IsBot`
flag, holding up to five characters with generated names, levels, classes,
stats, skills and starter gear. They are created once, saved like any other
character, and reloaded on every start — a bot's progress belongs to the
server's data, not to a process. Every bot animates one character in its own
persistence context, so the characters of one account can play at the same time.

**Two ticks make up the mind of a bot.** The offline MU Helper AI runs twice a
second and does what it does for a human's offline session: attack, heal, buff,
pick items up. On top of it, a bot navigator runs every second and decides the
things an offline session never had to: where to hunt, when to travel or warp,
when to go shopping, whom to follow. Everything a bot changes about itself —
equipping, jewels, resets, master points — is queued into the AI tick, so it
never runs while the combat handler is working on the same character.

**Bots act through the regular player actions.** Moving an item, talking to a
merchant, consuming a jewel, entering an event: a bot goes through the same
actions with the same validations a client's packet would trigger. It cannot do
anything a player could not do, and rule changes apply to bots for free.

**The population is split over the game servers.** Bots count towards the player
count of their server exactly like players do, and a server which reached its
maximum player count turns new clients away — so a population large enough to
fill a server would lock the players out of it. `Bot capacity %` (60 by default)
is the share of a server's player limit its bots may occupy; the rest stays
reserved for the players. Which accounts a server animates is a pure function of
the account index and the set of configured game servers, so every server
computes the same split without asking the others — which also holds when each
game server runs as its own process. Exactly one server generates the
population, so accounts and character names are never created twice. Accounts
which do not fit stay offline until the deployment offers the room for them.

## Configuration

The *Bots* feature plugin, in the "Feature Plugins" section of the admin panel:

- **`Enabled`** — spawns the bots after the server has started. Off by default.
- **`Number of accounts`** — how many bot accounts to maintain.
- **`Characters per account`** — how many characters each account animates at
once (at most five).
- **`Bot capacity %`** — the share of a game server's maximum player count its
bots may occupy; the rest is reserved for the players.
- **`Presence rotation`** — bots log in and out over the day instead of all
being online around the clock.
- **`Min. online share %`** — how much of the population stays online at the
quietest hour.
- **`Bots pay reset costs`** — whether bots pay the configured zen and item
costs for their resets. Off by default: they take no part in the economy those
costs are balanced for.
- **`Reset bots`** — purges and regenerates the whole population on the next
start, then clears itself.

## What a bot does

### Hunting and travelling

A bot hunts where the monsters actually are: it scans its surroundings for live
monsters instead of walking to a spawn point which may be empty. Long distances
are covered with a cached route over the whole map, walked a few steps at a
time, so the bot can stop and fight on the way.

It only engages what it can survive. The decision is made against the monster's
real damage, defense and attack rate versus the bot's own defense, health and
chance to be hit — a monster's nominal level says little about its punch on the
high-end maps. An agility build's dodge therefore counts as the defense it
really is, and better gear opens tougher maps, exactly like for a player.

Map access follows the game's warp list: a bot enters a map only if its level
may legally warp there, and a bot which finds itself on a map it may not be on
(after a reset, for instance) leaves for the best map it may use. The map it
reached is persisted, so a restarted bot wakes up where it stopped.

### Fighting and progressing

A bot fights with the strongest skill of its class it has learned and can pay
for; casters keep their distance and drink mana. Skills are learned against the
game's own requirements — total energy, leadership, character level — at
generation and again on every level-up, and the class buffs are kept up on their
own.

Level-up points follow a per-class build modelled on what players actually play:
an agility/shield meta on reset servers, guide-style builds on classic ones,
chosen automatically by whether the reset feature is configured. Classes with
two viable archetypes (a warrior or a wizard Magic Gladiator, a pure or an
energy Blade Knight) roll one per bot, and a stat which hits a server's maximum
overflows into the rest of the build.

Bots evolve like players do. The second-generation class change happens at level
200 — the same assignment the class-change quest performs — and the master class
at the game's maximum level, followed by a relog, because the master attributes
only mount when a character enters the world. Master points go into the master
skill tree through the regular action, with its rank gates and skill
requirements, preferring passives which boost a stat and strengtheners of skills
the bot actually uses; a bonus tied to a weapon type the bot does not fight with
is never bought. On a server with the reset feature, a bot only masters once its
reset limit is exhausted — while resets remain, resetting is what players do, so
the bots do it too.

A mastered bot changes what it hunts. Master experience is only granted for
monsters of at least `Minimum monster level for master experience` (95 in the
default configuration), and a character at the maximum level earns nothing
else — so below that line a kill pays a mastered bot nothing at all. It
therefore looks for maps which hold such monsters, and takes the weakest ones
above the line rather than the strongest: master experience hardly grows with
the monster's level, so the cheapest kill above it is the best one. Those
monsters carry 40.000+ health, well beyond the hit budget a bot's usual gear
affords, so the budget is stretched for them — a slow fight it survives beats a
quick one worth nothing. What is not stretched is its survivability: a monster
whose hits the bot cannot take is refused, mastered or not.

### Items and money

Dropped gear is judged before it is picked up: a bot collects what it can wear
and what is worth money, and leaves the rest lying. An upgrade is put on through
the regular move-item action, with the whole swap planned first — which slot,
and which pieces have to come off, including the other hand for a two-handed
weapon. If the engine refuses the equip after all, the old gear goes straight
back on. The replaced piece stays in the backpack and is sold on the next trip
to town, rather than being dropped where the next bot would pick it up again.

When the backpack fills up or the potions run low, the bot walks to a merchant,
sells its junk, and buys refills from the proceeds while the shop dialog visibly
occupies it — on a map without a merchant it warps home first, like a player
would. Looted Jewels of Bless, Soul and Life are spent on its own equipment
through the regular consume action, with the same success rates and failure
penalties a player faces, and with the caution a player shows: a Soul is only
risked where a failure cannot destroy the item's level.

Wings do not drop, so bots earn them at the classic milestones instead — the
first pair at level 180, the second at 280 and the third, master-only pair at
400. Which class wears which pair comes from the item data, and the outgrown
pair is destroyed rather than dropped.

### Mini game events

A bot never enters Blood Castle, Devil Square or Chaos Castle on its own — it
has no ticket and does not farm for one. It enters when a player who leads a
party with bots enters with their own ticket: the leader's entry legitimizes the
visit for the whole group.

Each bot is checked against the entry restrictions a player faces (the level
bracket, including the separate one for the special characters, the master-class
requirement, the player-killer rule). A bot which does not qualify leaves the
party and goes back to its own life instead of blocking the entry.

Inside, its open-world routine is suspended: no shopping, no map changes, no
boredom, no grudges. It fights what the event throws at it and keeps up with the
leader. Chaos Castle is a free-for-all, so there the other participants are
targets like everyone else — and a fight inside leaves no grudge outside. A bot
which dies respawns in the safezone like a player, which takes it out of the
event; the survivors are warped out when the event ends.

### Company and rhythm

Bots hunt in parties of two to five, grouped by level so the whole party can
hunt the leader's maps. The elf heals, the buffs are shared, the party
experience bonus applies. Parties re-form every hour.

A player may invite a bot into their own party: it accepts after a human-like
pause of a few seconds, provided the level gap is sane and it is not in the
middle of an errand. A living player takes precedence over the bot's own company
— a bot hunting with other bots leaves them for the inviter, and breaks that bot
party up if it was leading it, so a player never has to guess which bot happens
to be free. In a party the bot follows its leader, defers a due reset, and
eventually leaves politely: when the leader enters a map it may not access,
before its own logout, or simply when it gets bored.

A bot fights back when a player attacks it, but only as far as the game's own
PvP rules allow: inside the active self-defense window, or against a player
already flagged as a killer. It can therefore never be provoked into becoming an
outlaw that players could farm for free. It remembers who hit it, and a killed
bot walks back to its killer — waiting for a legal opening rather than taking
one.

Over the day, the presence rotation logs bots in and out: fewest in the early
morning, most in the evening, and never more than one at a time, so the
population ebbs and flows instead of appearing and vanishing in blocks.

### Keeping itself alive

The engine's attribute system is not thread-safe, and a lost race can corrupt a
character's attribute graph for good. A bot which hits it stops playing and
throws on every following tick. Rather than leave it lying there, a bot counts
the ticks which fail in a row and, after twenty of them, has itself restarted: a
fresh login rebuilds the attribute graph and heals it — the same thing a player
would do. A single failing tick is skipped, as before.

## What it costs

Measured on a 12-core host, as a rough guide for capacity planning:

| Population | CPU | Memory |
| --- | --- | --- |
| 250 bots | ~0.35 core | ~760 MB |
| 1100 bots | ~1.7 cores | ~1.2 GiB |

Generating a fresh population costs about a second per account (the password
hash dominates); starting an existing one of 1100 bots takes some 15 seconds.

## Known limitations

- **The engine's races are hit more often.** Neither `MagicEffectsList` nor
`ComposableAttribute` is thread-safe, and a thousand bots run into them more
often than human players do: a few caught exceptions per minute. A bot whose
attribute graph gets corrupted restarts itself (see above); a real fix belongs
into the engine, not into the bots.
- **Master skills which cost ten points at once are never learned.** A bot
invests every point as it earns it, so it never holds ten of them, and the
branches of the tree behind such a skill stay untouched.
- **The Summoner's enemy debuffs (Sleep, Weakness, Innovation) are unused.**
Deliberate: they would be cast through the buff rotation, which would have the
bot put itself to sleep. A cast-on-enemy path in the combat handler would be
needed.
- **Bots never buy equipment.** They wear what they find, so their gear lags
behind their level, and a bot at the maximum level is weaker than a player of
the same level would be. It is the reason a mastered bot needs a stretched hit
budget to reach the monsters which pay master experience at all. Letting bots
spend their money on gear would close the loop; they earn plenty of it.
- **Bots do no quests and do not trade with players.** Deliberate scope. The
quests which matter for progression (the class changes) are performed
directly, and trading would be an abuse surface.
- **Adding a game server to a running deployment does not spread the bots onto
it before a restart.** Deliberate: moving a bot between two running servers
would animate one account from two persistence contexts, which corrupts the
character.

## Enabling it on a server

1. Enable the *Bots* plugin and set the number of accounts. Each account
animates up to five characters, so 50 accounts × 5 = 250 bots.
2. Check that the population fits. The bots of a game server may occupy
`Bot capacity %` of its player limit — with the default of 60 %, a server for
1000 players hosts up to 600 bots. What does not fit stays offline, and the
plugin says so in the log: raise the player limit, raise the share, or add a
game server, over which the population then spreads by itself.
3. Restart the server. The population is generated on the first start and
reloaded afterwards.
4. To build a fresh population, set `Reset bots`: it purges the old one,
generates a new one, and clears the flag again.
3 changes: 3 additions & 0 deletions docs/Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,9 @@ database (e.g. RavenDB).

* [Master Skill System](MasterSystem.md): Description about the master skill system

* [Server-side AI Bots](Bots.md): Description about the bots which populate
the server

* [GameMap](GameMap.md): Description about the GameMap implementation

* [Progress](Progress.md): Information about the feature implementation
Expand Down
7 changes: 7 additions & 0 deletions src/DataModel/Entities/Account.cs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,13 @@ public class Account
/// </summary>
public bool IsTemplate { get; set; }

/// <summary>
/// Gets or sets a value indicating whether this account is a server-side bot account.
/// Bot accounts are generated and maintained by the bot feature; this flag is the reliable
/// marker used to load them on startup (instead of regenerating) and to purge them.
/// </summary>
public bool IsBot { get; set; }

/// <summary>
/// Gets or sets the characters.
/// </summary>
Expand Down
Loading