BLSE0.6

Python mods

Animation: sequences, playing, rules and events

On this page 22 sections

(b0.5.49) Playing an animation on any NPC, reading what a character plays now, events when an animation starts and ends, rules that change which animation the game picks for an activity, and new animations added to models without editing them.

Come here when your mod makes characters move in a new way: a dancer who dances on the spot, a guard who stands with crossed arms, every prostitute flirting instead of standing idle, a scene that waits until an NPC finished a gesture, or a pack of new dance animations added to every male NPC model.

A first example: look at an NPC, type dance in the console, and the NPC stops walking and dances until you type dance again.

Python
import blse

def dance(name, args):
    target = blse.look_target()                 # (classname, name, handle) or None
    if not target:
        return
    npc = target[2]
    if blse.animation(npc) and blse.animation(npc)['held']:
        blse.stop_animation(npc)                # stop: the NPC's own animation comes back
    elif blse.play_animation(npc, 'pole_strole_01', 'loop still'):
        blse.console_print('Dancing.\n')

blse.add_command('dance', dance, 'dance: the NPC you look at dances (again: stops)')

Read this first

  • Only while a level runs (after blse.level_init, before blse.level_shutdown), and only from Python's normal code (the main thread, see Words used on this page). At the main menu and while loading, the functions return 0 or None. Rules (set_animation_rule) and the activity functions also work outside a level.
  • Animations are not saved. A mod's play_animation ends with the level, also when a save is loaded or the player changes level. Play it again from a blse.level_init handler if it should continue.
  • Sequence names depend on the model. A sequence that one model has may be missing on another; then play_animation returns 0. Look with blse.sequences(entity) first.
  • A dead NPC, or one in a conversation or a scripted scene, refuses (play_animation returns 0): the game's own scene goes first.
  • Nothing is hooked until a mod first uses a function or listens to an event. Some events are very frequent: listen only while you need them.
  • On a game version BLSE does not recognize, these functions are unavailable and return 0 / None.

Words used on this page#

  • Entity, index, handle: see the world page. entity is a server entity's index (1 = the player) or its handle (look_target()[2], or from find_entities).
  • Model: the 3D object the game draws for an entity, a .mdl file. A model holds the skeleton, the surfaces and the animations.
  • Sequence: one animation stored in a model ('lap_dance_01', 'Stance_Anger_Idle_1'), with a name and a number. Numbers are counted per entity across the model and the models it includes, starting at 0.
  • Activity: what the AI asks for (ACT_IDLE, ACT_WALK, ACT_RUN, VTMB's own such as ACT_DISPOSITION or ACT_LAP_DANCE), not a particular animation. Each activity has a name and a number ('ACT_WALK' is 9). Sequences in the model are marked with an activity; when the AI asks for one, the game picks one of the sequences marked with it.
  • Cycle: how far a sequence has played, from 0.0 (start) to 1.0 (end).
  • Rate: playback speed. 1.0 is normal, 0.5 half speed, 2.0 double, 0 frozen.
  • Loop: a looping sequence (walk, idle) starts again at the end by itself; one that does not loop stops at its end ("finished").
  • Think: the moment, many times a second, when the game updates an NPC's AI. At each think the AI also puts its own animation back.
  • Schedule: a plan the AI follows (a list of tasks such as "stop moving, then wait"). SCHED_WAIT_FOR_SCRIPT is the game's "stand and wait" schedule. See NPC AI.
  • Include (a model): a model can take more animations from other .mdl files it names (its include list). Bloodlines' character models take almost all their animations this way from the shared files in models/character/shared/.
  • Main thread, safe point: Python code runs on the game's main thread. Events that happen deep inside the game are delivered to Python a moment later, at the next safe point (between two frames), not at the exact moment.
  • Payload: the text an event hands to your handler: several fields separated by NUL characters (character 0), read with data.split('\0').

Bloodlines' animations are sequences in the models. A character model has almost none of its own: it includes the shared animation models (models/character/shared/female/..., male/...), so a pedestrian has several hundred, numbered across all of them. An activity (ACT_IDLE, ACT_WALK, ACT_RUN, VTMB's own such as ACT_DISPOSITION or ACT_LAP_DANCE) is what the AI asks for; the game then picks one of the sequences marked with it.

entity is an index or a handle (look_target()[2]); 1 is the player. While a level runs. Nothing is hooked until a mod first uses a function or listens to an event.

Functions#

