NPCs & Factions¶
This page covers working with NPCs, mutants, squads, and the faction relation system from script.
NPC vs mutant¶
Anomaly has two broad categories of AI entities:
| Stalkers / humanoid NPCs | Mutants / creatures | |
|---|---|---|
| Class | CAI_Stalker |
Various monster classes |
| Check | IsStalker(obj) |
IsMonster(obj) |
| Death callback | npc_on_death_callback |
monster_on_death_callback |
| Faction | Yes (.community()) |
No |
| Dialog | Yes | No |
| Squad | Usually | Rarely (some creature packs) |
Death callbacks¶
The two most common NPC callbacks. Both follow the same pattern.
npc_on_death_callback¶
Fires when any humanoid NPC dies.
local function npc_on_death_callback(victim, who)
-- victim and who are game_object references
-- who can be nil (killed by environment / anomaly)
if not (victim and who) then return end
local victim_name = victim:name()
local killer_name = who:name()
printf("[my_mod] %s killed by %s", victim_name, killer_name)
-- Check if player was the killer
if who:id() == 0 then -- actor always has id 0 on the client
on_player_killed_npc(victim)
end
end
function on_game_start()
RegisterScriptCallback("npc_on_death_callback", npc_on_death_callback)
end
function on_game_end()
UnregisterScriptCallback("npc_on_death_callback", npc_on_death_callback)
end
monster_on_death_callback¶
Same signature, fires for mutants.
local function monster_on_death_callback(victim, who)
if not (victim and who) then return end
-- same pattern as npc_on_death_callback
end
Modifying incoming hits¶
npc_on_before_hit fires before damage is applied to an NPC. You can read the hit, modify it, or cancel it entirely. This callback requires the modded exes.
local function npc_on_before_hit(npc, hit, bone_id, flags)
-- hit.draftsman = source game_object (who fired the shot)
-- hit.type = hit type (hit.wound, hit.explosion, etc.)
-- hit.power = damage amount
-- bone_id = bone that was hit
-- Example: cancel all damage from the actor to a specific NPC
if hit.draftsman and hit.draftsman:id() == 0 then
if npc:name() == "my_protected_npc" then
flags.ret_value = false -- block the hit
end
end
-- Example: halve all damage to NPCs from explosions
if hit.type == hit.explosion then
hit.power = hit.power * 0.5
end
end
function on_game_start()
RegisterScriptCallback("npc_on_before_hit", npc_on_before_hit)
end
The equivalent actor_on_before_hit fires when the player is hit. Both callbacks require the modded exes.
NPC game_object methods¶
When you have an NPC as a game_object (from a callback or db.storage), the common methods:
-- Identity
npc:id() -- numeric ID
npc:name() -- internal name (unique per NPC)
npc:character_name() -- display name (for PDA/dialog)
npc:section() -- config section
npc:community() -- faction string, e.g. "stalker", "bandit", "dolg"
-- Status
npc:alive() -- true if alive
npc:health -- property (0.0–1.0)
npc:position() -- vector3
npc:game_vertex_id()
npc:level_vertex_id()
-- Sight
npc:see(obj) -- can NPC see this object?
-- Info portions
npc:give_info_portion(id)
npc:has_info(id)
npc:disable_info_portion(id)
-- Relations
npc:relation(other) -- game_object.enemy / .neutral / .friend
npc:sympathy() -- 0.0–1.0
npc:set_sympathy(v)
npc:force_set_goodwill(value, target)
db.storage — per-NPC Lua state¶
db.storage is a table keyed by object ID that holds each online NPC's Lua-side state. It's written by the NPC binder when the NPC enters the world, and cleared when it leaves.
-- Get the game_object for any online NPC by ID
local npc = db.storage[id] and db.storage[id].object
if npc and npc:alive() then
local pos = npc:position()
end
Key fields inside db.storage[id]:
| Field | Description |
|---|---|
.object |
The NPC's game_object |
.active_section |
Current behavior section (e.g. "walker@idle") |
.state_mgr |
State machine manager instance |
.move_mgr |
Movement manager instance |
.squad_id |
Squad ID (may not always be present) |
Note
db.storage[id] only exists while the NPC is online (within spawn distance). For offline NPCs, use alife_object(id) to get the server-side entity.
Factions and relations¶
Reading relations¶
local relation = game_relations.get_npcs_relation(npc1, npc2)
-- returns: game_object.friend, game_object.neutral, or game_object.enemy
if relation == game_object.enemy then
-- hostile
end
Setting relations¶
game_relations.set_npcs_relation(npc, db.actor, game_relations.FRIENDS)
-- game_relations.FRIENDS = 1000
-- game_relations.NEUTRALS = 0
-- game_relations.ENEMIES = -1000
-- Set NPC sympathy (0.0 = hate, 1.0 = love)
game_relations.set_npc_sympathy(npc, 0.8)
Community/faction goodwill¶
-- Actor's goodwill with a faction (-10000 to 10000)
local goodwill = db.actor:community_goodwill("dolg")
db.actor:set_community_goodwill("dolg", 1000)
-- NPC's faction community (short name)
local faction = npc:community() -- "stalker", "bandit", "dolg", "freedom", etc.
Squads¶
Squads are the server-side grouping of NPCs. They have their own se_abstract object accessible via alife_object(squad_id).
Getting an NPC's squad¶
local function get_squad(npc_or_se_obj)
return get_object_squad(npc_or_se_obj) -- helper from _g.script
end
-- Or manually from the server entity
local se_npc = alife_object(npc:id())
if se_npc and se_npc.group_id ~= 65535 then
local squad = alife_object(se_npc.group_id)
-- use squad
end
Iterating squad members¶
local squad = get_object_squad(npc)
if squad then
for member in squad:squad_members() do
-- member.id is the NPC's ID
local member_obj = db.storage[member.id] and db.storage[member.id].object
if member_obj and member_obj:alive() then
printf("squad member: %s", member_obj:name())
end
end
local leader_id = squad:commander_id()
local count = squad:npc_count()
end
Dialog system¶
Dialog conditions and actions are plain Lua functions called from dialog XML. The XML references them by script_text function name.
Condition function (precondition)¶
Returns true to show a dialog option, false to hide it.
-- In dialogs.script or your own script file (must be a global function)
function my_mod_can_give_quest(first_speaker, second_speaker)
-- first_speaker = NPC, second_speaker = actor (db.actor)
return not db.actor:has_info("my_mod_quest_given")
and first_speaker:community() == "stalker"
end
Action function (dialog result)¶
Called when the player selects a dialog option. No return value.
function my_mod_give_quest(first_speaker, second_speaker)
db.actor:give_info_portion("my_mod_quest_given")
alife_create_item("my_quest_document", db.actor)
news_manager.send_tip(db.actor, "my_mod_quest_start_tip", 0, first_speaker, 7000)
end
Registering in dialog XML¶
<dialog id="my_mod_quest_dialog">
<phrase_list>
<phrase id="0">
<text>my_mod_dialog_ask</text>
<next>1</next>
</phrase>
<phrase id="1">
<text>my_mod_dialog_response</text>
<precondition>my_mod_can_give_quest</precondition>
<action>my_mod_give_quest</action>
</phrase>
</phrase_list>
</dialog>
Common patterns¶
Give loot when an NPC dies¶
local function npc_on_death_callback(victim, who)
if not victim then return end
if victim:name() ~= "my_special_npc" then return end
-- Spawn items on their body (will appear when looting)
alife_create_item("my_special_document", victim)
alife_create_item("bandage", victim)
end
Check if the player killed an NPC¶
local function npc_on_death_callback(victim, who)
if not (victim and who) then return end
if who:id() ~= db.actor:id() then return end -- actor's client id is 0, but use :id() for clarity
-- Player made the kill
my_data.player_kills = (my_data.player_kills or 0) + 1
end
Find all online NPCs of a faction¶
local function get_online_faction_npcs(faction)
local result = {}
for id, data in pairs(db.storage) do
local obj = data.object
if obj and obj:alive() and IsStalker(obj) and obj:community() == faction then
result[#result + 1] = obj
end
end
return result
end
Make all nearby NPCs friendly¶
local function pacify_nearby(radius)
level.iterate_nearest(db.actor:position(), radius, function(obj)
if IsStalker(obj) and obj:alive() then
game_relations.set_npc_sympathy(obj, 1.0)
game_relations.set_npcs_relation(obj, db.actor, game_relations.FRIENDS)
end
end)
end
See also¶
- alife — spawning NPCs and working with server-side entities
- Engine Internals — online/offline lifecycle that governs when db.storage entries exist
- game_object — base methods available on all NPC game_objects