Combat Event Scripts
"implements" : "combatEvent" in a mod's scripts section declares a combat event script. These scripts handle unit events during battle, including waits, attacks and round starts. Use them for effects triggered by battle events.
Attaching a script to a unit
A COMBAT_EVENT_TRIGGER bonus assigns a combat event script to a unit. Creature abilities, artifacts, secondary skills and other bonus sources can grant it:
A spell grants the same bonus for its own duration through the built-in attachCombatScript spell effect:
eventParameters is passed unchanged and validated against the script's schema. The engine does not add caster data. Include the spell, spell power or other required caster data in this object.
Description shown to the player
Every scripted ability uses the same COMBAT_EVENT_TRIGGER bonus type. The script's description field provides its player-facing text. ${val} is replaced with the total value of bonuses granting this script. ${parameterName} is replaced with the corresponding bonus parameter:
Writing a script
Scripts extend combatScript and define only the required event methods. Events without a matching method are skipped:
Assumptions and guarantees
VCMI guarantees the following:
- every implemented handler executes for its bearer. Events without a matching handler are skipped
- bonus parameters are read-only. Persistent state requires a separate bonus
- each action dispatches
onActionFinishedonce, regardless of attack, counterattack or target count - script effects can generate
onDeath, but script-applied spells do not generateonSpellHit. Death handlers generated by a script execute after the current handler returns - handlers execute serially and do not nest
- every matching handler executes. A script must skip any counterattack, repeated attack or dead-bearer case it does not support
Event handlers
All handlers share the same signature and return nothing:
Signature: function Script:on<Event>(server, battle, unit, other, payload)
Bonus parameters initialize fields of self. For example, { "poison" : true } in addInfo becomes self.poison. The bonus value becomes self.val.
Each bonus granting the same script creates a separate handler invocation with its own val. Values are not combined before execution. Use the same stacking group for all sources when only the strongest bonus should apply, as in the core fire shield configuration.
Parameters:
server- callback for battle state modifications. See BattleServerbattle- current battle state. See Battleunit- bonus bearer. See Unitother- other event participant, such as the attacker; may be nilpayload- data about the attack or spell that caused this event. Fields unused by an event keep their default values:ranged- whether the attack was a shotisCounter- whether the attack is a counterattack, either a first strike or a regular retaliationattackIndex- position of this attack among attacks made by the same side during the action. The first attack and every counterattack use0; the second attack of a double attack uses1targets- one entry per affected unit. Each entry containsunit,damage,killed,damageBeforeDefenseandhealthBeforeAttack. Before the attack, onlyunitandhealthBeforeAttackare set because damage has not been calculatedspell- spell that causedonUnitSpellcastoronSpellHit; nil for other events
For onSpellHit, unitBefore contains the target state captured before spell effects. It is nil for other events.
Every handler receives the complete target list. self:ownEntry(unit, payload) returns the entry for the handling unit. target.unit is nil if the event removed the unit from the battlefield.
Handlers:
onBeforeAttack- executes on the attacker before each attack, including counterattacksonBeforeAttacked- executes before each attack on every target, including secondary targetsonAfterAttack- executes on the attacker after the attack. It also executes if an attack reaction killed the attacker; checkunit:isAlive()when requiredonAfterAttacked- executes after the attack on every target. It also executes after a lethal hit; checkunit:isAlive()when requiredonWait- executes whenunitwaitsonDefend- executes whenunitdefendsonBeforeMove- executes before movement. See MovesonAfterMove- executes after movement, once for everyonBeforeMoveonUnitSpellcast- executes afterunitcasts a spellonSpellHit- executes after casting on every affected unit.otheris the casting unit, or nil for a hero cast. See Spell hitsonDeath- executes after the action that killedunit.otheris the killing unit, or nil for deaths caused by spells, moats or scripts. See DeathsonActionFinished- executes once after action resolution on the actor and every affected unit.otheris the actor, or nil for a hero cast. See The end of an actiononBattleSetup- executes once for every unit before tactics.otheris nilonBattleStart- executes once for every unit after tactics and before opening spells.otheris nilonRoundStart- executes for every alive unit at the start of each round after the first.onBattleStartcovers the first round.otheris nil
Attack events execute in this order for each attack:
The attacker and targets form one group ordered by priority. The side of the attack does not affect order. Life drain at priority 0 therefore executes before fire shield at priority 50.
The complete sequence also executes for counterattacks, additional attacks and attacks whose bearer dies during resolution. Each script decides whether its effect still applies.
Moves
A move is a move action, movement before a melee attack or adjacent spell cast, or return movement from RETURN_AFTER_STRIKE. Return movement is a separate move. No movement events execute when the unit remains on its current hex.
Every onBeforeMove has a matching onAfterMove, including movement cancelled after a handler changes the unit or destination. Movement events preceding an attack execute before attack count is calculated, so ADDITIONAL_ATTACK granted by them applies to the current attack.
Spell hits
onSpellHit is generated only by deliberate hero and unit casts, including Enchanter casts. Moats, spell-like attacks, obstacle effects and script-applied spells do not generate it.
Deaths
Death events execute after the action that caused them. Deaths from one action form one batch. Deaths caused by handlers form subsequent batches until none remain. Clones are included. payload.targets contains one entry per death in the batch; killed is the number of creatures killed by the lethal hit.
The end of an action
onActionFinished executes once for the actor and every unit affected by the action, after counterattacks and additional attacks. Use it to apply effects accumulated across the action. other identifies the actor; compare it with unit to detect the unit's own action.
Built-in scripts
Every combat event script declares priority in its scripts entry. Handlers for the same event execute from lowest to highest priority. The usual value is 0. The field is required because the fallback bonus order is alphabetical and therefore unsuitable for behavior ordering.
Priority also orders death handlers. Resurrection scripts use a priority below 100. Scripts that react to a final death use 100 or higher.
ballistaDamage
Applies a CREATURE_DAMAGE bonus to a war machine during battle setup. The value uses its hero's base and artifact attack, multiplied by bonus val. Army, spell and terrain attack bonuses are excluded.
Override getDamageRange(unit, minDamage, maxDamage) in a patch to change the formula. It returns the final minimum and maximum damage.
arrowTowerDamage
Applies arrow tower damage calculated from the defended town's buildings during battle setup. Outside a siege, the tower keeps its creature damage.
Parameters:
keepBase- damage of the keep in a town with nothing builttowerBase- damage of the two lesser towers in a town with nothing builtperBuilding- damage each building adds to the keep; the lesser towers get half of it
The scripts below preserve compatibility with retired bonuses. They reproduce the corresponding H3 and WoG behavior, including quirks. Mods requiring different behavior should provide separate scripts.
rebirth
Resurrects the bearer once per battle. The restored count is a percentage of the initial stack count and uses random rounding. Clones are not resurrected. The resurrected stack cannot retaliate until its next turn.
Priority 0 executes resurrection before final-death reactions.
Parameters:
val- share of the starting size of the stack that is resurrected, in percentguaranteed- if true, restores at least one creature
lifeDrain
Restores part of the bearer's damage as health and may resurrect creatures in the stack. Only damage dealt to living targets counts.
Priority 0 applies healing before later attack reactions.
Parameters:
val- share of the dealt damage restored to the attacker, in percent
fireShield
Deals fire damage to a melee attacker based on damage before defence. Fire-immune attackers and secondary targets of area attacks take no reflected damage.
Priority 50.
Parameters:
val- share of the reflected damage, in percent
deathStare
Kills creatures in the attacked stack. Each creature in the bearer stack provides one chance, and eligible attackers limit the maximum kills.
Priority 100 executes after reactions that may kill the bearer and prevent the effect.
Parameters:
val- chance for each creature to kill one, in percentsituation- when the ability applies:"melee","ranged","rangedDistancePenalty","rangedWallPenalty"or"rangedDistanceAndWallPenalty"spell- spell used for the animation, immunities and combat log. Defaults to death stare
killsIn returns the kill count, or nil when the ability does not apply. Override it in a patch to add another situation. combat/deathStareCommander is an example:
"commander" is DEPRECATED and is defined by that patch. It preserves the commander skill converted from DEATH_STARE. For this situation, val is the kill count before applying the stack-level ratio. Add new situations through patches.
enchanted
Reapplies a spell to the bearer or its side at the start of every round.
Parameters:
spell- spell whose effects are appliedlevel- mastery level the effects are applied atmassive- true to affect every allied unit instead of only the bearerduration- how many turns the effects last. Defaults to 50, long enough for the effect to accumulate instead of expiring between rounds
summonGuardians
DEPRECATED, transition only - see the note at the start of this section.
Summons guardians around the bearer at battle start. Placement follows H3 rules, including positions near the bearer's battlefield edge.
Parameters:
creature- creature to summon as guardianval- size of each guardian stack, in percent of the guarded stack
transmutation
DEPRECATED, transition only - see the note at the start of this section.
Replaces the attacked stack with another creature type, as used by the WoG werewolf ability. Non-living units and units with TRANSMUTATION_IMMUNITY are not affected.
Priority 300.
Parameters:
val- percentage chance to trigger on each attackcreature- creature the victim turns into. Defaults to the attacker's own creaturetransmuteBy-"health"keeps the total health of the victim,"count"keeps its creature count
soulSteal
DEPRECATED, transition only - see the note at the start of this section.
Adds creatures to the bearer stack for each killed enemy creature, including growth beyond the original stack size. Only living targets count.
Parameters:
val- creatures gained for each killed enemy creaturepermanent- true to keep the gained creatures after the battle
destruction
DEPRECATED, transition only - see the note at the start of this section.
Kills creatures of the attacked stack outright, on top of the damage the attack itself dealt.
Priority 400.
Parameters:
val- percentage chance to trigger on each attackkillBy-"percentage"kills a share of the victim's stack,"count"kills a fixed numberamount- the share, or the number of creatures, depending onkillBy