Function
blse.sequences(entity) [(index, name, activity, seconds, loops), ...] of its model, e.g. (312, 'lap_dance_01', 'ACT_LAP_DANCE', 9.6, 1); activity is '' if none, seconds the length at normal speed, loops 1 or 0. None without a model
blse.lookup_sequence(entity, name) The sequence's number (case is ignored), or None
blse.play_animation(entity, sequence[, options[, rate]]) Plays a sequence (its name, or its number) from the start. options: words, 'loop' (until stop_animation; otherwise once), 'still' (an NPC first stops walking, see below). rate 0 to 16, 1 normal (default). 1 or 0
blse.stop_animation(entity) Ends a mod's animation; the NPC's own comes back at its next think. 1, or 0 if none plays
blse.animation(entity) e.g. {'sequence': 312, 'name': 'lap_dance_01', 'activity': 'ACT_LAP_DANCE', 'npc_activity': 'ACT_IDLE', 'ideal_activity': 'ACT_IDLE', 'cycle': 0.42, 'rate': 1.0, 'loops': 1, 'finished': 0, 'held': 1}, or None
blse.set_animation_rule(who, activity, sequences) When the game picks a sequence for activity on an NPC matching who, one of sequences is used instead. 1 or 0
blse.animation_rules() [(who, activity, sequences), ...]
blse.activity_id(name) / blse.activity_name(number) Activity numbers and names ('ACT_WALK' <-> 9), or None

animation(entity): sequence, name and activity are what the model plays now; npc_activity and ideal_activity (characters only, else None) are the activity the AI plays and the one it wants; cycle runs from 0 to 1 through the sequence; finished is 1 once a sequence that does not loop reached its end; held is 1 while a mod's play_animation holds the NPC.

blse.sequences#

Python
blse.sequences(entity)   # -> [(index, name, activity, seconds, loops), ...] or None

Lists every animation the entity's model can play. Use it to find out which names a character has before you play one, for example in a console command that prints them while you write your mod.

  • entity: index or handle of an entity with a model (a character, a prop, an item in the world).
  • Returns a list with one tuple per sequence, in number order:
    • index (int): the sequence's number, from 0;
    • name (str): its name, as play_animation takes it;
    • activity (str): the activity it is marked with ('ACT_IDLE'), or '' if none;
    • seconds (float): its length at normal speed (rate 1);
    • loops (int): 1 if it loops by itself, else 0.
  • Returns None outside a level, for an entity that does not exist or has no model. Raises RuntimeError if memory runs out.
Python
# Print every dance a character has.
for index, name, activity, seconds, loops in blse.sequences(npc) or []:
    if name.lower().find('dance') >= 0:
        blse.log('%d %s %s %.1f s' % (index, name, activity, seconds))

Notes: a character has several hundred sequences (at most 4096 are listed). The list includes the sequences of the models it includes and of any animation file a mod added.

blse.lookup_sequence#

Python
blse.lookup_sequence(entity, name)   # -> int or None

Finds one sequence's number by its name. Use it to check that a model has an animation before playing it, or to compare with the sequence number from blse.animation.

  • entity: index or handle of an entity with a model.
  • name (str): the sequence's name; case is ignored. A text of digits ('312') is taken as a number and returned if the model has that many sequences.
  • Returns the number (int), or None if the model has no such sequence, the name is empty or longer than 127 characters, the entity has no model, or no level runs.
Python
if blse.lookup_sequence(npc, 'lap_dance_01') is None:
    blse.log('this model cannot lap dance')

blse.play_animation#

Python
blse.play_animation(entity, sequence[, options[, rate]])   # -> 1 or 0

Plays one animation on an entity, from its start. On an NPC, BLSE keeps it playing against the AI (see Playing on an NPC) until it ends, stop_animation ends it, or the game changes the NPC's animation itself.

  • entity: index or handle of an entity with a model.
  • sequence: the sequence's name (str, case ignored) or its number (int). Anything else raises TypeError.
  • options (str, optional): words separated by spaces, commas or tabs, case ignored. None or '' means none.
    • 'loop': play again and again until stop_animation (or until the game changes the animation). Without it the sequence plays once.
    • 'once': play once (the default; cancels a 'loop' written before it).
    • 'still': an NPC first stops what it does (walking, for example) and stands while it plays. Ignored for other entities.
    • Any other word raises ValueError.
  • rate (float, optional): speed, 0 to 16; 1.0 (default) is normal speed, 0 freezes the first frame. Outside the range raises ValueError.
  • Returns 1 if it plays (with 'still', it may start up to 1.5 seconds later, once the NPC stands), 0 otherwise: no level runs, the entity does not exist or has no model, the model has no such sequence, the NPC is dead, in a conversation or in a scripted scene, or the game refused.
