Player animation script (mp/playeranim.script)

Status and authority

This page specifies mp/playeranim.script as implemented by the recovered stock Call of Duty: United Offensive game module. The authority is:

  • src/bg/bg_animation_script_parser.c for the file and section parser;

  • src/bg/bg_parse_conditions.c and src/bg/bg_parse_condition_bits.c for condition clauses and aliases;

  • src/bg/bg_parse_commands.c for animation commands;

  • src/bg/bg_animation_conditions.c for condition evaluation and script selection;

  • src/bg/bg_animation_execution.c for playback and event dispatch;

  • src/bg/bg_animation_script_data.c for the stock name tables;

  • src/bg/bg_animation_lookup.c for animation-name registration;

  • src/bg/bg_animation_finalize.c for the static player animation table.

The page describes the stock multiplayer file. It does not describe a later Call of Duty animation-script dialect, a single-player file, or a custom compatibility extension.

What the file controls

The player animation script is a condition table. It answers:

  • which animation a player’s legs or torso plays for a state and move type;

  • which animation plays when an event occurs, such as firing or landing;

  • which animation plays when the player changes state;

  • which canned animation set is available to script code.

It does not define animation names. Every animation name in the file must exist in the loaded multiplayer animation tree. The player animation table is rebuilt from that tree, and a name that is absent from the tree is a load error.

