BLSE0.6

Python mods

Models and appearance

On this page 16 sections

The size of a model and its single bones, faces, skins, colour and materials, and outfits: clothes the player wears whatever armor is on. Entities are found and created as in the world.

Come here when your mod changes how a character or object looks: make a giant or a tiny NPC, give someone a bigger head or longer legs, set a facial expression, switch a skin or a body part (a hat, a bag), tint or fade a character, paint one character with other textures, or dress the player in your own clothes.

A first example: look at an NPC, type giant in the console, and it becomes twice as big, also after you save and load.

Python
import blse

def giant(name, args):
    target = blse.look_target()          # (classname, name, handle) or None
    if target and blse.set_model_scale(target[2], 2.0, 'all keep'):
        blse.console_print('Fee-fi-fo-fum.\n')

blse.add_command('giant', giant, 'giant: the character you look at grows')

Read this first

  • Only while a level runs (after blse.level_init, before blse.level_shutdown). At the main menu and while loading, the functions return 0 or None.
  • Only entities with a model: characters, props, items lying in the world. Colour and render mode (set_render_color, set_render_mode) also work on entities without one.
  • Most changes end with the level unless you add the option word keep. With keep, BLSE stores the change in the save and applies it again after a load and when the level is entered again. Without it, a save holds the entity as the game had it.
  • Only the entity you name changes: no model file is edited, and other entities with the same model look as before.
  • Bad numbers raise errors: values out of range and unknown option words raise ValueError; a missing entity, model, bone or controller returns 0 or None.
  • 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 such as models/character/npc/common/citizen/male/male_citizen_1.mdl.
  • Bone: one joint of a model's skeleton ('Bip01 Head', 'Bip01 L Calf'). Animations move the bones; the model's surface follows them. Bones form a tree: moving a thigh moves the calf and foot below it ("children" of the thigh).
  • Hit boxes: invisible boxes fixed to bones, which bullets and blows hit.
  • Collision box: the invisible box around a whole character that others bump into.
  • Flex controller (flex): one slider of a face, such as "smile", "blink" or "jaw open". Expressions and lip-sync move these.
  • Skin: one of several texture sets a model can come with (a clean and a dirty shirt, for example), numbered from 0.
  • Body group: a named part of a model that has several versions (sub-models), such as a hat that can be shown in two styles or hidden; numbered from 0.
  • Material: a .vmt file that tells the game how to draw a surface (which texture, shiny or not). A model lists the materials its parts use.
  • Render colour, mode and FX: the game's per-entity tint and transparency (r, g, b, a, each 0-255), and how the entity is drawn (normal, see-through, glowing, ...).
  • Option words: some functions take a text of words separated by spaces ('view hitboxes keep'), to switch extra behaviour on.

Models: bones and scale#

entity is a server entity's index (1 = the player) or its handle (look_target()[2]). Entities with a model: characters, props, items lying in the world. While a level runs.

Function
blse.bones(entity) The names of the model's bones in the model's order ('Bip01', 'Bip01 Pelvis', ..., 'Bip01 Head'), or None
blse.bone_position(entity, bone) (x, y, z, pitch, yaw, roll) of a bone in the world, by name (case is ignored), or None
blse.model_scale(entity) The model's scale: 1.0 normal; 0.0 if it was never set (drawn as 1); None without a model
blse.set_model_scale(entity, scale[, parts]) Draws the model larger or smaller, 0.05 to 8 (ValueError outside). Returns 1 or 0
blse.model_scale_parts(entity) The parts scaled with the model, e.g. 'view collision'; '' for the picture only

blse.bones#

Python
blse.bones(entity)   # -> list of names, or None

The names of the model's bones, in the model's own order: ['Bip01', 'Bip01 Pelvis', ..., 'Bip01 Head', ...].

When to use it: find the exact bone names before you scale or move a bone, or before you ask where a bone is. The game's characters use names starting with Bip01.

Returns a list of text, or None if the entity has no model (or no level).

Python
for b in blse.bones(1):
    blse.log(b)          # every bone of the player's model, in blse.log

blse.bone_position#

Python
blse.bone_position(entity, bone)   # -> (x, y, z, pitch, yaw, roll), or None

Where one bone is in the world right now, and which way it points.