Python
# A bystander dances on the spot until told to stop.
blse.play_animation(npc, 'pole_strole_01', 'loop still')

# Half-speed, once.
blse.play_animation(npc, 'Stance_Anger_Idle_1', '', 0.5)

# By number, from the list of sequences.
blse.play_animation(npc, 312)

Notes: playing again on the same NPC replaces the earlier play (its 'still' wait too). With 'still', if the NPC cannot be made to stand (the game refuses its waiting schedule), the animation plays at once anyway and BLSE writes the reason to the log. The player and other non-NPC entities just get the sequence, without the holding (see below).

blse.stop_animation#

Python
blse.stop_animation(entity)   # -> 1 or 0

Ends an animation a mod started with play_animation. The NPC's own animation comes back at its next think; an NPC that was made to stand ('still') ends its waiting schedule and goes back to its own plans.

  • entity: index or handle.
  • Returns 1 if a mod's animation was playing (or a 'still' play was still waiting to start), 0 if none was, or no level runs.
Python
blse.stop_animation(npc)

Note: this does not stop the game's own animations; on props and the player there is nothing held to stop, so it returns 0.

blse.animation#

Python
blse.animation(entity)   # -> dict or None

Reads what an entity plays right now. Use it to wait until a gesture is over, to check if your mod's animation still holds, or to find out which sequence the game picked.

  • entity: index or handle of an entity with a model.
  • Returns a dictionary, or None outside a level, for a missing entity or one without a model. Raises RuntimeError if memory runs out. The keys:
    • 'sequence' (int): the number of the sequence the model plays;
    • 'name' (str): its name;
    • 'activity' (str or None): the activity that sequence is marked with, None if none;
    • 'npc_activity' (str or None): the activity the AI plays now (characters only; None for other entities);
    • 'ideal_activity' (str or None): the activity the AI wants to play next (characters only);
    • 'cycle' (float): how far it has played, 0 to 1;
    • 'rate' (float): the speed, 1.0 normal;
    • 'loops' (int): 1 if it loops;
    • 'finished' (int): 1 once a sequence that does not loop reached its end;
    • 'held' (int): 1 while a mod's play_animation holds the NPC (also while a 'still' play waits for the NPC to stand).
Python
a = blse.animation(npc)
if a and a['name'] == 'pole_strole_01' and a['finished']:
    blse.log('done')

blse.set_animation_rule and blse.animation_rules#

Python
blse.set_animation_rule(who, activity, sequences)   # -> 1 or 0
blse.animation_rules()                              # -> [(who, activity, sequences), ...]

A rule says: "when the game picks an animation for this activity on these NPCs, use one of mine instead". Unlike play_animation, the NPC keeps its AI: it walks, idles and fights as before, only with other animations. A mod uses it to give every prostitute a flirting idle, or one guard an aggressive walk. See Rules: another animation for an activity for the details.

  • who: which NPCs, one of:
    • an entity (int, index or handle): that NPC only, until the level ends. An entity that does not exist returns 0;
    • '*': every NPC;
    • 'model:<text>': NPCs whose model path contains the text, case ignored ('model:prostitute');
    • any other text: a classname ('npc_VPedestrian') or a targetname (the name a map gives an entity), case ignored. At most 200 characters.
  • activity: a name ('ACT_WALK', case ignored) or a number (9, or '9'). An unknown activity returns 0.
  • sequences: one sequence name, or several separated by commas, spaces, tabs or ; (one is chosen at random each time). None or '' removes the rule for this who and activity.
  • Returns 1 if the rule was set or removed (also when there was none to remove), 0 otherwise (unknown activity, missing entity, empty who, game version not recognized). Wrong argument types raise TypeError.

animation_rules() lists the rules in the order they apply: who as given (an entity shows as '#<handle>'), the activity's name (or its number as text if it has no name), and the sequences joined with commas. Rules of plugins are listed too.

Python
blse.set_animation_rule('model:prostitute', 'ACT_IDLE', 'Stance_Flirt_Idle_1, Stance_Flirt_Idle_2')
blse.set_animation_rule(npc, 'ACT_WALK', 'knife_aggressive_walk')  # this NPC stalks
blse.set_animation_rule(npc, 'ACT_WALK', None)             # and walks again

for who, activity, sequences in blse.animation_rules():
    blse.log('%s %s -> %s' % (who, activity, sequences))

