The reset-aware level gate in BotPartyHandler rejected nearly every party invitation on servers with resets, because it folded reset count into the level scale (one reset ~= 400 points against a 500 cap). A veteran player inviting a freshly generated bot was always over the limit, so the invitation was silently declined. Remove the gate so a bot accepts any inviter who is alive and in the world, matching OpenMU's own party action; the situational safeguards (shopping, revenge, mini game, pending invite, human companion) stay in place.
328 lines
19 KiB
Markdown
328 lines
19 KiB
Markdown
# 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.
|
||
- **`Jewel stock per kind`** — how many Jewels of Bless, Soul and Life a bot
|
||
keeps of each kind. Above it, it stops picking them up and sells what it
|
||
already carries. Depends on the server's drop rates: on high rates a small
|
||
stock keeps the backpack free, on low rates a larger one is never reached
|
||
anyway.
|
||
- **`Potion stock charges`** — how many charges of healing and of mana potions a
|
||
bot restocks to at a merchant. Depends on what the shops sell: a server whose
|
||
potions come in stacks of 255 fills the target in a single purchase.
|
||
- **`Reset bots`** — deletes the whole population and generates it again, then
|
||
clears itself. With the number of accounts set to zero it deletes without
|
||
generating anything.
|
||
- **`Purge bots`** — deletes the whole population WITHOUT generating a new one,
|
||
and switches the feature off. It works with the feature enabled or disabled;
|
||
the switch-off is what makes it a purge, since the same pass would otherwise
|
||
create the population again right after deleting it. Clears itself afterwards.
|
||
|
||
## 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, and a bot travels on a player's terms:
|
||
it enters a map only if its level may legally warp there, it meets the map's own
|
||
requirements, and it pays the warp's fare out of its own Zen. 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.
|
||
|
||
Which map it goes to is drawn rather than maximized. The maps of a level band
|
||
differ by a few monster levels, so always taking the strongest one made it every
|
||
bot's answer and left the rest of the band deserted. The best map still wins
|
||
about a third of the picks and the runners-up split the rest, so the population
|
||
spreads over the maps a player of that band would choose between. Every map in
|
||
the draw is one the bot may legally reach, can afford, and which is better than
|
||
where it stands - the draw only decides between improvements.
|
||
|
||
A map can pass every check and still pay nothing: the monsters the bot may fight
|
||
are a rare kind among ones it must refuse, or other hunters empty the grounds
|
||
first. The bot notices the way a player would, by having landed no hit in
|
||
minutes, and steps down to easier ground one notch at a time until it finds
|
||
something it can farm; the regular map choice carries it back up as its level and
|
||
gear recover. A bot which cannot afford any trip at all walks home to its class
|
||
town for free, so it is never stranded on ground it cannot earn on.
|
||
|
||
### 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. Between skills worth about the
|
||
same it takes the one with the longer reach - the flat bonus of a spell is a
|
||
rounding error next to a high-level character's own damage, while three tiles of
|
||
range are three tiles at any level. Skills the game only activates during a
|
||
castle siege are left out, and so are a pet's skills unless the pet is actually
|
||
equipped: Plasma Storm draws its damage from the Fenrir, but the attribute behind
|
||
it is derived from the character's own stats, so nothing but the pet slot tells a
|
||
mounted character from one riding nothing. 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.
|
||
|
||
A skill the character cannot currently cast is passed over, in the attack
|
||
rotation and in the buffs alike. That is not the same as not having learned it: a
|
||
reset keeps every skill but takes back the level which unlocked it, so a veteran
|
||
back at level 12 still owns Swell Life, which asks for level 120. The game
|
||
refuses such a cast silently, so a character which kept trying would simply stand
|
||
there — buffing something that never takes effect, and never getting as far as
|
||
attacking.
|
||
|
||
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.
|
||
|
||
A merchant trip is the only moment a bot can turn loot into anything, so it goes
|
||
whenever it has something to gain there: the backpack is filling with junk, the
|
||
potions are running low, a jewel is waiting to be used, or a surplus is waiting
|
||
to be sold. Restocking needs the means to pay for it, though - Zen, or loot to
|
||
sell once it is there. A broke bot buys nothing, so the trip would leave it just
|
||
as short as it set out, and it would set out again instead of hunting, which is
|
||
the only way it could have earned the money. It picks the merchant which sells what it needs right now, and on a
|
||
map whose merchants sell no potions while it needs some, it warps home to a real
|
||
town instead. While the shop dialog visibly occupies it, the bot sells its junk,
|
||
repairs its gear — which is what earns the NPC's discount — and buys potions and,
|
||
where a shop offers them, jewels.
|
||
|
||
Only Jewels of Bless, Soul and Life are collected, and only up to the configured
|
||
stock: a bot cannot trade or craft, so any other kind would be a backpack slot it
|
||
never gets back. They 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.
|
||
|
||
A bot which reaches the server's maximum inventory money can no longer sell
|
||
anything — the money simply does not fit. What it cannot sell it keeps, and
|
||
destroys only what has no other way out: jewels beyond its stock, and, while the
|
||
backpack is genuinely full, junk gear. The repair bill is what normally keeps it
|
||
away from that limit in the first place.
|
||
|
||
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, as long as it is not in the middle of an errand. There is
|
||
no level gate — just like OpenMU's own party action, a bot accepts an inviter of
|
||
any level, since it is the player who invites and the bot leaves once it gets
|
||
bored. 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 deletes the old one,
|
||
generates a new one, and clears the flag again.
|
||
5. To stop the bots without losing them, uncheck `Enabled`: they log out within
|
||
a few seconds and nothing is deleted, so checking it again brings the same
|
||
characters back. To get rid of them for good, set `Purge bots` — it deletes
|
||
every bot account with its characters, items and storages, and leaves the
|
||
feature switched off.
|