On this page 17 sections
- Words used on this page
- Functions
blse.fire_inputblse.set_relationshipandblse.relationshipblse.set_enemyandblse.npc_enemyblse.start_scheduleblse.npc_scheduleblse.npc_stateblse.move_npcandblse.move_npc_toblse.follow_npcandblse.npc_leaderblse.npc_conditionsblse.npc_weaponblse.npc_sensesandblse.set_npc_sense- Events
- The
blse.npc_scheduleevent - The
blse.npc_stateevent - Notes
Relationships, enemies, schedules, moving and following, perception, and events when an NPC's schedule or state changes. Everything goes through the game's own code: the functions its maps and its aiscripted_schedule entity use. npc is an NPC's index or handle; other entities likewise, -1 for none. While a level runs; functions return 1 or 0 unless noted.
This page is for mods that tell NPCs what to do: make a bodyguard who follows the player and fights for them, turn a group of thugs hostile, make a pedestrian walk to a spot, make guards see further, or react when an NPC starts fleeing.
A first example: type bodyguard while looking at an NPC, and that NPC follows you and attacks whoever you look at next with sic.
import blse
guard = [None] # the bodyguard's handle (a list, so the functions below can change it)
def bodyguard(name, args):
target = blse.look_target() # (classname, name, handle) or None
if target:
guard[0] = target[2]
blse.follow_npc(guard[0], 1) # follow the player
blse.console_print('Following you.\n')
def sic(name, args):
target = blse.look_target()
if guard[0] and target:
blse.set_relationship(guard[0], target[2], 'hate', 99)
blse.set_enemy(guard[0], target[2])
blse.add_command('bodyguard', bodyguard, 'bodyguard: the NPC you look at follows you')
blse.add_command('sic', sic, 'sic: your bodyguard attacks what you look at')Read this first
- Only while a level runs (after
blse.level_init, beforeblse.level_shutdown), and only on the game version BLSE recognizes. Otherwise the functions return0orNone. - Only NPCs.
npcmust be a non-player character (an entity of the game's NPC kind). The player, props or items give0/None. - Busy NPCs are left alone. An NPC that is dead, in a scripted scene (a cut scene, an animation a map plays) or in a conversation cannot be given a schedule or sent walking: those functions return
0. - What is saved: relationships and senses you set are kept in saves. Following is not (see
follow_npc). Enemies and schedules are the NPC's current state and change on their own. - Who fights. Hate makes an NPC fight only if its kind can: civilians (pedestrians, bluebloods, prostitutes, patients) have no attack and take cover from an enemy instead. Thugs, gangsters, guards and cops attack.
Words used on this page#
- NPC: a non-player character: anyone in the world except the player.
- Entity, index, handle: see Words used on the world page. Here:
npcand the other entity arguments take an index or a handle (1is the player);-1means "none" where a function allows it. Functions that return an entity return its handle. - Relationship / disposition: how one NPC feels about one other entity:
'hate'(attacks it, or takes cover if it cannot fight),'fear'(runs from it),'like'(an ally) or'neutral'(ignores it). The game also has relationships by class (all cops hate criminals, for example); yours are added on top, for one entity. - Priority: when an NPC hates several entities, it picks the one with the highest priority as its enemy.
- Enemy: the one entity an NPC is fighting (or fleeing from) right now.
- Schedule: a small plan an NPC carries out: "idle and patrol", "flee", "take cover", "attack from range". The game's AI picks one after another. Names look like
SCHED_TROIKA_FLEE; each kind of NPC knows its own set. - State: the NPC's overall mood:
'idle','alert','combat','dead', ... (list below). Schedules are chosen to fit the state. - Condition: one fact the AI has noticed this moment, as a number: "can see the enemy", "no ammunition", "too far to shoot". The game decides what to do from the conditions.
- Input: a command sent to an entity, as maps do it with their I/O ("input/output") system:
Kill,Open,ChangeSchedule. - Units: distances in the game's units (a unit is roughly an inch).
Functions#
| Function | |
|---|---|
blse.fire_input(entity, input[, value[, activator]]) |
Sends an input to any entity, also an unnamed one, as the map's I/O does: fire_input(npc, 'Kill'), fire_input(door, 'Open'). Input rules, filters and blse.entity_input see it. activator: an entity, or -1 (the default) for the player. 1 if the entity handled it |
blse.set_relationship(npc, target, disposition[, priority]) |
How the NPC feels about one entity: 'hate', 'fear', 'like' or 'neutral'; priority (default 0) decides between enemies, higher first. Saved with the game |
blse.relationship(npc, target) |
(disposition, priority), e.g. ('neutral', 0); also the game's own (by class), or None |
blse.set_enemy(npc, target) |
Makes target (a character: the player or an NPC) the NPC's enemy and tells it where the target is. -1 clears the enemy and calms the NPC: back to idle, its current schedule (taking cover, chasing) ended, so it talks again |
blse.npc_enemy(npc) |
The handle of its enemy, or None |
blse.start_schedule(npc, schedule) |
Runs one of the NPC's schedules once ('SCHED_TROIKA_FLEE' or 'TROIKA_FLEE'), then its own AI carries on. It starts when the NPC's running schedule ends, as the map's ChangeSchedule input does: a walking NPC first finishes its step of the path (up to about 1.5 s). 0 if its kind has no such schedule, or it is dead, in a scripted scene or a conversation |
blse.npc_schedule(npc) |
The schedule it runs now ('SCHED_TROIKA_IDLE_PATROL'; '' if none), or None |
blse.npc_state(npc) |
'idle', 'alert', 'combat', 'fleeing', 'cowering', 'hunting', 'dialog', 'script', 'dead', ... (the game's own names), or None |
blse.move_npc(npc, goal[, run]) |
Sends the NPC to where an entity is now, walking or running (run 1) |
blse.move_npc_to(npc, x, y, z[, run]) |
Sends the NPC to a point |
blse.follow_npc(npc, leader) |
The NPC follows the leader (the player, another NPC, any entity): when it is more than 160 units away it is sent after it, running beyond 420. -1 stops. It also stops when the NPC dies, the level ends, or it is moved with move_npc; fights (also while it looks for an enemy it still hates), scripted scenes and conversations pause it. Not kept in saves: a mod that wants a lasting follower stores that (entity_set) and calls it again after blse.after_load and level_init |
blse.npc_leader(npc) |
The handle of the entity it follows, or None |
blse.npc_conditions(npc) |
(b0.5.48) The AI conditions set on the NPC now, as numbers (below): why it does or does not shoot. None if not an NPC |
blse.npc_weapon(npc) |
(b0.5.48) (classname, clip): the weapon in its hands and the rounds in its clip, or None |
blse.npc_senses(npc) |
{'sight': 3072.0, 'fov': 156.9, 'vision': 440.0, 'hearing': 1.0, 'vision_set': 0, 'hearing_set': 0, 'inspection': 3}, or None |
blse.set_npc_sense(npc, sense, value) |
'sight': how far it sees, in units. 'fov': its field of view in degrees (1-360). 'vision', 'hearing': the multipliers of the game's stealth system (how quickly it notices a sneaking player); a negative value gives the game's own back (from the NPC's stealth level; a value the map set is not restored: read it with npc_senses first). Kept in saves |
blse.npc_schedule (event) |
npc, cls, name, old, new = data.split('\0'): an NPC started a schedule (every change, also the game's own; frequent, so only listen while needed) |
blse.npc_state (event) |
npc, cls, name, old, new = data.split('\0'): an NPC's state changed (checked ten times a second while anyone listens) |
Where do you get an NPC's handle? From blse.find_entities (the NPCs near a point), blse.look_target() (the one under the crosshair), blse.spawn_entity (one you created), the blse.entity_spawned event, or the events on this page.
blse.fire_input#
blse.fire_input(entity, input[, value[, activator]]) # -> 1 or 0Sends an input (a command) to any entity, exactly as a map's I/O system does, also to an entity without a name (maps can only address named ones).
When to use it: kill an NPC ('Kill'), open a door ('Open'), start a map's relay ('Trigger'), send an NPC another schedule the map's way ('ChangeSchedule').
entity: an index or handle. Not0(the world).input(text): the input's name, 1 to 127 characters, e.g.'Kill','Open','SetHealth'.value(text, optional): the input's parameter, up to 1023 characters, e.g.'50'forSetHealth. Default''(none).activator(optional): the entity that "caused" it (maps and scripts can check it).-1, the default, means the player.
Returns 1 if the entity handled the input, 0 if not (it does not know that input, no level, unknown entity or activator). Too long input or value raises ValueError.
Input rules, filters and the blse.entity_input event see inputs sent this way like any other.
blse.fire_input(npc, 'Kill')
blse.fire_input(door, 'Open')Limit: entities may keep the text you send, so BLSE keeps every different input and value text for the rest of the game session, at most 20000. After that, new texts are refused (0, logged once). Sending the same few texts again and again costs nothing.
blse.set_relationship and blse.relationship#
blse.set_relationship(npc, target, disposition[, priority]) # -> 1 or 0
blse.relationship(npc, target) # -> (disposition, priority), or NoneSet or read how an NPC feels about one other entity.
When to use them: turn a gang hostile to the player after a quest choice; make a companion like the player; make cops ignore one particular NPC.
npc: the NPC whose feelings change.target: the entity it feels this way about (the player, another NPC, any entity). Not the NPC itself.disposition(text):'hate','fear','like'or'neutral'(the game's names'D_HT','D_FR','D_LI','D_NU'work too; any case). Another word raisesValueError.priority(whole number, optional, default0): -1000 to 1000 (outside raisesValueError). Among hated entities, the NPC fights the one with the highest priority first.
set_relationship returns 1 if set, 0 if not (not an NPC, unknown target, no level). The relationship is saved with the game.
relationship returns what the NPC feels now, as (disposition, priority), e.g. ('neutral', 0). This includes the game's own relationships by class, not only ones you set. None if the NPC or target does not exist (or no level).
blse.set_relationship(thug, 1, 'hate', 50) # this thug now hates the player
blse.log(str(blse.relationship(thug, 1))) # logs ('hate', 50)Enemies stay while hated. The game drops an enemy the NPC does not hate; to start a fight, set_relationship(npc, target, 'hate', 99) first, then set_enemy. To end one, make it neutral again and set_enemy(npc, -1).
blse.set_enemy and blse.npc_enemy#
blse.set_enemy(npc, target) # -> 1 or 0
blse.npc_enemy(npc) # -> handle, or Noneset_enemy makes target the NPC's current enemy and tells it where the target is, so it reacts at once instead of waiting to notice it.
target: a character (the player or an NPC), not the NPC itself. Props and items are refused.target = -1clears the enemy and calms the NPC: no enemy, its state goes back to idle (from combat or alert), and its current schedule (taking cover, chasing) is ended so it picks a peaceful one. Until then an NPC stays "in combat" and will not talk.
Returns 1 if done, 0 if not (not an NPC, a dead NPC, a target that is not a character, no level).
npc_enemy returns the handle of the NPC's current enemy, or None if it has none (or it is not an NPC).
# Start a fight between two NPCs:
blse.set_relationship(a, b, 'hate', 99)
blse.set_enemy(a, b)
# End it:
blse.set_relationship(a, b, 'neutral')
blse.set_enemy(a, -1)blse.start_schedule#
blse.start_schedule(npc, schedule) # -> 1 or 0Makes an NPC carry out one schedule once; afterwards its own AI carries on as usual.
When to use it: make a pedestrian flee ('SCHED_TROIKA_FLEE'), cower ('SCHED_TROIKA_COWER'), or make a gunman shoot at its enemy regardless of what it thinks ('SCHED_TROIKA_FORCED_RANGE_ATTACK1').
schedule(text): the schedule's name, with or withoutSCHED_('SCHED_TROIKA_FLEE'or'TROIKA_FLEE'), 1 to 120 characters (an empty or longer name raisesValueError).
When it starts: like the map's ChangeSchedule input, it starts when the NPC's running schedule ends. A walking NPC first finishes its step of the path (up to about 1.5 seconds).
Returns 1 if the schedule was given, 0 if the NPC's kind has no such schedule, or the NPC is dead, in a scripted scene or a conversation (or not an NPC, no level).
Schedule names are the game's (SCHED_TROIKA_COWER, SCHED_TROIKA_FLEE, SCHED_VCOP_WANDER_PATROL, ...). Each kind of NPC knows its own set; the general schedules every NPC has (SCHED_WAIT_FOR_SCRIPT, for example) work too (b0.5.49). npc_schedule and the blse.npc_schedule event show which ones an NPC uses.
if not blse.start_schedule(npc, 'SCHED_TROIKA_FLEE'):
blse.log('this NPC cannot flee right now')blse.npc_schedule#
blse.npc_schedule(npc) # -> name, or NoneThe name of the schedule the NPC runs now, e.g. 'SCHED_TROIKA_IDLE_PATROL'; '' if it runs none. None if it is not an NPC (or no level).
When to use it: find out which schedule names an NPC kind uses, before you call start_schedule; check whether your schedule is running.
blse.npc_state#
blse.npc_state(npc) # -> state name, or NoneThe NPC's current state, as the game names it: 'none', 'idle', 'alert', 'combat', 'script' (in a scripted scene), 'playdead', 'prone', 'dead', 'fleeing', 'retreating', 'cowering', 'hunting' (looking for an enemy it lost), 'dialog' (in a conversation), 'oblivious', 'criminal_suspicion', or 'unknown' for any other value. None if it is not an NPC (or no level).
if blse.npc_state(npc) == 'dead':
blse.log('too late')blse.move_npc and blse.move_npc_to#
blse.move_npc(npc, goal[, run]) # -> 1 or 0
blse.move_npc_to(npc, x, y, z[, run]) # -> 1 or 0Send an NPC walking (or running) somewhere: to where an entity is now (move_npc), or to a point (move_npc_to). The NPC finds its own path, as with the game's aiscripted_schedule.
When to use them: a contact walks to the bar; a guard runs to the alarm; a quest NPC meets the player at a door.
goal: an entity (index or handle), not the NPC itself. The NPC walks to the spot where the goal is at the moment of the call; it does not chase it if it moves (usefollow_npcfor that).x, y, z: the point, each between -65536 and 65536.run(optional, default0):1runs,0walks. Some kinds of NPC crawl instead, whatever you choose (as withaiscripted_schedule).
Returns 1 if the NPC started moving, 0 if not: not an NPC, dead, in a scripted scene or a conversation, the game could not start the walk (no path, for example), or no level. Both also end follow_npc for this NPC.
blse.move_npc_to(npc, 512, -128, 0, 1) # run thereHow it works: the NPC walks to a marker BLSE places (an info_target named blse_goal, reused, never removed), because the game keeps the goal as a plain pointer and reads it again while walking: a goal entity removed meanwhile would crash it. Do not remove these markers.
blse.follow_npc and blse.npc_leader#
blse.follow_npc(npc, leader) # -> 1 or 0
blse.npc_leader(npc) # -> handle, or Nonefollow_npc makes an NPC follow a leader: the player, another NPC, or any entity. Twice a second BLSE checks the distance: when the NPC is more than 160 units away it is sent after the leader, running when more than 420 units away; it is sent again when the leader has moved about 96 units from where the NPC was sent.
When to use it: a bodyguard, a companion of your own, a prisoner being escorted.
leader: an index or handle, not the NPC itself.-1stops following.
Returns 1 if set (or stopped), 0 if not (not an NPC, a dead NPC, unknown leader, no level).
Following stops when the NPC dies, the leader no longer exists, the level ends, or the NPC is moved with move_npc / move_npc_to. Fights (also while it looks for an enemy it still hates), scripted scenes and conversations pause it; it resumes afterwards.
Not kept in saves: a mod that wants a lasting follower stores that (for example with entity_set) and calls follow_npc again after blse.after_load and level_init.
npc_leader returns the handle of the entity the NPC follows, or None.
The game's own follower system is not used: for NPCs in a squad (cops) it ends the game ("Followers can not be in squads"), and pedestrians ignore it.
blse.follow_npc(npc, 1) # follow the player
blse.follow_npc(npc, -1) # stopblse.npc_conditions#
blse.npc_conditions(npc) # -> list of numbers, or None(b0.5.48) The AI conditions the NPC has right now, as a list of numbers (ids 0 to 255 are checked). The main use: finding out why an NPC does or does not shoot. None if it is not an NPC (or no level).
Why an NPC does not shoot (b0.5.48, from the game's code): it fires only while it has condition 79 (can fire). Others it can have instead: 64 no ammunition in the clip, 8 or 95 too close (under 100 units, or under the weapon's minimum), 96 too far, 97 not facing the enemy, 99 a character it does not hate is in the line of fire (the player, for a companion behind the player; 46 when that lasts over 2.5 s), 102 sight blocked, 71 it lost its enemy. start_schedule(npc, 'SCHED_TROIKA_FORCED_RANGE_ATTACK1') makes it face its enemy and fire regardless, until the enemy dies. Other attack schedules: SCHED_TROIKA_RANGE_ATTACK1, SCHED_TROIKA_TAKE_COVER_PEEK_OUT_FIRE, SCHED_TROIKA_STEP_BACK_RANGE_ATTACK1, SCHED_TROIKA_COMBAT_DODGE.
c = blse.npc_conditions(npc)
if c is not None and 79 not in c:
if 99 in c:
blse.log('someone is in its line of fire')blse.npc_weapon#
blse.npc_weapon(npc) # -> (classname, clip), or None(b0.5.48) The weapon in the NPC's hands and how many rounds are in its clip, e.g. ('item_w_colt_anaconda', 6). None if it holds nothing (or it is not an NPC, no level). A clip of 0 or less means "no ammunition" to the game.
blse.npc_senses and blse.set_npc_sense#
blse.npc_senses(npc) # -> dict, or None
blse.set_npc_sense(npc, sense, value) # -> 1 or 0Read or change how well an NPC perceives the world.
When to use them: guards that see further at night; a blind NPC; a stealth mod that makes some NPCs harder to sneak past.
npc_senses returns a dict such as {'sight': 3072.0, 'fov': 156.9, 'vision': 440.0, 'hearing': 1.0, 'vision_set': 0, 'hearing_set': 0, 'inspection': 3}, or None if it is not an NPC (or no level):
'sight': how far it can see, in units.'fov': its field of view, in degrees (how wide it looks).'vision','hearing': the multipliers of the game's stealth system, how quickly it notices a sneaking player, as in effect now.'vision_set','hearing_set':1if that multiplier was set (by the map or a mod),0if it comes from the NPC's stealth level.'inspection': the NPC's stealth level, as the game stores it.
set_npc_sense changes one of them:
sense:'sight','fov','vision'or'hearing'(any case; another word raisesValueError).value:'sight': 0 to 100000 units.'fov': 1 to 360 degrees.'vision','hearing': a multiplier up to 100; a negative value gives the game's own back (from the NPC's stealth level; a value the map set is not restored: read it withnpc_sensesfirst).
Returns 1 if set, 0 if not (a value out of range, not an NPC, no level). Kept in saves.
blse.set_npc_sense(guard, 'sight', 4096)
blse.set_npc_sense(guard, 'fov', 200)Events#
Two events tell you when NPCs change what they do. Both have the same five parts:
npc: the NPC's handle, as text (int(npc)gives the number);cls: its classname;name: its targetname (''if none);old: the schedule or state before;new: the schedule or state now.
The blse.npc_schedule event#
Sent every time any NPC starts a schedule: the game's own choices too, not only yours. This is frequent (many per second in a busy level), so subscribe only while you need it and keep the handler short. Delivered to Python a moment later in the same frame (the NPC is in the middle of thinking when it happens).
def on_schedule(topic, data):
npc, cls, name, old, new = data.split('\0')
if new == 'SCHED_TROIKA_FLEE':
blse.log('%s (%s) runs away' % (name, cls))
token = blse.subscribe('blse.npc_schedule', on_schedule)
# ... when done:
blse.unsubscribe(token)The blse.npc_state event#
Sent when an NPC's state changes (old and new are state names as npc_state gives them). States are compared ten times a second while anyone listens, so a change is reported up to a tenth of a second late. The first comparison after you subscribe only records the states; NPCs that appear later are recorded without an event.
def on_state(topic, data):
npc, cls, name, old, new = data.split('\0')
if new == 'combat' and int(npc) == guard[0]: # guard: from the first example
blse.show_text('Your bodyguard is fighting!')
blse.subscribe('blse.npc_state', on_state)Notes#
- Who fights. Hate makes an NPC fight only if its kind can: civilians (pedestrians, bluebloods, prostitutes, patients) have no attack and take cover from an enemy instead. Thugs, gangsters, guards and cops attack.
- Why an NPC does not shoot: see
npc_conditions. - Enemies stay while hated: see
set_relationship. - Schedule names: see
start_schedule. - Moves go to markers named
blse_goal: seemove_npc. - The game's own follower system is not used: see
follow_npc. fire_inputkeeps every different input text for the rest of the game (an entity may keep it), at most 20000.
def make_bodyguard(npc):
blse.follow_npc(npc, 1)
blse.set_npc_sense(npc, 'sight', 4096)
def on_damage_to_player(attacker, npc):
blse.set_relationship(npc, attacker, 'hate', 99)
blse.set_enemy(npc, attacker)