Notes: a rule can be set outside a level (at the top of a script, for example). Python's rules are removed when Python starts again with the engine; the scripts then set them again.

blse.activity_id and blse.activity_name#

Python
blse.activity_id(name)        # -> int or None
blse.activity_name(number)    # -> str or None

Turn an activity's name into its number and back. Useful to read numbers in the game's data or to check a name is right before making a rule.

  • name (str): the activity's name, case ignored ('act_walk' works). A text of digits is taken as a number and returned if it is 0 to 8191.
  • number (int): 0 to 8191.
  • Return the number or the name, or None if the game does not know it (or the game version is not recognized).
Python
blse.activity_id('ACT_WALK')   # 9
blse.activity_name(9)          # 'ACT_WALK'

Playing on an NPC#

An NPC's AI puts its own animation back at every think. BLSE keeps a mod's animation against that until:

  • it ends (played once), or stop_animation;
  • the game changes the NPC's animation itself: it dies, flinches from a hit, is knocked back, starts a conversation or a scripted scene, or its schedule sets an activity;
  • the level ends (loads and level changes included; animations are not saved).

Then the NPC's own animation comes back. Without 'still' the NPC keeps doing what it did: one walking somewhere keeps walking while it plays (its feet slide). With 'still' it stops at once and plays (the game's SCHED_WAIT_FOR_SCRIPT: it stands and waits; damage ends it); afterwards it goes back to its own schedules. A dead NPC, or one in a conversation or a scripted scene, refuses (0).

While the animation is held, BLSE also keeps the rate and the looping the mod asked for (the game resets both with every new sequence).

Other entities (props, items in the world) just get the sequence. The player does too, but the player's movement animation takes over at once (the console's player_sequence has the same limit).

Python
# A bystander dances on the spot until told to stop.
def dance(npc):
    return blse.play_animation(npc, 'pole_strole_01', 'loop still')

# Half-speed, once.
blse.play_animation(npc, 'Stance_Anger_Idle_1', '', 0.5)

Rules: another animation for an activity#

who is one of:

who Matches
an entity (number) that NPC only, until the level ends
'*' every NPC
'model:<text>' NPCs whose model path contains the text ('model:prostitute')
any other text a classname ('npc_VPedestrian') or a targetname, case ignored

activity is a name ('ACT_WALK') or a number. A rule matches the activity the AI asks for and the one the game turns it into (by the weapon held, e.g. a walk with a pistol). sequences is one name or several separated by commas or spaces (one is chosen at random each time); a model that has none of them keeps the game's choice. None or '' removes the rule. A later rule for the same who and activity replaces it. The first matching rule in the order they were made applies. Rules last until removed (Python's until Python restarts with the engine; one entity's until the level ends).

In detail: when one sequence is picked at random and this model does not have it, the game's own choice stays for that time. Plugins can also change the choice (after the rules); the blse.anim_chosen event tells you the final pick.

Python
blse.set_animation_rule('model:prostitute', 'ACT_IDLE', 'Stance_Flirt_Idle_1, Stance_Flirt_Idle_2')
blse.set_animation_rule(npc, 'ACT_WALK', 'knife_aggressive_walk')  # this NPC stalks
blse.set_animation_rule(npc, 'ACT_WALK', None)             # and walks again

Not covered by rules: animations the game picks another way (gestures in conversations, melee attack lists, the knockback's flying, the player's animations). A rule replacing a movement activity (ACT_WALK, ACT_RUN) with a sequence that does not move makes the NPC slide.

New animations for models#

(b0.5.49) A mod ships an animation-only .mdl (the skeleton and its sequences, no meshes) and BLSE adds it to a model when the engine loads it, without editing the model's .mdl. Several mods can add to the same model side by side.

This is the game's own mechanism: character models already take their animations from included models (models/character/shared/male/npc_allsequences.mdl and the files it includes). BLSE appends the mod's file to the model's include list; the engine then loads it through the search paths (mod folders included), matches the bones by name (a different number of bones or other proportions are fine: the engine retargets) and numbers its sequences after all of the model's own, so the game's sequence numbers (and saves) do not change.

