GameServer.bb
The per-tick simulation core. Holds the live UpdateActorInstances loop that drives every actor's AI / movement / combat / water-tick / breath / faction-aggro / spawn-management each engine frame, plus the canonical ActorAttack damage engine that P_AttackActor bottoms out in. The companion files are Server.bb (entry point + helpers like UpdateAttribute / UpdateAttributeMax / UpdateReputation — see P_StatUpdate), Actors.bb (the ActorInstance Type + linked-list helpers), and ServerNet.bb (packet dispatch).
This page interleaves a modern conceptual overview of the architectural patterns that have emerged from recent hardening with the legacy function-by-function reference. For the per-function API the reference further down is authoritative; the overview is for orientation.
The dominant per-frame cost on a populated server. Walks every ActorInstance (NPCs + players), driving:
- Movement integration (X/Y/Z toward DestX/Y/Z, snap to terrain via
LinePick,SetArearebinds on portal cross) - AI (
AILookForTargets,AICallForHelp, target acquisition, faction-aggro propagation) - Combat tick (
AttackTimecooldowns, callsActorAttackwhen ready) - Water-tick (
Underwateraccumulator, breath consumption, water-damage application — walks the per-areaFirstWaterchain, not a globalFor Each ServerWater) - Spell recharge (every 100ms, all known spells across all actors)
- Spawn point processing (NPC respawn timers)
The loop uses the canonical iterator hazard pattern — DeferKillActor(AI, Null) enqueues a PendingKill work item rather than calling KillActor(AI, ...) mid-iteration. After the loop completes, ProcessPendingKills drains the queue. This prevents the classic Blitz3D "Delete current For-Each element" iterator-cursor corruption.
Server-authoritative damage resolution. Four CombatFormula variants (config-set at boot):
CombatFormula |
Hit chance | Damage formula |
|---|---|---|
1 (Normal) |
90% | weapon.Damage ± strength-rolled bonus, critical 1/10 (×2). Armour subtracts GetArmourLevel + Resistances[DamageType] - 100 + ToughnessStat / 8. |
2 (No strength bonus) |
90% | weapon.Damage flat. Critical 1/10 (×2). Same armour formula. |
3 (Multiplied) |
90% | weapon.Damage × Strength. Critical 1/10. |
4 (Attack script) |
N/A | Delegates to a ThreadScript("Attack", "Main", attacker, victim) — content authors implement the formula in .rsl. No range/damage check server-side. |
See P_AttackActor for the wire-protocol view (the H/Y/O broadcast tuple, Damage+1 encoding, observer re-sync via P_StatUpdate).
The critical-damage notification is the only place in the engine that emits P_ChatMessage Chr$(250) custom-RGB lines outside of BVM_OUTPUT (Chr$(255) + Chr$(225) + Chr$(100) peach/orange). See P_ChatMessage.
Several broadcast hot-paths historically walked Each ActorInstance with an inner filter (If A2\RNID > 0, If A2\Leader = NPC, etc.). For populated servers with many NPCs the inner filter dominated cost. The chain initiative replaced these with O(N-filtered) walks via per-purpose linked lists:
FirstOnlinePlayer/NextOnlinePlayer(head Global inActors.bb;NextOnlinePlayerField onActorInstance) — engine-wide chain of every player withRNID > 0. TheRNID > 0invariant is enforced at the call sites that invokeOnlinePlayerInsert(the login / character-spawn path), not insideOnlinePlayerInsertitself — so adding a new caller requires preserving the invariant. Used by/yell,/gm,/g,/pm,/allplayerschat broadcasts (replaced 7 loops inServerNet.bb'sP_ChatMessagehandler — seeP_ChatMessage) and the per-tickP_StandardUpdatebroadcast inUpdateActorInstances(the dominant per-frame cost on a populated server).FirstSlave/NextSlave(Fields onActorInstanceinActors.bb) — per-leader chain of pet/slave actors. Used by/petcommand dispatch (ServerNet.bb:266), theFreeActorInstanceSlavescleanup walk, and the pet-aggro broadcast inActorAttack. Maintained bySlaveLink/SlaveUnlink(also inActors.bb).FirstInZone/NextInZone(per-Area;FirstInZonehead Field onAreaInstanceinServerAreas.bb,NextInZoneField onActorInstanceinActors.bb) — per-area chain of every actor in thatAreaInstance. Used by the per-tick attribute-update broadcast (UpdateAttributeinServer.bb— seeP_StatUpdate) and theCase "O"observer broadcast inActorAttack.Area\FirstWater/ServerWater\NextWater(per-Area, inServerAreas.bb) — per-area chain ofServerWaterinstances. Used by the water-tick loop inUpdateActorInstances(replaced a nestedFor Each ServerWaterthat scaled poorly with water-instance count across all areas).
Every list has insertion/removal helpers (e.g. OnlinePlayerInsert / OnlinePlayerRemove for FirstOnlinePlayer) that maintain the chain invariants. The cleanup path in FreeActorInstance calls these unconditionally — so any actor that's ever been on a chain is correctly removed at free time. Regression tests for the chain semantics live in src/Tests/Modules/OnlinePlayerChainTest.bb and SlaveChainTest.bb.
Every Object.AreaInstance(AI\ServerArea) lookup in this file checks the result for Null before walking FirstInZone — an actor mid-warp (SetArea rebinding zones) returns Null and the broadcast simply doesn't fire that tick (the actor's in-memory state already updated; only the network replication drops). PRs #154 / #176 / #182–#188 covered this discipline sweep.
The same shape applies to Object.ActorInstance(handle) and Object.ServerWater(handle) results. See CLAUDE.md → "Handle-lookup Null discipline" for the canonical pattern. The water-tick site is a notable example: the initial scan captures Underwater = Handle(SW) for a live ServerWater, but ≥1 second may elapse before the damage branch runs — Object.ServerWater returns Null if the owning area was unloaded in that window, and the damage branch correctly skips (breath loss already ran and is fine on stale water).
SpawnActor[] / Spawned[] per-AreaInstance arrays drive NPC respawn. The pattern: Spawned[i] counts live instances of SpawnActor[i] in this AreaInstance; when an actor dies (KillActor decrement) and Spawned[i] < SpawnMax[i], the respawn timer counts down to spawn a fresh one. PreLoadSpawns (Server.bb:741) is the startup optimization that warm-spawns everyone before players connect; the live spawner in UpdateActorInstances handles ongoing respawns. Both code paths now guard ActorList(ua\SpawnActor[i]) = Null before CreateActorInstance — see CLAUDE.md → "Soft-fail" section.
This module contains the following types:
This module contains the following functions:
- CreateGameWindow
- GiveXP
- KillActor
- FireProjectile
- ActorAttack
- UpdateActorInstances
- SetArea
- LeaveParty
- CommandPet
- AILookForTargets
- AICallForHelp
Game.GameWindow (global)
This global contains the object representing the server's Game window, and is set when the window is created.
LastSpellRecharge (global)
This global stores the time at which the last spell recharge step occured. This is timed to happen ten times per second in the UpdateActorInstances function.
GameArea.Area (global)
This global contains the ServerAreas->Area object representing the zone selected for viewing in the server's Game window by the user.
LoginMessage$ (global)
This global stores the current login message displayed to players when entering the game.
GaneWindow (type)
This type represents a server Game window. It contains the handles of the window and all child gadgets.
CreateGameWindow.GameWindow()
Return value: Handle of the newly created GameWindow object
Parameters: None
This function creates a new GameWindow and all gadgets, then returns the handle.
GiveXP(A.ActorInstance, XP, IgnoreParty)
Return value: None
Parameters:
- A.ActorInstance - The actor instance to give XP points to
- XP - The number of XP points to give
- IgnoreParty - True/False flag for whether to skip sharing XP points with the actor instance's party (defaults to False)
This function gives XP points to a character, automatically sharing them with his party if he has one and the IgnoreParty parameter is set to False. If the actor instance has a leader, XP points automatically go to the leader instead. The LevelUp script is also called and a network message sent to the client, if the actor instance is an online player.
KillActor(A.ActorInstance, Killer.ActorInstance)
Return value: None
Parameters:
- A.ActorInstance - The actor instance to kill
- Killer.ActorInstance - The actor instance who killed the other
This function kills an actor instance. The Killer parameter may be null. If the killer is a valid actor instance, he will receive XP points and a reduced rating with the dead actor's faction if the setting is enabled. Any scripts waiting on a WaitKill command for this death are updated. If the killed actor instance was a player character, the player death script is called. If it was an NPC, any players in the same zone are informed of the death, the death script is called if present, and the actor instance is removed entirely from the game.
FireProjectile(P.Projectile, A1.ActorInstance, A2.ActorInstance)
Return value: None
Parameters:
- P.Projectile - The projectile to fire
- A1.ActorInstance - The actor instance firing the projectile
- A2.ActorInstance - The target actor instance
This function fires a projectile from an actor instance to a target actor instance. It will only run if neither actor instance is a non-combatant, and the target actor instance's rating with the firing actor instance's home faction is 50% or lower. First all players in the zone are sent network messages to display the projectile. Next the damage is calculated and applied to the target. If the target is an NPC set to "defensive" aggressiveness, it is made angry. Finally, if the target actor instance's health attribute has dropped to 0 or below after the damage, the KillActor function is called.
ActorAttack(A1.ActorInstance, A2.ActorInstance)
Return value: Success flag
Parameters:
- A1.ActorInstance - The actor instance performing the attack
- A2.ActorInstance - The actor instance being attacked
This function makes an actor instance attack another actor instance. If the attacking actor is using a ranged weapon, the attack is diverted through FireProjectile. Otherwise range, faction ratings and aggressiveness settings are checked to make sure that the attack is valid. If so, either damage is calculated according to one of 3 formulae, or the Attack script is called to handle the attack. If the Attack script is not used, weapon and armour damage are applied, and players in the zone are sent a network message telling them to display the attack. Finally, any pets of the attacking actor instance are also set to attack the target, and the target is killed with the KillActor function if its health has dropped to 0 or below.
UpdateActorInstances(Broadcast)
Return value: None
Parameters:
- Broadcast - True/False flag on whether to broadcast a network update to all players
This function is responsible to all updates to actor instances such as movement, AI, actor effects, etc. The first section loops through every active actor effect and checks if the actor instance it relates to is dead or deleted, or if the effect has expired. In either case the effect is removed from the game. Next the function decides whether a recharge cycle for spells (abilities) is required this frame. If so, the Recharge variable is set to True to ensure that it happens later in the function. The next section loops through every actor instance in the game and performs various updates. The first is to check whether the actor instance has entered a portal or a trigger. This only applies to online player characters (i.e. those with a RottNet ID above 0), and since checking every portal and trigger takes some time, it only applies to actor instances in the current UpdateArea.
Next, if the Recharge variable is True, recharge times for all memorised spells are reduced if above 0. Then the actor instance is moved towards its destination. Actor instances carrying a rider are merely moved to the same position as the rider, rather than moving in the usual way. The next step is to check if the actor instance is underwater. If so, any relevant water damage is applied. The last stage of the loop is to update AI if the actor is an NPC. The AI consists of various modes defined by the Actors->AI_... constants.
The final section runs the broadcast, if the Broadcast parameter is True. This involves looping through every online player character, and sending a StandardUpdate packet for every other actor instance in the same zone.
SetArea(A.ActorInstance, Ar.Area, Instance, Waypoint, Portal, X#, Y#, Z#)
Return value: None
Parameters:
- A.ActorInstance - The actor instance to warp
- Ar.Area - The ServerAreas->Area to send the actor instance to
- Instance - The instance of the zone to warp to
- Waypoint - The waypoint within the new zone to start the actor instance at (default -1)
- Portal - The portal within the new zone to start the actor instance at (default 0)
- X# - The X position within the new zone to start the actor instance at (default 0)
- Y# - The Y position within the new zone to start the actor instance at (default 0)
- Z# - The Z position within the new zone to start the actor instance at (default 0)
This function warps an actor instance to any zone or zone instance. The existence of the requested instance is checked, and if it is not present instance #0 will be quietly used instead. If the actor instance being warped is riding a mount, the mount is warped first using recursion. The next thing the function does it to update the players list on the server's Game window, if the actor instance is a human player, and if his old or new zone is the selected GameArea. Next, if the actor instance is not being warped back to the same zone he is already in, he is removed from the linked list of characters in the old zone, and added to the linked list of characters in the new zone. Next the actor instance's position is updated, either to a waypoint, a portal, or direct to a specific position. The final section of the function deals with telling player characters in the old and new zone that the actor instance has left or entered, and, if the actor instance is human, sending a network message telling his client about the new zone.
LeaveParty(AI.ActorInstance)
Return value: None
Parameters:
- AI.ActorInstance - The actor instance to remove from its party
This function removes an actor instance from its party, if it is a member of one. Any other players in the party will be sent a network message informing them of the event.
CommandPet(AI.ActorInstance, Command$, Params$)
Return value: None
Parameters:
- AI.ActorInstance - The pet (slave) actor instance to command
- Command$ - The command itself
- Params$ - Additional data for the command if required
This function gives an instruction to an actor instance who is the pet of another actor instance. Valid commands are wait/stay, follow/come, attack and name. The name command also requires the new name to be passed in the Params$ parameter.
AILookForTargets(AI.ActorInstance)
Return value: None
Parameters:
- AI.ActorInstance - The actor instance who should look for targets
This function causes an actor instance to search its immediate area for any valid targets to attack, and if found, to start chasing and attacking them. It is used several times in the UpdateActorInstances function.
AICallForHelp(AI.ActorInstance)
Return value: None
Parameters:
- AI.ActorInstance - The actor instance who should 'call' for help
- Server.bb — entry point. Hosts
UpdateAttribute/UpdateAttributeMax/UpdateReputation(the canonical broadcast helpers forP_StatUpdate) and theOnLostConnectionconsolidation point. - Actors.bb —
ActorInstanceType, linked-list helpers (OnlinePlayerInsert/Remove,SlaveLink/Unlink,FirstInZonechain maintenance),FreeActorInstance(the canonical actor-free path with all chain cleanups),SafeFreeActorInstance(the every-framePlayerTarget-clearing wrapper). - ServerNet.bb — packet dispatch.
P_AttackActorhandler bottoms out inActorAttackhere;P_ChatMessageslash commands include several that mutate per-actor state. - ServerAreas.bb —
Area/AreaInstanceType +Area\FirstWaterchain. Mid-warpObject.AreaInstance(...)Null discipline lives at this boundary. - Spells.bb —
SpellType +MemorisingSpellserver-side queue.ActorAttackconsumes weapon damage; the spell cast path lives inP_SpellUpdatehandler in ServerNet, not here. - ScriptingCommands.bb — BVM functions that invoke GameServer routines from script context (
BVM_KILLACTOR→KillActor,BVM_GIVEXP→GiveXP, etc.). Privilege gating on those callers prevents clicker exploits from reaching these mutators.
P_AttackActordetail — wire-protocol view ofActorAttack's output (H/Y/O sub-codes, Damage+1 encoding).P_StatUpdatedetail — broadcasts the attribute changes thatActorAttack/ water-tick / etc. produce.P_SpellUpdatedetail — the 11-gate spell-cast handler.- CLAUDE.md → "Iterator-during-iteration hazards" — the
DeferKillActor/ProcessPendingKillspattern. - CLAUDE.md → "Handle-lookup Null discipline" — the
Object.AreaInstance/Object.ServerWaterguards used throughout this file. - CLAUDE.md → "Soft-fail on server-controlled data" — why server packet handlers and per-tick code must not RuntimeError on data values.
This function finds all other NPCs who have a very high faction rating (90%+) with the specified actor instance and, if they are within their aggression range and not already involved in combat, causes them to come to the aid of the specified actor instance by helping to attack its current target.