The file is not GSC. It has its own parser, its own section keywords, and its own condition and command grammar. It is also not the AI animation layer. The AI layer is a set of GSC scripts described in AI animation scripts (animscripts/*.gsc).

File and load order

The engine loads exactly one path:

mp/playeranim.script

The path is fixed in the stock build. The file is read once and cached. The parser then runs twice:

  1. Before animation trees are finalized, the parser registers the animation names used by the file as runtime animation names. For each new name it records a reference to the multiplayer tree through Scr_FindAnim.

  2. After the static player animation table is rebuilt from the multiplayer tree, the parser runs again. This pass stamps animation-group state flags, event flags, and blendtime values onto the static entries, and fills each command slot duration from the entry duration.

The two passes explain why a name can be accepted in the first pass and still fail if the multiplayer tree does not define it. Add the node to animtrees/multiplayer.atr first, then reference it here.

The parser does not accept a path argument, a second file, or an include. There is no #include and no section inheritance.

Sections

A file is a sequence of sections. A section keyword is recognized only at brace depth zero. The section keywords are, in parser table order:

defines
animations
canned_animations
statechanges
events

A section keyword at a deeper brace depth is an error. A new section resets the current animation group and the current event.

The parser starts in the defines section. A set token before the first section keyword is parsed as a definition. Any other token that is not a section keyword is skipped. A non-set token in defines is also skipped. This tolerance lets stray text in the stock file pass without becoming syntax. Comments are removed by the tokenizer.

DEFINES

DEFINES builds named bit masks for bitmask condition types. The grammar is:

set <condition-type> <alias> = <bit-expression>

Only three condition types are bitmask types and therefore valid after set:

  • WEAPONS;

  • WEAPONCLASS;

  • MOVETYPE.

A set for any other condition type is an error:

BG_AnimParseAnimScript: can not make a define of type '%s'

Stock examples:

set weapons stickgrenades = RGD-33russianfrag_mp AND Stielhandgranate_mp
set weaponclass autofire = mg AND smg
set movetype moving = walk AND walkcr AND walkcrbk AND walkbk AND run AND runbk AND runcr AND runcrbk AND walkprone AND walkpronebk

The bit expression language is:

expression = [ "MINUS" | "NOT" ] clause
             { ( "AND" | "MINUS" | "NOT" ) clause } ;
clause     = value | "all" | "none" ;
value      = <alias or stock value name>

The operators are unusual:

  • AND combines the accumulated masks with a bitwise OR. It is not an intersection.

  • MINUS removes the next clause’s mask from the result.

  • NOT is an alias for MINUS.

  • A value name is looked up first in the aliases already defined for the same condition type, then in the stock value table.

  • all sets both 32-bit words to all ones.

  • none sets bit 0. It does not produce an empty mask. The parser applies none as soon as it reads the token, so a following AND reports BG_ParseConditionBits: unexpected 'AND'. Place none last when it shares an expression with other values.

  • A comma is not an operator inside a set expression. It ends the expression. The section parser then handles the next token normally: a set starts a new definition, and anything else is skipped.

Do not put two values next to each other without an operator. The parser joins the words into one alias name.

A bitmask condition matches when any bit in the script mask intersects the client’s condition bits. The alias is therefore a set of alternatives, not a requirement that every bit be present.

Each condition type accepts at most 16 aliases. The alias name is local to its condition type.

Condition types

The stock parser recognizes eleven condition types. Names are case-insensitive.

Type

Mode

Values

Meaning

WEAPONS

Bitmask

The dynamic weapon table. Stock files usually define aliases.

Weapon identity.

WEAPONCLASS

Bitmask

RIFLE, MG, SMG, LMG, PISTOL, GRENADE, ROCKETLAUNCHER, TURRET, SPOTTER, NON-PLAYER, FLAMETHROWER.

Weapon class.

MOUNTED

Equal

MG42, vehDriver, vehGunner, vehPassenger1 through vehPassenger4.

Mounted position.

VEHICLE

Equal

TANK, JEEP, FLAK88.

Vehicle type.

VEHICLE_MOTION

Equal

IDLE, FORWARD, REVERSING.

Vehicle motion.

MOVETYPE

Bitmask

The move-type table listed below.

Movement family.

UNDERHAND

Equal

No value table.

Bare keyword. The parser supplies value 1.

CROUCHING

Equal

No value table.

Bare keyword. The parser supplies value 1.

FIRING

Equal

No value table.

Bare keyword. The parser supplies value 1.

WEAPON_POSITION

Equal

HIP, ADS.

Weapon position.

STRAFING

Equal

NOT, LEFT, RIGHT.

Strafe state.

The three bare keywords take no value:

crouching
underhand
firing

Writing crouching 1 is wrong. The parser will read 1 as the next condition type and fail.

States and move types

ANIMATIONS and CANNED_ANIMATIONS use the same state and move-type tables. State names are case-insensitive.

State

Index

Meaning

RELAXED

0

No immediate threat.

QUERY

1

Suspicious or searching.

ALERT

2

Enemy known, not yet in combat.

COMBAT

3

Active combat.

Move types are the inner blocks under a state. The stock table is:

Index

Name

Index

Name

0

** UNUSED **

9

WALKPRONEBK

1

IDLE

10

RUN

2

IDLECR

11

RUNBK

3

IDLEPRONE

12

RUNCR

4

WALK

13

RUNCRBK

5

WALKBK

14

TURNRIGHT

6

WALKCR

15

TURNLEFT

7

WALKCRBK

16

CLIMBUP

8

WALKPRONE

17

CLIMBDOWN

The table has no straferight or strafeleft entry. Strafe state is a condition type, STRAFING, not a move type.

ANIMATIONS

ANIMATIONS is the main state table. Its grammar is:

STATE <state>
{
    <move-type>
    {
        <conditions>
        {
            <commands>
        }
    }
}

Empty state blocks are valid:

STATE RELAXED
{
}

Empty move-type blocks are also valid. A state can contain several move-type blocks, and a move type can contain several condition blocks.

Stock structure:

STATE RELAXED
{
}

STATE ALERT
{
}

STATE COMBAT
{
    idle
    {
        default
        {
            both stand_idle
        }
    }
}

The first condition block that matches wins. Put specific conditions before general ones, and end the list with default.

Conditions

When a condition block has conditions, it lists them all on one line. The condition parser stops at the end of the line, so a condition line ends before the opening brace. Conditions are ANDed: every condition must match for the block to be selected.

weaponclass lmg, movetype prone
{
    both c_mg_dp28_prone_fire
}

A bitmask condition value uses the same expression syntax as DEFINES. The expression ends at a comma or at the end of the line. Write the comma directly after the preceding value, as in weaponclass lmg, movetype prone. An equal-mode value has one trailing comma removed. A condition with no value table, such as crouching, underhand, or firing, reads no value token. A comma after one is read as the next condition type, which is not a condition name. Separate bare conditions with whitespace.

default means an unconditional block:

default
{
    both stand_idle
}

Use default alone. The parser returns immediately when it sees default, but it does not clear conditions that were already parsed earlier in the same list.

An empty condition list is an error:

BG_ParseConditions: no conditions found

An equal-mode condition with no value is an error:

BG_ParseConditions: expected condition value, found end of line

A script may have at most 11 conditions:

BG_ParseConditions: exceeded maximum conditions (11)

CANNED_ANIMATIONS

CANNED_ANIMATIONS uses the same grammar as ANIMATIONS and stores its tables separately in table->canned.

The stock multiplayer file contains no CANNED_ANIMATIONS section. The recovered source has the parser and the table layout, but no recovered stock call site reads the canned table. Treat the section as reserved parser support, not as a documented stock content path.

If you add one, keep its animation names in the multiplayer tree, as with the main table.

STATECHANGES

STATECHANGES describes animations for a transition between two states. The grammar is:

statechange <to-state> <from-state>
{
    <conditions>
    {
        <commands>
    }
}

The parser reads the destination state first and the source state second. It stores the entry at:

statechanges[toState][fromState]

The runtime call is:

BG_AnimScriptStateChange(player, fromState, toState);

That function reads the same [to][from] cell. Keep the declaration order and the runtime argument order distinct when reading or writing a STATECHANGES block.

The stock multiplayer file contains no STATECHANGES section. The parser and the lookup path exist, but no stock example establishes a content convention.

A state change with no matching entry returns -1. A dead player also returns -1.

EVENTS

EVENTS describes one-shot reactions. The grammar is:

<event>
{
    <conditions>
    {
        <commands>
    }
}

The stock event names are:

Index

Event

Index

Event

0

PAIN

8

CLIMBMOUNT

1

DEATH

9

CLIMBDISMOUNT

2

FIREWEAPON

10

RELOAD

3

JUMP

11

CROUCH_TO_PRONE

4

JUMPBK

12

PRONE_TO_CROUCH

5

LAND

13

MELEEATTACK

6

DROPWEAPON

14

LMG_DEPLOY

7

RAISEWEAPON

15

LMG_BREAKDOWN

Events select the first matching script, then randomly select one of that script’s commands. The random choice differs from state animation selection, which is deterministic per client.

DEATH is the only event that still plays when the player is dead. Every other event returns -1 once pmType is PM_TYPE_DEAD or later.

Commands

A condition block contains up to eight command lines. Each command line names up to two body-part slots:

<slot> <animation> [duration N] [turretanim] [blendtime N]
[<slot> <animation> ...]
[sound <alias>]

The slots are:

Slot

Meaning

legs

Lower-body animation.

torso

Upper-body animation.

both

One animation on both body parts. It ends the slot sequence.

A command line can carry at most two slots. The common stock form is one legs slot and one torso slot:

legs c_mp_jeep_passenger_idle torso pt_stand_ads_pistol

both is a single slot and terminates the sequence:

both c_mg_dp28_prone_fire

Slot parameters:

Parameter

Effect

duration N

Overrides the slot duration with N milliseconds. The runtime adds 50 ms to the value before starting the animation.

turretanim

Marks the animation as a turret animation. Valid only on both.

blendtime N

Sets the shared static animation entry’s blend time to N milliseconds. This is not a per-command value. It affects every use of that animation.

sound <alias>

Plays a sound alias with the command. A raw .wav path is rejected: wav files not supported, only sound scripts.

If duration is omitted, the slot uses the static animation entry’s duration.

The stock file has duration values from 5 to 550. A small value is intentional in some jump and takeoff entries; it is not a missing zero.

Examples from the stock multiplayer file:

torso pt_prone_shoot_auto duration 150

weaponclass lmg, movetype prone
{
    both c_mg_dp28_prone_fire
}

both pb_runjump_takeoff duration 5 blendtime 100

legs c_mp_jeep_passenger_idle torso pt_stand_ads_pistol

mounted vehDriver, vehicle flak88
{
    both standMG42_aim turretanim
}

Command selection and playback

State animation selection walks the state table from the requested state down to RELAXED. A non-empty list that yields no matching script does not stop the walk; the next lower state is tried. This gives COMBAT a fallback path through ALERT and QUERY to RELAXED.

Within a list, the first script whose conditions all match is selected. For state animations, the command is:

commands[clientNum % commandCount]

This is deterministic per client. For events and state changes, the command is selected randomly:

commands[randrange(0, commandCount)]

The random range includes zero and excludes commandCount.

The selected command then runs through BG_AnimScriptAnimation:

  1. Play slot 0, if present.

  2. Play slot 1, if present.

  3. Play the command’s sound alias, if present.

  4. Return the duration if a legs or both animation started, otherwise -1.

BG_ExecuteCommand converts that result to a boolean. It also refuses to play a state animation once the player is dead.

State animations run with setTimer false. Events and state changes run with setTimer true.

Recovered quirks

The recovered body contains three behaviors that content authors should know.

  • The second slot’s duration is computed from command.duration[0], not command.duration[1]. If a command gives the legs and torso slots different durations, the torso slot uses the legs value.

  • The second slot’s legs-started test checks command.bodyPart[0], not command.bodyPart[1]. The second playback result can therefore overwrite the first result in the return path.

  • The bitmask parser clears the low word of its accumulator per condition but does not clear the high word per condition. A later bitmask condition in the same list can inherit high bits from an earlier one.

These are stock behaviors, not recommendations. Avoid relying on them in new content.

Stock multiplayer examples

The stock file uses the sections in this order:

Section

Approximate stock line

DEFINES

18

ANIMATIONS

98

STATE RELAXED

100

STATE ALERT

105

STATE COMBAT

110

EVENTS

1019

STATE RELAXED and STATE ALERT are empty in the stock file. The state fallback path still reaches COMBAT content from those states.

A weapon-class and stance combination:

weaponclass pistol, movetype crouching, weapon_position ads
{
    torso pt_crouch_shoot_ads_pistol
}

A mounted vehicle pose:

mounted vehDriver, vehicle flak88
{
    both standMG42_aim turretanim
}

A jump command with both an explicit duration and a shared blend time:

both pb_runjump_takeoff duration 5 blendtime 100

Stale comment block

A large comment block above ANIMATIONS describes an older condition and command vocabulary. The recovered parser does not implement that vocabulary. Do not copy the comment block as authority.

Comment block says

Actual stock parser

Condition position

There is no position condition. Use WEAPON_POSITION with HIP or ADS.

Condition enemy_weapon

No such condition type. The recovered table has WEAPONS and WEAPONCLASS.

Condition underwater

No such condition type.

Condition leaning

No such condition type.

Body part turret

The body-part table has only LEGS, TORSO, and BOTH. turretanim is a command parameter on both.

Move types straferight and strafeleft

The move-type table has no such entries. Use the STRAFING condition with LEFT or RIGHT.

sound <filename>

The parser requires a sound alias and rejects a .wav path.

The comment block is useful as a historical description of the file’s intent. It is not a grammar.

Limits and diagnostics

Limit

Value

File size

At most 99,998 bytes. A file of 99,999 bytes or more is rejected.

Scripts per list

Global scripts

2,048.

Conditions per script

Commands per condition block

8. A ninth command reports BG_ParseCommands: exceeded maximum number of animations (8).

Aliases per condition type

Runtime animation names

512. A longer registration reports BG_AnimationIndexForString: exceeded maximum runtime animations (512).

Animation name length

Less than 64 bytes.

Important diagnostics:

Message

Cause

Couldn't load player animation script %s

The file is missing, empty, or at least 99,999 bytes.

BG_AnimParseAnimScript: unexpected '%s'

A section keyword appears inside a block, or a block is malformed.

BG_AnimParseAnimScript: expected condition type string

A set line has no condition type.

BG_AnimParseAnimScript: can not make a define of type '%s'

A set uses an equal-mode condition.

BG_AnimParseAnimScript: expected '=', found '%s'

A set line is missing =.

BG_AnimParseAnimScript: expected state type

STATE has no state name.

BG_ParseConditions: no conditions found

A condition block is empty.

BG_ParseConditions: expected condition value, found end of line

An equal-mode condition has no value.

BG_ParseCommands: expected animation

A body-part slot has no animation name.

BG_ParseCommands: unknown parameter '%s'

A command parameter is not recognized.

BG_ParseCommands: Turret animations can only be played on the 'both' body part

turretanim is on legs or torso.

wav files not supported, only sound scripts

sound names a raw wave file.

Could not find animation tree '%s'

The multiplayer tree is missing when the player table is built.

Authoring checklist

  1. Add every animation name to animtrees/multiplayer.atr before using it here.

  2. Put specific condition blocks before general ones.

  3. End a condition list with a single default block.

  4. Write a comma directly after a bitmask value. Separate other conditions with whitespace.

  5. Use legs and torso for split-body poses, and both for a single full-body animation.

  6. Use duration only when the asset duration is wrong for the state.

  7. Use blendtime deliberately. It changes the shared animation entry, not one command.

  8. Use a sound alias, not a .wav path.

  9. Check the STATE and event names against the stock tables above.

  10. Rebuild the multiplayer tree after adding a node, then reload the map.

Implementation basis

Source

Role

src/bg/bg_animation_script_parser.c

File load, section dispatch, DEFINES and ANIMATIONS structure.

src/bg/bg_parse_conditions.c

Condition list parsing and condition-count limit.

src/bg/bg_parse_condition_bits.c

Bitmask clause parsing, aliases, AND, MINUS, NOT, all, none.

src/bg/bg_parse_commands.c

Body-part slots, duration, turretanim, blendtime, sound.

src/bg/bg_animation_script_data.c

Stock condition, state, move-type, event, and body-part tables.

src/bg/bg_animation_conditions.c

Condition evaluation and first-match selection.

src/bg/bg_animation_execution.c

State fallback, command selection, playback, events, and state changes.

src/bg/bg_animation_lookup.c

Runtime animation-name registration and limits.

src/bg/bg_animation_finalize.c

Static player table rebuild from the multiplayer tree.

src/qcommon/bg_animation_types.h

Script, command, list, and table layout.

mp/playeranim.script

Stock examples, section order, and the stale comment block.