Function
blse.add_model_animations(target, anim_mdl, replace=0) Adds anim_mdl to target at its next load. 1, or 0 if not available on this game build. ValueError for paths that are not .mdl files (or the same file twice)
blse.model_animations([text]) [(target, file, 'add' or 'replace', who, [models it was added to]), ...], only those whose paths contain text
Python
# Every male NPC that uses the game's shared NPC animations gets a mod's dances.
blse.add_model_animations('models/character/shared/male/npc_allsequences.mdl', 'models/mymod/male_dances.mdl')
# One character only.
blse.add_model_animations('models/character/npc/unique/Downtown/Lacroix/Lacroix.mdl', 'models/mymod/lacroix_extra.mdl')

blse.add_model_animations#

Python
blse.add_model_animations(target, anim_mdl, replace=0)   # -> 1 or 0

Registers an animation file to be added to a model (or to every model that includes it) the next time the engine loads that model. Use it at the top of your script so it is in place before the first level loads.

  • target (str): the model to change, a .mdl path as the game names it (models/...; case and / or \ do not matter; models/ is added if missing). A shared model that other models include means every model that includes it directly (see the list below).
  • anim_mdl (str): your animation file, also a .mdl path.
  • replace (int, optional): 0 (default) adds the sequences next to the model's own; 1 replaces the model's sequences of the same names (see "Replacing" below).
  • Returns 1 when registered, 0 if the feature is not available on this game build. Raises ValueError if either path is not a .mdl path, or if target and anim_mdl are the same file. Registering the same pair again only changes replace.
Python
blse.add_model_animations('models/character/shared/female/npc_allsequences.mdl',
                          'models/mymod/female_idles.mdl', 1)

Note: if the model is already loaded, it gets the file only at its next load, usually the next game start (BLSE logs this).

blse.model_animations#

Python
blse.model_animations([text])   # -> [(target, file, mode, who, models), ...]

Lists the registered animation files: from Python, from plugins and from blse/animations.txt files. Use it to check that your registration took effect.

  • text (str, optional): only registrations whose target or file path contains this text (case ignored). Left out: all.
  • Returns a list of tuples:
    • target (str): the model path as BLSE stores it (lower case, /);
    • file (str): the animation file;
    • mode (str): 'add' or 'replace';
    • who (str): who registered it: 'python', the plugin's name, or 'mod <folder>' for a mod folder's animations.txt;
    • models (list of str): the loaded models it was actually added to so far.
Python
for target, file, mode, who, models in blse.model_animations('mymod'):
    blse.log('%s %s %s: %d models' % (mode, file, target, len(models)))

