On this page 13 sections
- Words used on this page
- Damage
- blse.set_damage_multiplier, blse.clear_damage_multipliers
- blse.damage
- Stat changes
- Disciplines
- Blocking Disciplines and changing their cost
- Kills, damage, pushes and console commands
- blse.entity_killed
- blse.damage_entity
- blse.entity_velocity, blse.push_entity
- blse.console_command
- The game's Python errors
This page is about fighting and character values: changing how much damage hits do, knowing every hit, kill and stat change (blood, health, Humanity, Disciplines), blocking Disciplines or changing their blood cost, hurting and pushing entities, and running console commands. Come here for a difficulty mod, a survival mod, a mod that rewards kills, or new rules for Disciplines.
A minimal working example: the player takes half damage, and every kill by the player is announced.
import blse
blse.set_damage_multiplier('player', 0.5)
def on_killed(topic, data):
f = data.split('\0')
if f[3] == 'player':
blse.show_text('You killed %s' % (f[1] or f[0]), 'top')
blse.subscribe('blse.entity_killed', on_killed)Important, before you start:
- Almost everything here needs a recognized game build (
blse.build_id()is notNone). On other builds the rule functions raiseRuntimeErrorand the events are never sent. - Rules are not saved with the game and are cleared on an engine restart: set them when your script runs (at the top of the file is fine; they need no level).
- Events reach Python at the end of the frame, after the game applied the change. To change a hit before it lands, use a rule (
set_damage_multiplier); native plugins can also use filters. - Do not change a stat from a
blse.stat_changehandler without a guard: your change is reported again and can loop.
Words used on this page#
| Word | Meaning |
|---|---|
| Victim, attacker, inflictor | Who is hit; who caused it; what delivered it (the weapon or projectile) |
| Damage type bits | A number whose bits say what kind of damage a hit is: 0 generic, 2 bullet, 128 club, ... (the Source engine's DMG_* values) |
| Stat | A character value the game keeps in its stat lists (vdata/system/stats.txt): blood pool, health (damage taken), Humanity, attributes, abilities, Disciplines |
| Base value | A stat's own value, before temporary effects |
| Discipline | A vampire power (Potence, Celerity, Auspex, ...). Activating one costs blood |
| Rule | A setting made with a set_..._rule or set_..._multiplier function; the game applies it itself, with no Python running at that moment |
| Velocity | Speed and direction, in units a second, as (x, y, z) |
Damage#
On recognized game builds, BLSE sees every hit the game applies through CBaseEntity::TakeDamage: weapons, fists, scripted damage, damage to props.
| Function / topic | |
|---|---|
blse.set_damage_multiplier(victim, factor[, attacker]) |
Multiplies damage to victim from attacker (default '*'). Each is '*', a classname ('player', 'npc_VVampire'), a targetname, or (b0.5.48) '#' and an entity's handle for that one entity ('#%d' % npc: a companion with no name of its own; set it again after a load, which gives entities new handles). Factor 0 means no damage; 1 removes the rule. Matching rules multiply. Returns 1; raises RuntimeError on unknown builds |
blse.clear_damage_multipliers() |
Removes every rule set from Python |
blse.damage |
Report after each hit: victim_class, victim_name, attacker_class, attacker_name, inflictor_class, inflictor_name, damage, original, type = data.split('\0'). damage is what was applied after the rules (before the game's own difficulty/resistance scaling), original what the game asked for, type the damage-type bits. Delivered at the end of the frame |
import blse
blse.set_damage_multiplier('player', 0.5) # the player takes half damage
blse.set_damage_multiplier('*', 2.0, 'player') # and deals doubleThe rules are applied natively inside the game's damage code, so no Python runs there. They are not saved with the game; set them again when your script runs (they are cleared on an engine restart, when scripts run again anyway).
blse.set_damage_multiplier, blse.clear_damage_multipliers#
blse.set_damage_multiplier(victim, factor[, attacker]) # -> 1
blse.clear_damage_multipliers()(b0.5.3) When to use it: difficulty settings, armour that protects against one enemy, a companion that cannot be hurt by the player.
victim,attacker:'*'(anyone), a classname ('player','npc_VVampire'), a targetname, or'#'and a handle ('#%d' % npc, b0.5.48) for one entity. Names are 1-63 characters.attackerdefaults to'*'.factor: 0 or more;0no damage,0.5half,2double,1removes the rule. When several rules match a hit, their factors multiply.- Returns 1. Errors:
ValueErrorfor a bad name or a negative factor;RuntimeErroron unknown builds. clear_damage_multipliers()removes every rule set from Python; returnsNone.- A handle rule must be set again after a load (entities get new handles).
blse.damage#
Sent after each hit the game applied (b0.5.3), at the end of the frame. Fields, in order: victim classname, victim name, attacker classname, attacker name, inflictor classname, inflictor name, damage (what was applied after the rules, before the game's own difficulty and resistance scaling), original (what the game asked for), type (the damage type bits). Numbers are text: float(damage), int(type). For the entities' handles, use blse.hit.
def on_damage(topic, data):
vcls, vname, acls, aname, icls, iname, damage, original, kind = data.split('\0')
if vcls == 'player':
blse.log('player hit by %s for %s' % (acls, damage))
blse.subscribe('blse.damage', on_damage)Stat changes#
On recognized game builds, blse.stat_change (b0.5.5) reports every change to a character stat's base value, for the player and for NPCs: blood, damage taken, humanity, attributes, abilities, Disciplines and which of them are active.
import blse
def on_stat(topic, data):
owner, index, old, new, method, cause, entity, name = data.split('\0')
if owner == '1' and name == 'bloodpool':
blse.log('blood %s -> %s (%s)' % (old, new, cause.split(' < ')[0]))
blse.subscribe('blse.stat_change', on_stat)| Field | |
|---|---|
owner |
Entity index of the character. The player is 1 |
index, name |
The stat, as defined in vdata/system/stats.txt, by its internal (lower-case) name: bloodpool, health, humanity, strength, brawl, potence, active_potence, ... The index is its position within its group (Attributes, Abilities, Disciplines, ActiveDisciplines), so compare names, not indices |
old, new |
Integer values before and after |
method |
The game method that made the change: IncBase, DecBase, AddBase, SubBase, SetBase |
cause |
The game functions that led to it, innermost first, separated by ' < ' (from the game's own crash-report trace). Only engine frames (RunListenServer < CEngineAPI::Run ...) when the change came from player input or a game script, such as activating a Discipline |
entity |
The entity name the innermost cause reported, if any (for kills: the victim) |
Delivered at the end of the frame. Filling a new character's stats with their defaults is not reported.
When to use it: a survival mod that watches blood, a mod that rewards Humanity changes, a HUD that shows a stat. owner, index, old and new are text: compare owner == '1' for the player and use int(new). Experience, Masquerade violations and Humanity also arrive here (names experience, masquerade, humanity).
healthcounts damage taken, not health left:0is unhurt, it rises when hit (OnTakeDamage_Alive) and falls when healing (HealthHeal < UpdateVampHeal_HOT).- Feeding: the player's
bloodpoolrises by 1 per gulp (IncBloodPool < Feed), the victim's falls (DecBloodPool < Feed) and itshealthrises by 3. - Potence:
bloodpoolfalls by 1 (CVStatAction_t::Apply),active_potencegoes0 -> 2, and back to 0 when it runs out (GameFrame). - Killing an innocent:
humanityfalls by 1, causeEvent_Killed,entitynames the victim.
Disciplines#
On recognized game builds (b0.5.14):
| Topic | data |
|---|---|
blse.discipline_activated |
owner_class, owner_name, owner_index, discipline, level = data.split('\0'): a character activated a Discipline. discipline is its stats.txt name in lower case (potence, celerity, presence, blood_healing, auspex, obfuscate, ...); the player is owner_index == '1'. owner_class and owner_name are empty if BLSE cannot confirm the owner |
blse.discipline_ended |
Same fields: the Discipline's effect ended (ran out or was cancelled) |
They are derived from the active_<discipline> stats, so the same changes also appear in blse.stat_change. Delivered at the end of the frame. To block a Discipline or change its cost, see the next section (b0.5.20).
level is the Discipline's level as text (int(level)); owner_index is the entity index as text ('1' the player).
Blocking Disciplines and changing their cost#
blse.set_discipline_rule(discipline[, who[, allow[, cost]]]) (b0.5.20) controls activations before the game applies them. discipline is a stats.txt name ('potence') or '*'; who is '*' (default), 'player', a classname or a targetname. allow=0 blocks the activation: no effect and no blood cost. cost multiplies the blood cost (0 free, 2 double; rounded to whole blood points, halves away from zero). Matching rules all must allow, and their costs multiply; allow=1, cost=1 removes a rule. blse.clear_discipline_rules() removes Python's rules (they are also cleared on an engine restart).
import blse
blse.set_discipline_rule('potence', 'player', 0) # the player cannot use Potence
blse.set_discipline_rule('*', 'player', 1, 0.5) # every Discipline at half cost
blse.set_discipline_rule('celerity', 'npc_VCop', 0) # cops cannot use Celerityblse.set_discipline_rule, blse.clear_discipline_rules#
blse.set_discipline_rule(discipline[, who[, allow[, cost]]]) # -> 1
blse.clear_discipline_rules()discipline: a stats.txt name in lower case ('potence','celerity') or'*'for all.who:'*'(default),'player', a classname or a targetname; 1-63 characters.allow: 1 (default) allows, 0 blocks.cost: multiplies the blood cost, 0 or more (default 1).- Returns 1. Errors:
ValueErrorfor a bad name or a negative cost;RuntimeErroron unknown builds. clear_discipline_rules()removes Python's rules; returnsNone.- Native plugins can decide per activation with a Discipline filter; Python rules are applied first.
blse.discipline_blocked reports each blocked activation (same fields as discipline_activated). The game would show its "Need More Blood" notice for a blocked activation (a failed activation is all it knows); since b0.5.21 BLSE hides that notice, and a mod can show its own explanation with blse.show_text.
Kills, damage, pushes and console commands#
blse.entity_killed (event) |
An entity with health (a character, a breakable object) was killed by a hit. Data: victim class, name, handle; attacker class, name, handle; inflictor (the weapon or projectile) class, name, handle; damage; damage type bits, separated by '\0' (empty fields where there is none) |
blse.damage_entity(entity, amount[, attacker[, type]]) |
Hurts an entity through the game's own damage: the victim reacts to the attacker (flees, fights back), and a kill counts as the attacker's (the player loses Humanity, the police come). attacker: an entity, or -1 (the default) for none; type: damage type bits (0 generic, 128 club, 2 bullet...). Damage rules, filters and blse.damage apply. Returns 1 or 0 |
blse.entity_velocity(entity) |
(x, y, z) in units a second, or None |
blse.push_entity(entity, x, y, z[, add]) |
Adds the velocity (add 1, the default) or sets it (0). A push upwards takes the entity off the ground first. The player, NPCs (b0.5.44) and physics props (b0.5.44). Returns 1 or 0 |
blse.console_command(line) |
Runs a console command line as if typed (queued: it runs before the next frame). 1-1023 characters, no line breaks |
push_entity moves the player by its velocity. NPCs set their own velocity every time they think, so since b0.5.44 a push on an NPC uses the game's own flying knockback (what a strong blow does) with the given velocity: the NPC flies (the animation chosen from the direction), lands and gets up. It returns 0 when the game would not knock that NPC back now (in a conversation, dead) or the NPC has no flying animation. Physics props (prop_physics: cans, bottles, boxes) are moved by the physics engine, so a push on them sets their physics object's velocity (b0.5.44). entity_velocity of a prop is not its physics velocity.
def on_killed(topic, data):
f = data.split('\0')
victim, killer, weapon = f[1] or f[0], f[3], f[6]
if killer == 'player':
blse.console_print('You killed %s with %s' % (victim, weapon))
blse.subscribe('blse.entity_killed', on_killed)blse.entity_killed#
(b0.5.41) Fields, in order (11): victim classname, name, handle; attacker classname, name, handle; inflictor classname, name, handle; the damage; the damage type bits. Empty fields where there is none. Delivered at the end of the frame (native plugins right after the hit). Handles are text: int(f[2]).
blse.damage_entity#
blse.damage_entity(entity, amount[, attacker[, type]]) # -> 1 or 0(b0.5.41) When to use it: a trap, poison, a Discipline of your own, a scripted execution.
entity: an index or handle.amount: 0 to 1000000 (elseValueError).attacker: an entity, or -1 (default) for none. With an attacker the victim reacts to it and a kill counts as the attacker's.type: damage type bits (default 0, generic).- Returns 1, or 0 if the entity (or the attacker given) does not exist or the game refused the damage.
blse.damage_entity(npc, 25, 1, 128) # the player clubs the NPC for 25blse.entity_velocity, blse.push_entity#
blse.entity_velocity(entity) # -> (x, y, z) or None
blse.push_entity(entity, x, y, z[, add]) # -> 1 or 0(b0.5.41; NPCs and props b0.5.44) entity_velocity returns the entity's speed and direction in units a second, or None. push_entity adds (add 1, the default) or sets (0) a velocity; each component -10000 to 10000 (else ValueError). Returns 1, or 0 if the entity cannot be pushed now. When to use it: a knockback blow, a jump boost, throwing a prop.
blse.push_entity(1, 0, 0, 400) # the player jumpsblse.console_command#
blse.console_command(line) # -> 1 or 0(b0.5.41) Runs a console command line as if typed. It is queued and runs before the next frame, so its effect is not visible right after the call. line: 1-1023 characters, no line breaks (else ValueError). Returns 1 if queued, 0 if the console is not available.
blse.console_command('fov 90')The game's own Python already reaches an entity's appearance: fields by name (ent.renderamt = 128, ent.skin = 1) and inputs as methods (ent.Kill()).
The game's Python errors#
Python's error output (the game's tracebacks) is always copied to blse.log (b0.5.40). When game code run as a string fails (a map's output naming Python, a dialogue condition), the log also names that code and where in vampire.dll it was run from (b0.5.44), since the traceback only says <string>. Not logged: map outputs calling ccmd.<name>, which never worked and fail harmlessly (every map's leftover cubemaps_builder entity calls ccmd.wc_create 1.5 s after it loads; the game still prints its SystemError).
BLSE fixes none of the game's own bugs (b0.5.49): game fixes belong to a separate fixes mod. blse.fixes() and the [Fixes] section of config/blse.ini (b0.5.41-b0.5.48) are gone; BLSE removes the old section's entries. The old long_saved_lines fix is part of BLSE's save system since b0.5.49, always on (see Saving data).