Population Engine (Advance Fake Players)
A drop-in fake-population system for rAthena that fills your Ragnarok Online server with persistent, AI driven shell players making every zone feel alive without a single real player online.
by Louis T Steinhil Β· listed Oct 29, 2026
Population Engine
- A drop-in fake-population system for rAthena that fills your Ragnarok Online server with persistent, AI driven shell players making every zone feel alive without a single real player online.
What Is It?
- Population Engine (PE) spawns virtual player characters (called "shells") that look, walk, fight, sit, and vend exactly like real players to any connected client. They are not monsters they appear in the player list, have names, equipment, classes, and behaviors. They require zero client modifications and work with any standard rAthena compatible RO client.
src/map/
βββ population_engine.cpp β Core engine (spawning, timers, vendors, arena)
βββ population_engine.hpp β Public API
βββ population_engine/
βββ core/
β βββ population_engine_core.hpp β All types, enums, constants
β βββ population_shell_state.hpp β Per-shell runtime struct (sd->pop)
β βββ pe_perf.hpp / pe_perf.cpp β RAII performance profiler
βββ config/
β βββ population_config.hpp/.cpp β YAML DB loaders and lookup functions
βββ runtime/
βββ population_engine_combat.cpp β Combat AI tick
βββ population_engine_path.cpp β Wander/movement AI
βββ population_shell_runtime.cpp β Target tracking, hotspot seeding
db/
βββ population_engine.yml β Job profiles, behaviors, gear, skills
βββ population_spawn.yml β Map lists and population targets per zone
βββ population_vendor_pop.yml β Jobs used for vendor shells
βββ population_vendors.yml β Vendor inventories + map placement rules
βββ population_names.yml β Name generation profiles
βββ population_gear_sets.yml β Shared gear pools referenced by profiles
βββ population_chat.yml β Chat phrases per behavior
βββ population_pvp.yml β Arena team definitions
βββ population_skill_db.yml β Skill usage rules per job
conf/battle/
βββ population_engine.conf β Runtime tuning (counts, timing, features)Setup & Configuration
- Build
- Add population_engine.cpp (unity build root) to your map-server project. It pulls in all subfiles via #include. No CMake changes required beyond adding the one .cpp.
- Database
- Copy all db/population_*.yml files into your server's db folder.
- Battle Config
- Edit population_engine.conf:
- Setting Default Description
population_engine_enable on Master on/off switch
population_engine_max_count 500 Global shell cap
population_engine_autosummon_batch_size 100 Shells spawned per 10s tick (0 = unlimited)
population_engine_vending_enable on Vendor shells open real stalls
population_engine_name_bot_fallback off Use syllable names instead of Bot_<id>
- Configure Populations
- Edit population_spawn.yml β define which maps get which shell profiles and how many:
-
- Configure Vendors (optional)
- Edit population_vendors.yml to define VendorPlacement entries restricting vendors to specific maps with bounding boxes, spacing, and caps.
- In-Game Management
-
Shell Behaviors
- Behavior can be overridden per map category β e.g. a Combat job becomes Vendor when it spawns in a town.
- Behavior Description
Combat Actively seeks and fights monsters using skill rotation
Guard Stays near spawn point, only attacks if approached
Wander Roams the map aimlessly (progressive-vector algorithm)
Support Follows and buffs nearby shells/players
Social Walks around, emotes, chats
Vendor Sits and opens a real vending stall with configurable stock
Sit Stationary decoration shell
AI Skill Condition System
- Each shell job has a full skill ruleset defined in population_skill_db.yml. Every skill entry has a condition gate that controls when the shell is eligible to use it β evaluated every combat tick with zero scripting overhead.
- credits to Singe Horizontal: For Expanded AI Conditions (https://rathena.org/board/topic/130266-mod-expanded-ai-conditions/)
Flat (Scalar) Conditions
32 named predicates cover the full range of tactical decisions:
- Predicate Description
hp_below / hp_above Self HP% threshold
sp_below / sp_above Self SP% threshold
enemy_hp_below / enemy_hp_above Target HP% threshold
enemy_status / not_enemy_status Target has/lacks a status effect (SC name)
self_status / not_self_status Self has/lacks a status effect
ally_hp_below Any ally in range below HP%
ally_status / not_ally_status Any ally in range has/lacks a status effect
distance_below / distance_above Range to target in cells
melee_attacked A melee attacker is in range and targeting this shell
range_attacked A ranged attacker is targeting this shell
self_targeted Any enemy is specifically targeting this shell
self_being_cast_on A skill is being cast at this shell (proactive dodge window)
enemy_casting Target is mid-cast
enemy_casting_ground Target is casting a ground AoE
enemy_hidden A hidden/cloaked enemy is in detection range
enemy_count_nearby Enemy count in range meets threshold
ally_count_nearby Ally count in range meets threshold
enemy_element Target's element matches (Water/Fire/Wind/Earth/Holy/etc.)
enemy_race Target's race matches (Brute/DemiHuman/Undead/etc.)
enemy_is_boss Target is an MVP or boss-class monster
map_zone Shell is in a town / field / dungeon
has_sphere Shell has β₯ N spirit spheres
cell_has_skill_unit Shell's current cell has an active skill unit
after_skill Shell's previous successful cast was a specific skill enables combos
damaged_gt Shell received a single hit exceeding N% of max HP enables burst reactions
skill_used A specific skill was used against this shell in the last 3 seconds mirrors mob_skill_db skillused
Expanded Conditions Boolean Logic Trees
For complex decisions, Condition: accepts a full boolean expression tree written in YAML:
Gates: AND, OR, NAND, NOR, XOR, NXOR
Leaf token grammar:
- [not_]<target>_<predicate>[_<cmp><value>]
Targets: self, enemy, ally
Comparators: lt, le, gt, ge, eq
Predicates: any status name (without SC_ prefix), hp_pct, sp_pct, distance, or any named predicate from the flat list
Examples:
-
Sequence form is sugar for AND: β so a simple list of tokens is an implicit all-must-pass check. Each leaf can be individually prefixed with not_ to invert it; entire subtrees can be inverted with NAND/NOR.
Invalid or unparseable trees evaluate to false (skill is skipped) and the error is logged at startup β no silent misfires.
Per-Skill Extra Controls
- Field Description
Rate 1β10000 chance weight this entry fires when conditions pass
State any / has_target / no_target β further narrows when the entry is eligible
Target current / self / ally β who the skill is cast on
Cooldown Per-entry cooldown in ms β prevents individual skill spam (e.g. Devotion: 30000)
AroundRange Randomize ground/AoE placement within N cells of the shell
Vendor System
- Static stock: YAML-defined item list (e.g. potions, arrows)
- Dynamic stock: Automatically derived from mob drop tables of source maps vendors in Morroc sell Pyramid drops, vendors in Izlude sell Byalan drops
- VendorPlacement: Hard restrict vendors to specific maps, bounding boxes, minimum spacing, and maximum count
- Price control: PriceMultiplier on dynamic stock, per-item Price override on static stock
Arena System
Shells can be assigned to PvP arenas via script (population_engine_arena_start). Inside an arena:
- Ally shells buff and heal each other (and real ally players)
- Enemy shells fight opposing team shells and real enemy players
- battle_check_target is hooked to return correct BCT flags for all combinations
Pros
- Zero client mods: works with vanilla RO clients
- No real DB saves: shells never touch char-server, registry, or achievement tables; safe
- Hot-reload: all YAML databases reload live via @populate reload
- YAML-driven: everything configurable without recompiling
- Map-aware behavior: town vs field vs dungeon behavior per job
- Real vending: vendor shells open actual vending_openvending stalls; players can buy
- Progressive vector wander: shells find walkable paths even in complex geometry
- Per-map party ID: shells on the same map auto-party for skill interactions
- Performance profiler built-in: PE_PERF_SCOPE macros with @populate timing
- FluxCP integration: live population count written to cp_population_stats SQL table (you need to edit fluxcp if you want population reflect on your website)
- Drift correction: shells that get teleported off their map auto-warp back
- Arena combat: real PvP arena simulation between shell teams + real players
Cons
- Single-threaded: rAthena's map-server is inherently single-threaded; shells are ticked only when real players are nearby (active AI range). Very high counts on very large player bases may cause frame-time pressure
- Memory-heavy at scale: each shell is a full map_session_data; see capacity table below
- No persistence between restarts: shells despawn and respawn fresh on every server boot (intentional)
- Combat AI is basic: it uses population_skill_db rules, not a full decision tree; don't expect pro-level PvP behavior
- Vendor stock is static per-spawn: stock does not deplete or restock dynamically after spawn (reloading YAML respawns them)
How Many Shells Can One Server Hold?
Memory Per Shell
- Each shell allocates a full map_session_data. In rAthena this struct contains inventory arrays, status blocks, skill data, and the custom s_population state:
- Component Approx Size
Core struct + status ~50 KB
Inventory (100 slots) ~6 KB
Cart (500 slots, vendor only) ~28 KB
Skill arrays ~10 KB
Misc pointers / linked lists ~5 KB
Total (combat shell) ~70β80 KB
Total (vendor shell with cart) ~100β110 KB
Capacity by RAM
- RAM Safe Shell Cap Notes
4 GB ~5,000 Shared with OS, MySQL, char-server, login-server
8 GB ~15,000 Comfortable for a full mid-pop server
16 GB ~35,000 High-density; tick budget becomes the limit beforeβ¦
Known issues
No known issues yet.
Having trouble with Population Engine (Advance Fake Players)? Ask about it β