When to use it: play a sound or spawn an effect at a character's head or hand; aim a camera at a face.

  • bone: the bone's name from bones() (case is ignored), e.g. 'Bip01 Head'.

Returns the bone's position x, y, z and its angles pitch, yaw, roll in degrees, or None if there is no such bone or model (or no level). With the hitboxes part or option (below), the position follows the scaled or modified bones.

Python
p = blse.bone_position(npc, 'Bip01 Head')
if p:
    blse.play_sound_at('mymod/whisper.wav', p[0], p[1], p[2])

blse.model_scale, blse.set_model_scale and blse.model_scale_parts#

Python
blse.model_scale(entity)                    # -> number, or None
blse.set_model_scale(entity, scale[, parts])  # -> 1 or 0
blse.model_scale_parts(entity)              # -> text, or None

set_model_scale draws a whole model larger or smaller.

When to use it: a giant boss, a tiny ghoul, a Discipline that makes the player grow for a while.

  • scale: 0.05 to 8 (1 is normal size). Outside raises ValueError.
  • parts (text, optional, default ''): words, separated by spaces, that choose what grows with the picture and how long it lasts. See the two tables below. An unknown word raises ValueError. ('eyes' is another name for 'view'; 'model' is accepted and means the picture only.)

Returns 1 if set, 0 if not (no model, no level, or a part not available on this game version).

model_scale returns the scale: 1.0 normal; 0.0 if it was never set (drawn as 1); None without a model. model_scale_parts returns the parts as words, e.g. 'view collision' or 'view hitboxes collision keep'; '' for the picture only; None without a model.

By default only the picture changes: the game's eye height, collision box and hit boxes stay as they were. parts adds them, as options, in any combination ('view hitboxes', or 'all'):

Part Characters The player
view The eye height (where they see and aim from) The eye height, standing and crouched
hitboxes The bones hits, attachments and bone_position use The same
collision The box others bump into, and that the game names under the crosshair The box, and the hulls the player moves with (doorways, ceilings, crouching)

How long a scale lasts (b0.5.39), the same for the player and other characters:

parts word Saves and level changes
(neither, the default) Until the level ends. Saves, and a level's state when the player leaves it, hold the entity at the size it had before, so it loads at that size; the parts end with the level
keep Always. BLSE keeps the scale and its parts in the save (its own data in the game's script globals) and applies them again after a load, when the level is entered again, and for the player after every level change ('all keep')

Saves never hold the parts' values themselves: while the game writes a save or a level's state, BLSE takes them out, with the picture's scale, and puts them back after (keep brings them back after a load). A kept entry names the map, the entity's index, class and name (a loaded save gives entities their old indexes); one that no longer matches is dropped. set_model_scale(entity, scale) without keep ends a kept scale; a new game starts without any.

The game's client draws every entity's scale now; Bloodlines sets one itself only for gibs from an env_shooter with a scale keyvalue, which none of the game's or the Unofficial Patch's maps use. Limits: NPCs still find their paths as normal-sized characters (the game has only fixed navigation hulls, and each map's paths exist only for the hulls its own NPCs use), so a scaled-up NPC with collision can get stuck in doorways and under low ceilings: scale its look, view and hit boxes without collision when it has to walk around; a large player in a low room is pushed back to a fitting size by the game's own movement code (the camera with it); the crosshair names a scaled character only where its box is, so a giant without collision is named only near its feet.

Python
# A giant for good: drawn, seen, hit and bumped into at 1.8x, after loads too.
blse.set_model_scale(handle, 1.8, 'all keep')
# A Discipline's effect: the player 1.3x until the level ends (saves keep the normal size).
blse.set_model_scale(1, 1.3, 'view collision')
# Back to normal (also ends a kept scale):
blse.set_model_scale(1, 1.0)

Appearance: bones, faces, skins, colour, materials#

For character editors and other mods that change how one character looks (b0.5.47). entity as above; while a level runs. Each setter returns 1 or 0 (0: no such entity, model, bone or controller, or unavailable on this build) and raises ValueError for numbers out of range or unknown option words.

Names (bones, flex controllers, body groups, materials) are compared without case and may be up to 127 characters.

Bones#

A bone's modifier changes it in its own space after the game has posed the skeleton: a scale along the bone's axes, an offset (model units along its axes) and a turn. The bones below it follow, as if it had been modelled that way.

In plain words: the game first poses the character for the current animation, then BLSE applies your change to that one bone, every frame. Animations keep playing; the bone is simply bigger, moved or turned within the pose. "Its own axes" means the directions the bone itself points: for a calf, one axis runs along the shin.

Function
blse.set_bone_scale(entity, bone, scale[, options]) Scales a bone, 0.05 to 8, keeping its offset and turn
blse.set_bone_offset(entity, bone, x, y, z[, options]) Moves a bone, -256 to 256 units along its own axes, keeping its scale and turn
blse.set_bone_angles(entity, bone, pitch, yaw, roll[, options]) Turns a bone (degrees, -360 to 360), keeping its scale and offset
blse.set_bone_transform(entity, bone, sx, sy, sz, ox, oy, oz, pitch, yaw, roll[, options]) The whole modifier at once (scale per axis); 1 1 1 0 0 0 0 0 0 removes it
blse.bone_transform(entity, bone) (sx, sy, sz, ox, oy, oz, pitch, yaw, roll, options); 1 1 1 0 0 0 0 0 0 '' for a bone without one; None without the model or bone
blse.reset_bones(entity[, bone]) Removes one bone's modifier, or all of the entity's
blse.modified_bones(entity) The bones that have modifiers

Options, words in one string; None or leaving them out keeps the bone's options:

Word
alone The bones below keep their own size and place (a larger head without a larger hat bone; without it, a scaled thigh scales the whole leg)
hitboxes Also the bones hits, attachments and bone_position use (shots at a larger head hit it)
keep Kept in the save and applied again after a load and when the level is entered again (the player's on every level); otherwise a modifier ends with the level

'' (an empty text) sets no options, which removes the ones the bone had. Options not available on this game version make the call return 0.

Eyes follow: when a head (or the whole model) is scaled, BLSE scales the irises, the eye glints and the eyelids' movement with it, which the game draws at a fixed size otherwise. Bone names are compared without case. Children of a bone scaled differently along its axes are sheared, as in any 3D program: for "longer legs" move the lower bones (set_bone_offset on the calf) instead of stretching the thigh. A ragdoll is a new entity and is drawn without the modifiers.

Python
head = 'Bip01 Head'
blse.set_bone_scale(npc, head, 1.3, 'alone keep')  # a bigger head, the hat bone as it was, after loads too
blse.set_bone_offset(npc, 'Bip01 L Calf', 4, 0, 0)  # a longer left shin

blse.set_bone_scale#

Python
blse.set_bone_scale(entity, bone, scale[, options])   # -> 1 or 0

Makes one bone (and, unless alone, everything below it) larger or smaller, the same along all three axes. Its offset and turn stay as they were.

When to use it: a character editor's "head size" slider; muscular arms; a tiny goblin-like face.

  • bone: a name from bones().
  • scale: 0.05 to 8 (1 is normal).
  • options: see the table above.

Returns 1 if set, 0 if not. A value out of range raises ValueError.

blse.set_bone_offset#

Python
blse.set_bone_offset(entity, bone, x, y, z[, options])   # -> 1 or 0

Moves one bone from where the animation puts it, along the bone's own axes, by x, y, z model units (-256 to 256 each). Its scale and turn stay as they were. The bones below it move along.

When to use it: longer legs (move the calf away from the knee), broader shoulders, a lower jaw.

blse.set_bone_angles#

Python
blse.set_bone_angles(entity, bone, pitch, yaw, roll[, options])   # -> 1 or 0

Turns one bone on top of the animation, by the given angles in degrees (-360 to 360 each). Its scale and offset stay as they were.

When to use it: tilt a head, bend a tail, make a hunched back.

blse.set_bone_transform and blse.bone_transform#

Python
blse.set_bone_transform(entity, bone, sx, sy, sz, ox, oy, oz, pitch, yaw, roll[, options])   # -> 1 or 0
blse.bone_transform(entity, bone)   # -> (sx, sy, sz, ox, oy, oz, pitch, yaw, roll, options), or None

set_bone_transform sets the whole modifier at once: a scale per axis (sx, sy, sz, each 0.05 to 8), an offset (ox, oy, oz, each -256 to 256) and angles (pitch, yaw, roll, each -360 to 360). 1 1 1 0 0 0 0 0 0 removes the modifier.

bone_transform reads a bone's modifier back, with its options as words (e.g. 'alone keep'). A bone without a modifier gives (1, 1, 1, 0, 0, 0, 0, 0, 0, ''). None without the model or bone.

When to use them: save and restore a character editor's settings; copy one character's look to another.

Python
t = blse.bone_transform(npc, 'Bip01 Head')
if t:
    sx, sy, sz, ox, oy, oz, p, y, r, options = t
    blse.set_bone_transform(other, 'Bip01 Head', sx, sy, sz, ox, oy, oz, p, y, r, options)

blse.reset_bones and blse.modified_bones#

Python
blse.reset_bones(entity[, bone])   # -> 1 or 0
blse.modified_bones(entity)        # -> list of names, or None

reset_bones removes one bone's modifier, or, without bone (or with None), all of the entity's modifiers, kept ones included. Returns 1, also when there was nothing to remove; 0 if the entity has no model (or no level).

modified_bones lists the bones that have a modifier, [] if none, None without a model.

Faces (flex controllers)#

Function
blse.flex_names(entity) The model's flex controllers (facial expressions), or None if it has none
blse.flex(entity, name) A controller's value in its own units
blse.flex_range(entity, name) Its (minimum, maximum)
blse.set_flex(entity, name, value[, options]) Holds the controller at the value (clamped to its range) against the game's own expressions until release_flex or the level ends; 'keep': kept in the save
blse.release_flex(entity[, name]) The game's expressions again, for one controller or all of the entity's

The value reaches the picture in 64 steps across the range. Blinking and the mouth's lip-sync still play on top.

A face is moved by its flex controllers: sliders such as a smile, a frown or a raised eyebrow. Each has its own range (often 0 to 1). Characters' heads have them; most props have none.

blse.flex_names#

Python
blse.flex_names(entity)   # -> list of names, or None

The names of the model's flex controllers, or None if it has none (or no model, no level). Use it to discover what a face can do.

blse.flex and blse.flex_range#

Python
blse.flex(entity, name)         # -> number, or None
blse.flex_range(entity, name)   # -> (minimum, maximum), or None

flex gives a controller's current value in its own units (while you hold it with set_flex, the value you set). flex_range gives the lowest and highest value it accepts. Both None if there is no such controller.

blse.set_flex and blse.release_flex#

Python
blse.set_flex(entity, name, value[, options])   # -> 1 or 0
blse.release_flex(entity[, name])               # -> 1 or 0

set_flex holds one controller at a value, against the game's own expressions, until release_flex or the level ends.

When to use it: a permanent scar-twisted sneer; a character editor's face sliders; a frightened face during a scene.

  • name: a controller from flex_names().
  • value: a number in the controller's units; it is clamped (brought) into its range. Not a number (NaN, infinity) raises ValueError.
  • options: '' (default) or 'keep' (kept in the save, applied again after loads). Another word raises ValueError.

Returns 1 if held, 0 if there is no such controller.

release_flex gives the game its expressions back for one controller, or, without name (or None), for all of them. Returns 1, also if nothing was held; 0 without a model.

Python
names = blse.flex_names(npc)
if names:
    lo, hi = blse.flex_range(npc, names[0])
    blse.set_flex(npc, names[0], hi)       # that controller all the way

Skins, body groups, colour#

The game keeps all of these in its own saves; keep applies them again after a load and when the level is entered again (the player's on every level), for example after a script gave the character another model.

Function
blse.entity_skin(entity) / blse.skin_count(entity) The model's skin, and how many it has
blse.set_entity_skin(entity, skin[, options]) 0 to skin_count - 1; 'fade': the game's cross-fade instead of at once; 'keep'
blse.body_groups(entity) [(name, count, value), ...]: the model's body groups, how many sub-models each has and the one shown
blse.set_body_group(entity, group, value[, options]) Shows sub-model value (0 to count - 1) of a group, by name; 'keep'
blse.render_color(entity) / blse.render_mode(entity) (r, g, b, a), 0-255; (mode, fx)
blse.set_render_color(entity, r, g, b[, a[, options]]) Tints the model (255 255 255 is none) and fades it; -1 keeps a value; 'keep'
blse.set_render_mode(entity, mode[, fx[, options]]) The render mode and effect, 0-255 (-1 keeps); 'keep'

An alpha below 255 fades the entity: BLSE draws it see-through the way the game fades a character during a skin cross-fade (the game itself ignores the colour's alpha for characters). The fade lasts until the entity is gone or the level ends; with 'keep' also after loads and level changes. Without 'keep' the alpha value itself stays on the entity (the game saves it), so render_color still reports it after a load or a return to the level, while the entity is drawn solid again: set it back to 255 when the fade should end. Colour and mode work on any entity, also ones without a model. Bloodlines' own character models have few body groups (most groups have one sub-model); props and mods' models use them more.

The game's Python already reads and writes ent.skin and has the inputs SetBodygroup, Color and Alpha; BLSE adds names and counts, reading alpha, render mode and FX, and keep.

blse.entity_skin, blse.skin_count and blse.set_entity_skin#

Python
blse.entity_skin(entity)                   # -> number, or None
blse.skin_count(entity)                    # -> number, or None
blse.set_entity_skin(entity, skin[, options])   # -> 1 or 0

Which skin (texture set) a model shows, how many it has, and switching it.

When to use it: a bloodied shirt after a fight, a clean one after a shower; picking one of a model's variants.

  • skin: 0 to skin_count - 1 (a model without extra skins has only 0). Outside 0..1023 raises ValueError; a number the model does not have returns 0.
  • options: words from 'fade' (the game's soft cross-fade from the old skin instead of switching at once) and 'keep'.

entity_skin and skin_count return None without a model. set_entity_skin returns 1 or 0.

Python
if blse.skin_count(npc) > 1:
    blse.set_entity_skin(npc, 1, 'fade keep')

blse.body_groups and blse.set_body_group#

Python
blse.body_groups(entity)                         # -> [(name, count, value), ...], or None
blse.set_body_group(entity, group, value[, options])   # -> 1 or 0

body_groups lists the model's body groups: each group's name, how many sub-models (versions) it has, and the one shown now. None without a model.

set_body_group shows sub-model value (0 to count - 1) of the group named group. Options: '' or 'keep' ('fade' raises ValueError). Returns 0 for an unknown group or a value out of range.

When to use it: show or hide a hat, glasses or a bag on models that have them as body groups.

Python
for name, count, value in blse.body_groups(npc):
    blse.log('%s: %d of %d' % (name, value, count))

blse.render_color and blse.set_render_color#

Python
blse.render_color(entity)                           # -> (r, g, b, a), or None
blse.set_render_color(entity, r, g, b[, a[, options]])   # -> 1 or 0

Tint an entity and fade it out.

When to use it: a ghostly see-through spirit (a 100), a sickly green tint, a character fading away when a Discipline hides it.

  • r, g, b: the tint, 0 to 255 each; 255, 255, 255 is no tint. -1 keeps the current value.
  • a (optional): the alpha, 0 (invisible) to 255 (solid). -1 (the default) keeps it.
  • options: '' or 'keep'.

Values outside -1..255 raise ValueError. Works on any entity, also without a model. render_color returns the four values, None if the entity does not exist (or no level).

Python
blse.set_render_color(npc, -1, -1, -1, 120)    # half see-through, colour unchanged
blse.set_render_color(npc, -1, -1, -1, 255)    # solid again

blse.render_mode and blse.set_render_mode#

Python
blse.render_mode(entity)                       # -> (mode, fx), or None
blse.set_render_mode(entity, mode[, fx[, options]])   # -> 1 or 0

The engine's render mode (how the entity is blended into the picture) and render effect (FX, such as pulsing or flickering), as the numbers maps use for rendermode and renderfx. Each 0 to 255; -1 keeps the current value (the default for fx). Options: '' or 'keep'. Values outside -1..255 raise ValueError. Works on any entity.

Materials#

One character drawn with other materials, without changing any other entity that uses the same model:

Function
blse.model_materials(entity) The material paths the model draws with, e.g. 'models/character/npc/common/blood_doll/bd_asp/blooddollbodyasp'
blse.set_entity_material(entity, original, replacement[, options]) Draws the meshes that use original (a path from model_materials, or just its last part) with replacement instead: a material path, without materials/ and .vmt. '' hides those meshes, None removes the replacement. 'keep'
blse.entity_materials(entity) [(original, replacement), ...] set by mods
blse.clear_entity_materials(entity) The model's own materials again

A replacement is a material (a .vmt file the mod ships, for example a copy of the original with another $basetexture), not a bare texture. The game's own effect passes on the character (Disciplines, Obfuscate) draw as before. Effects that need see-through materials on an otherwise solid model may not show.

blse.model_materials#

Python
blse.model_materials(entity)   # -> list of paths, or None

The materials the entity's model draws with, as paths without materials/ and .vmt, each once. None without a model (or when materials are unavailable). Use it to find what to replace.

blse.set_entity_material#

Python
blse.set_entity_material(entity, original, replacement[, options])   # -> 1 or 0

Draws the parts of this one entity's model that use original with another material.

When to use it: a recoloured jacket for one NPC, a unique tattoo, a gang's colours, hiding a part of a model.

  • original: a path from model_materials(), or just its last part ('blooddollbodyasp'). A leading materials/ and a trailing .vmt are removed, \ becomes /.
  • replacement: a material path, without materials/ and .vmt (your mod ships it, e.g. 'models/mymod/jacket_red'). '' hides those parts of the model; None removes the replacement (same as before your call).
  • options: '' or 'keep'.

Returns 1 if set, 0 if the model has no such material, the replacement material does not exist, or materials are unavailable on this game version.

Python
blse.set_entity_material(npc, 'blooddollbodyasp', 'models/mymod/blooddoll_red', 'keep')

blse.entity_materials and blse.clear_entity_materials#

Python
blse.entity_materials(entity)         # -> [(original, replacement), ...], or None
blse.clear_entity_materials(entity)   # -> 1 or 0

entity_materials lists the replacements mods set on the entity ('' as replacement means hidden). clear_entity_materials removes them all, kept ones included: the model's own materials again. Returns 1, or 0 without a model or when materials are unavailable.

Outfits: clothes for the player#

An outfit is a set of clothes the player can put on and take off, made of your own model (one for men, one for women). Wearing an outfit changes only how the player looks: the armor the player has equipped keeps protecting exactly as before. Several mods can add outfits side by side without editing each other's files or the game's clan models.

Why BLSE is needed for this: the game picks the player's model again (from clan, gender and armor) every time a weapon or armor is put on or away, and switching between first and third person does exactly that. A model set with the game's own SetModel therefore disappears at the next weapon switch. An outfit stays on until it is taken off, also after loading a save and after a level change.

Read this first

  • Each model must match the gender it is for. The game reads the player's gender from the model file (a flag set when the model is compiled). If a woman's model were used for a man, the player would become a woman for the game (dialogue, voice). BLSE checks this and refuses such a model; blse.log then says made for the other gender.
  • The model files must be in your mod folder (or the game's), with all their files: the .mdl and, beside it, its .dx80.vtx, .dx7_2bone.vtx and .phy files, and the materials the model uses. A missing model is refused, never shown as an error model.
  • Your model needs the player's animations. Build it on the game's player skeleton, the way the game's own clan models are built, or it will stand still or stretch (an NPC model used as an outfit also looks broken on the character sheet, while the game's player models look right there).
  • The first-person hands do not change. What you see in first person (lockpicking, a held weapon) is a separate hands model the game picks for the clan, not the player's model. An outfit changes how the player looks in third person and to everyone else; the hands stay the clan's.
  • A save remembers the outfit. If the mod that added the outfit is removed later, that save still loads: the player gets the game's own model back and blse.log says why.
  • During the Protean beast form the beast is shown as usual; the outfit comes back after it.
  • BLSE does not make a shop. Items, prices, a merchant or a menu that sells outfits are your mod's part; it calls the functions below.

The quickest way: an outfits file, no code#

Put a text file named outfits.txt in your mod folder's blse folder (see Where mod files go; new to mod folders? Start with Start here):

text
Bin/loader/blse/mods/my_clothes/
    blse/
        outfits.txt
    models/
        my_clothes/
            leather_m.mdl        (and its .vtx and .phy files)
            leather_f.mdl
    materials/
        models/
            my_clothes/          (the models' materials and textures)

outfits.txt:

ini
; My clothing mod: one outfit
[leather_jacket]
name = Leather jacket
male = models/my_clothes/leather_m.mdl
female = models/my_clothes/leather_f.mdl

Start the game. blse.log says outfits: my_clothes: 1 outfit(s) from blse/outfits.txt, and the console command outfits lists it. The outfit is now registered: it exists, but nobody wears it yet. To put it on, a script calls blse.wear_outfit('leather_jacket') (for example when the player buys it). To try it right away, add the small script from Wearing outfits from a script and type wear_jacket in the console.

The file, line by line:

  • ; ... or # ... at the start of a line is a comment.
  • [leather_jacket] starts an outfit and gives its id, the name scripts use for it: 1 to 63 letters a-z, digits and _ (upper case is turned into lower case). Every [id] starts a new outfit; a file can hold as many as you like.
  • Each line below it is key = value. Spaces around = do not matter.
Key What it means Example
name A name for your shop or menu to show. BLSE itself does not use it. Optional. name = Leather jacket
male The model a male player wears: a path starting with models/ and ending with .mdl, as in your mod folder. male = models/my_clothes/leather_m.mdl
female The model a female player wears. female = models/my_clothes/leather_f.mdl
male.<clan> A model only for men of one clan, instead of male; the clan as the game names it (Brujah, Gangrel, Malkavian, Nosferatu, Toreador, Tremere, Ventrue; the game's full name such as Player_Nosferatu works too). Useful for Nosferatu, whose bodies differ. male.Nosferatu = models/my_clothes/leather_nos_m.mdl
female.<clan> The same for women of one clan. female.Nosferatu = models/my_clothes/leather_nos_f.mdl
skin Which skin of the model to show, if it has several (0 is the first). skin = 1
body.<group> Which part of a body group to show, by the group's name (0 is the first part). body.hood = 1
material.<original> Draws one of the model's materials with another one (your .vmt, path without materials/ and .vmt); empty hides those parts of the model. material.models/my_clothes/leather = models/my_clothes/leather_red

An outfit needs at least one model. If it has none for the player's gender (or clan), it cannot be worn by that player: wear_outfit returns 0. Bad lines are reported in blse.log with the outfit's id and the rest of the outfit still works. Mods are read in load order; if two mods use the same id, the later mod's outfit is used and blse.log says so.

Wearing outfits from a script#

A script decides when the player wears what. The simplest shop: a merchant sells an item (made with blse/items.txt), and a script puts the outfit on when the player uses or buys it.

Python
# my_clothes/blse/scripts/my_clothes.py
import blse

def wear_jacket(name, args):
    if blse.wear_outfit('leather_jacket'):
        blse.console_print('You put on the leather jacket.\n')

def take_it_off(name, args):
    blse.take_off_outfit()

blse.add_command('wear_jacket', wear_jacket, 'Puts on the leather jacket')
blse.add_command('jacket_off', take_it_off, 'Takes it off')

The functions:

blse.wear_outfit(id) puts an outfit on the player.

  • id: the outfit's id, from outfits.txt or blse.add_outfit.
  • Returns 1 when the player wears it (also if it was already worn), 0 if it cannot be worn: unknown id, no model for the player's gender or clan, a missing file, a model of the other gender, or not available on this game version. The reason is written to blse.log.
  • The outfit stays on through weapon and armor changes, first / third person, saves, loads and level changes, until another outfit is worn or it is taken off. Called while a level is loading or during the beast form, it is put on as soon as possible (it still returns 1).
  • Example: blse.wear_outfit('leather_jacket')

blse.take_off_outfit() takes the outfit (or a model set with set_player_model) off. The game picks its own model again from clan, gender and armor, and skin, body groups and materials the outfit changed are put back.

  • Returns 1, or 0 if the player wore nothing from BLSE.
  • Example: blse.take_off_outfit()

blse.worn_outfit() tells which outfit the player wears.

  • Returns the id ('leather_jacket'), or None if none.
  • Example: if blse.worn_outfit() == 'leather_jacket': ...

blse.outfits() lists every registered outfit, for a shop's list.

  • Returns a list of ids, e.g. ['leather_jacket', 'suit'].
  • Example: for i in blse.outfits(): blse.console_print(i + '\n')

blse.outfit_info(id) shows what an outfit is made of, for a shop to show its name.

  • Returns a dict with the keys above that were given (values as text) plus 'id' and 'who' ('mod <folder>', 'python' or the name of the plugin that registered it), or None for an unknown id.
  • Example: blse.outfit_info('leather_jacket')['name'] gives 'Leather jacket'.

blse.add_outfit(id, fields) registers an outfit from a script, instead of outfits.txt (for example outfits a script builds itself).

  • id: as in the file.
  • fields: a dict with the same keys as the file; numbers may be numbers or text.
  • Returns 1, or 0 if outfits are not available on this game version. A bad id, key or value raises ValueError saying which (TypeError if fields is not a dict).
  • Register when your script starts, so outfits worn in a save exist when it loads. Registering the same id again replaces the outfit (the later one wins, logged); a worn outfit changes at once.
  • Example:
    Python
    blse.add_outfit('suit', {'name': 'Black suit', 'male': 'models/my_clothes/suit_m.mdl',
                             'female': 'models/my_clothes/suit_f.mdl', 'skin': 0})

One model for the player, without an outfit#

blse.set_player_model(model[, options]) puts one model on the player directly, without registering an outfit: handy for a quest disguise or a test. It stays through weapon and armor changes like an outfit.

  • model: a path like 'models/my_mod/disguise.mdl'. None or '' removes it (the game's model comes back).
  • options: '' (the default): the model lasts until the level ends (a level change or a load brings the game's model back). 'keep': it is stored in the save and lasts until removed.
  • Returns 1 or 0 (refused: a bad path, a missing file, a model of the other gender; the reason is in blse.log). An unknown option raises ValueError.
  • The same model is used for both genders, so it must match the player's. Wearing an outfit replaces it, and the other way round: the player has one slot.
  • Example: blse.set_player_model('models/my_mod/disguise.mdl', 'keep')

blse.player_model_override() returns the model set with set_player_model, or None.

blse.player_model() returns the model the player has right now (the game's, an outfit's, a cutscene's), e.g. 'models/character/pc/male/brujah/armor0/brujah_male_armor_0.mdl', or None while no level runs. Useful to check what is shown.

When the outfit changes: the event#

blse.outfit_changed is sent every time what the player wears from BLSE changes, so a shop or another mod can react (update a menu, give a bonus). The data is four texts separated by '\0':

  1. the outfit now: its id, '*' for a model from set_player_model, or '' for none;
  2. the outfit before, in the same form;
  3. the model BLSE put on ('' when the game's own model is back);
  4. why: 'wear' (put on), 'remove' (taken off), 'load' (put on again from a loaded save), 'missing' (a save's or the worn outfit's mod or model is gone: the game's model is back), 'level' (a set_player_model without 'keep' ended with the level), 'unregistered' (the plugin that added the worn outfit went away).
Python
def on_outfit(topic, data):
    now, before, model, why = data.split('\0')
    if why == 'missing':
        blse.console_print('Your outfit could not be found; you wear your own clothes again.\n')

blse.subscribe('blse.outfit_changed', on_outfit)

Exact behaviour#

  • What BLSE watches: the game sets the player's model in one function. When it asks for one of its clan models (any clan, any armor) while an outfit is worn, BLSE gives the outfit's model instead. Any other model, from a game script or a cutscene (pc.SetModel(...)), is shown as the game wants; the outfit comes back the next time the game picks a clan model, exactly as the armor's model would.
  • The beast form (Protean): the beast is shown; when it ends, the outfit is shown again.
  • Gender or clan changes (character creation, the console's vgender_int / vclan): the outfit's model for the new gender or clan is used; if it has none, the game's model is shown until the outfit is taken off or another is worn.
  • Skin, body groups and materials are put on one frame after the model and taken back off with the outfit; the player's skin and body groups from before the first outfit are restored.
  • Saves: BLSE stores the worn outfit's id (or a kept set_player_model model) in the save, under its own name. The save itself always holds the game's own model for the player (and the skin and body groups from before the outfit), so it loads normally even without the mod or without BLSE. Loading a save puts it on again at the first frame. A save from a game without outfits loads unchanged.
  • Nothing changes in the game until a mod first puts an outfit on the player: registering outfits alone does not change any game code.
  • The first-person hands are the game's (the clan's own, as in "Read this first"): an outfit changes the body, which is seen in third person and wherever the game shows the player's model.
  • On a game version BLSE does not know, outfits are unavailable: wear_outfit returns 0 and blse.log says so at startup.
  • The console command outfits lists every outfit, who registered it, the worn one and the player's model.