How adding and replacing work#

  • Targets: paths as the game names them (models/...mdl; case and / or \ do not matter; a path without models/ gets it). A model other models include (any file under models/character/shared/, or one BLSE has seen in another model's include list) means every model that includes it directly: 58 male and 35 female NPC models include their npc_allsequences.mdl. The shared file itself never gets the include (the numbers inside every tree using it would shift). Any other target gets it itself.
  • Adding: the new sequences are found by name (play_animation, lookup_sequence, set_animation_rule, scripted sequences, choreography) and, if they carry an activity, join the game's choice for that activity. Two mods adding the same name to one model: the first in load order is found (logged).
  • Replacing (replace=1): every sequence of the animation file replaces the original of the same name, which BLSE renames <name>@orig (still playable by that name) and takes out of the activity choice. For a shared target the originals are hidden in the whole tree it includes (e.g. a male replacement in the male files only), and every model whose tree uses a file with a hidden original gets the replacement too (the player's model, characters that include such a file directly; logged). For an exact model only its own sequences are replaced: to replace an animation that comes from a shared file, target the shared model. Two mods replacing the same name: the later registration wins (logged).
  • When: register before the model loads: at the top of a script (scripts run at startup), from a plugin's load, or with a data file (below). A model already loaded gets it at its next load, which is usually the next game start (the engine keeps models across maps and saves; BLSE logs it). Python's registrations are made again by the scripts at each Python start.
  • The file must be a version 2531 (VTMB) .mdl with the bone names of the skeleton it animates (Bip01 ...): bones the target lacks are ignored (the engine prints chained model ... has a bone ... that isn't in chaining model ...), target bones the file lacks are not animated by it. At most 24 pose parameters. A missing file is skipped by the engine ("Model ... not found!").
  • Saves: sequence numbers are saved. A save made with an animation mod and loaded without it may hold numbers past a model's end: after every load from a save BLSE gives such characters sequence 0 (logged; their AI picks its own animation at once). The game alone does not do this: load a save made while a character played an added animation with BLSE installed.
  • Console: anim_models [text] lists the registrations and every loaded model BLSE changed, with its added files and hidden sequences.

Without code: blse/animations.txt#

A mod folder may hold blse/animations.txt, read at startup in load order (before plugins and scripts): one registration per line, <target> <animation file> [replace]; # or // start a comment; paths with spaces in "quotes". The file may be at most 1 MB; lines that are not two .mdl paths are skipped and written to the log.

text
# my_mod/blse/animations.txt
models/character/shared/male/npc_allsequences.mdl     models/my_mod/male_dances.mdl
models/character/shared/female/npc_allsequences.mdl   models/my_mod/female_idles.mdl   replace

Making the animation file needs the model tools modders already use for whole-model animation mods (decompile with Crowbar, compile back to version 2531): keep the skeleton and the new sequences, drop the meshes.

Console command anim_models#

text
anim_models [text]

Typed in the game's console: lists every registration (from Python, plugins and animations.txt files) and every loaded model BLSE changed so far, with the files added to it and the sequences hidden by a replacement. With text, only lines containing it. Use it to see why your animation does not show up (wrong path, model not loaded yet).

Events#

Listen only while needed: the hooks behind them are installed with the first listener, and some are frequent. Python gets them at the next safe point.

Event Payload
blse.anim_started handle, cls, name, seq, seqname, activity, previous = data.split('\0'): an entity's sequence (re)started (also the game's own); activity and previous (the sequence before) may be ''
blse.anim_ended handle, cls, name, seq, seqname, activity, how = data.split('\0'): how is 'finished' (it reached its end) or 'interrupted' (another sequence replaced it first). A looping sequence (walk, idle) never finishes: it ends when another one replaces it
blse.anim_chosen handle, cls, name, activity, translated, seqname, changed = data.split('\0'): an NPC's AI picked a sequence for an activity; translated is the activity after the game's weapon translation, changed '1' if a rule or filter changed the game's choice. Very frequent

Every entity with a model sends anim_started and anim_ended (props too): compare the handle.

All fields are text. handle is the entity's handle as a number in text (use int(handle) to pass it to other functions); cls its classname; name its targetname ('' if it has none); seq the sequence number as text; seqname its name; activity the activity it is marked with ('' if none).

Python
def on_ended(topic, data):
    handle, cls, name, seq, seqname, activity, how = data.split('\0')
    if seqname == 'pole_strole_01' and how == 'finished':
        blse.log('%s finished dancing' % name)

blse.subscribe('blse.anim_ended', on_ended)

blse.anim_started#

Fires when any entity with a model starts a sequence (or starts the same one again), whether the game or a mod started it. Use it to react when a character begins a gesture.

Payload, in order: handle, cls, name, seq, seqname, activity, previous (the name of the sequence it played before, '' if none or if the entity had not played a frame yet).

Python
def on_started(topic, data):
    handle, cls, name, seq, seqname, activity, previous = data.split('\0')
    if int(handle) == watched and activity == 'ACT_LAP_DANCE':
        blse.log('the dance begins')

blse.subscribe('blse.anim_started', on_started)

blse.anim_ended#

Fires when a sequence ends: either it reached its end (how is 'finished', only for sequences that do not loop) or another sequence replaced it first ('interrupted'). Use it to start the next step of a scene when a gesture is over.

Payload, in order: handle, cls, name, seq, seqname, activity, how. See the example under the Events table.

Note: an animation that finished and is then replaced does not send a second 'interrupted' event.

blse.anim_chosen#

Fires each time an NPC's AI picks a sequence for an activity, after BLSE's rules and plugins' filters. Use it while you write rules, to see which activities an NPC asks for and what it gets. It fires very often (many times a second per NPC): unsubscribe when you are done.

Payload, in order: handle, cls, name, activity (what the AI asked for), translated (the activity after the game turned it into a weapon-specific one, e.g. a walk with a pistol), seqname (the sequence finally used), changed ('1' if a rule or filter changed the game's choice, else '0').

Python
def on_chosen(topic, data):
    handle, cls, name, activity, translated, seqname, changed = data.split('\0')
    if int(handle) == npc:
        blse.log('%s -> %s (%s)' % (activity, seqname, changed))

blse.subscribe('blse.anim_chosen', on_chosen)

Bones the game moves by itself#

Hair, coat tails and breasts of many character models swing by themselves: the game's physics chains, a table in the model (not the .phy). Their console variables (bc_override, bc_maxangle, bc_spring, bc_damp, bc_gravity, bc_ground, bc_fade) can be set with blse.set_cvar; with bc_override 1 the others replace every chain's own values.

Python
blse.set_cvar('bc_override', '1')   # the bc_ values below now apply to every chain