AI animation scripts (animscripts/*.gsc)

Status and authority

This page documents the animscripts layer as it appears in the stock Call of Duty: United Offensive script corpus. It is not a recovered engine API reference.

The corpus establishes:

  • the file layout and the behavior entry points;

  • the animation state variables the scripts maintain;

  • the animation method names and call forms they use;

  • the notetrack bridge in shared.gsc;

  • the pose and movement helper layer;

  • the turret tree-switching pattern.

The recovered coduomp source does not contain implementations of the AI animation methods. A search for the method names in that tree returns no implementation:

grep -ril "setflaggedanimknob\|setanimknob\|useanimtree\|clearanim" src

The stock codbook API page likewise lists only getanimlength and animhasnotetrack among the animation builtins. This page therefore records observed call forms and names their argument positions as they appear at the call sites. It does not invent parameter contracts, return values, or error behavior for methods the recovered source does not implement.

The corpus is a superset of the scripts shipped in the stock UO packages. Across those packages, only pakuo00.pk3 lists animscripts/ entries. It carries 21 scripts, all present in the supplied attachment. The attachment adds behavior scripts and map-specific variants, for 67 files in total.

What the AI animation layer is

These files are ordinary GSC scripts. They are not a separate language, and they are not the engine mp/playeranim.script table described in Player animation script (mp/playeranim.script).

Behavior scripts are started through main(). 50 of the 67 files define one; the rest are helper or callback files. A behavior script selects an animation tree with #using_animtree and drives an AI actor with animation method calls. The scripts also keep a small animation state model in GSC variables. The engine does not derive that model from the tree or the player animation table.

The recovered engine source does not contain the AI behavior selector that starts these scripts. The scripts are started by the stock AI or by level scripts, not by the player animation table.

Corpus layout

The supplied corpus contains 67 .gsc files. Directory distribution:

Directory

Files

Contents

animscripts/

41

Base behaviors and shared helpers: init, shared, utility, combat, cover_*, walk, run, stop, pain, death, look, aim, prone, wounded, and others.

animscripts/traverse/

17

Ledge, fence, and platform traversal behaviors.

animscripts/scripted/

3

Scripted sequence playback, including vehicle and truck-ride cases.

animscripts/mg42/

3

MG42 turret behavior.

animscripts/b30cal/

3

M1919 Browning turret behavior.

50 files define a main function. The rest are helper or callback files. 63 files contain an active #using_animtree directive. Every file’s first active directive selects generic_human. Five files contain a second active directive: scripted.gsc repeats generic_human before init, and the four turret files switch to mg42 or 30cal.

In-game paths use backslashes in GSC calls:

animscripts\utility::initialize("combat");
animscripts\shared::DoNoteTracks("animdone");

Script names and function names are canonicalized like other GSC names, so a case mismatch between a call and a filename does not necessarily fail. The supplied corpus carries both weaponList.gsc and weaponlist.gsc, and init.gsc calls animscripts\WeaponList::initWeaponList().

Entry point pattern

A behavior main() follows a consistent shape:

#using_animtree ("generic_human");

main()
{
    self trackScriptState( "Walk Main", "code" );
    self endon("killanimscript");

    animscripts\utility::initialize("walk");

    for (;;)
    {
        // choose pose, movement, cover, enemy response, and so on
    }
}

The important parts are:

  • #using_animtree selects the tree for every % reference that follows it in the file.

  • self endon("killanimscript") lets the AI cancel the behavior.

  • trackScriptState tags the current script state for the AI debug view.

  • utility::initialize clears the body pose and applies the common setup.

  • The loop selects a behavior and waits on notifications or timers.

Some scripts use trackScriptState with a reason string rather than "code", for example when a sub-state returns. The method is observed in the corpus and is not implemented in the recovered source.

Animation state variables

init.gsc documents the model at the top of the file. The variables are conventional GSC fields on the AI actor.

Variable

Values

Meaning

self.anim_pose

"stand", "crouch", "prone", "back", "wounded"

Current body pose. "back" is a pain or death pose. "wounded" is handled by the wounded behavior, not by the general pose table.

self.anim_movement

"stop", "walk", "run"

Current movement family.

self.anim_idleSet

"none" and behavior-specific sets

Selected idle set.

self.anim_special

"none", "mg42", and behavior-specific values

Special animation mode.

self.anim_gunHand

"left", "right", "none"

Which hand holds the weapon model.

self.anim_script

Current behavior name

Set by utility::initialize and by behavior transitions.

self.anim_alertness

"casual", "alert", "aiming"

Alertness pose family.

self.anim_meleeState

"aim", "left", "right"

Melee state.

self.anim_needsToRechamber

0 or 1

Weapon rechamber flag.

self.anim_combatEndTime

Millisecond timestamp

Combat memory expiry.

These are script conventions. They are not fields in the player animation table, and the engine does not enforce their values.

Initialization

init.gsc runs once for the level and once per AI actor.

The level pass is guarded by anim.NotFirstTime. It sets:

  • the debug entity cvar animscriptent;

  • the global function pointers anim.println, anim.locSpam, anim.assert, and anim.PutGunInHand;

  • combat range and accuracy constants;

  • the pose and movement lookup table through SetPoseMovement::InitPoseMovementFunctions;

  • the weapon list and level face tables.

The per-actor pass sets these animation fields, among other per-actor bookkeeping:

self.anim_pose = "stand";
self.anim_movement = "stop";
self.anim_idleSet = "none";
self.anim_special = "none";
self.anim_gunHand = "none";
self.anim_PrevPutGunInHandTime = -1;
self.anim_needsToRechamber = 0;
self.anim_combatEndTime = gettime();
self.anim_script = "init";
self.anim_alertness = "casual";
self.anim_woundedBias = 0;
self.anim_woundedAmount = 0;
self.anim_woundedTime = GetTime();
self.anim_lastWound = "upper";
self.anim_lastDebugPrint = "";
self.anim_StopCowering = ::DoNothing;

It also calls shared::PutGunInHand("right"), initializes unique animations, starts the look thread, starts the debug info thread, and refills the weapon clip.

anim.PutGunInHand and the other anim entries are GSC function references, not engine natives. init.gsc assigns them so later code can call a function through an array or a function-pointer slot.

Shared helpers

shared.gsc contains functions shared by animation scripts and level scripts. Its header states that it cannot rely on init having run and cannot call functions that are not allowed in level scripts. That constraint explains why it reimplements or delegates low-level work instead of assuming per-actor initialization.

Observed shared helpers include:

  • PutGunInHand(hand): attaches or detaches the weapon model on tag_weapon_left and tag_weapon_right. hand is "left", "right", or "none".

  • DropAIWeapon().

  • LookAtEntity, LookAtPosition, LookAtStop, LookAtAnimations.

  • SetInCombat.

  • UpdateWounds.

  • DoNoteTracks and DoNoteTracksForever.

Level scripts call these directly. The recovered campaign script bastogne1.gsc calls animscripts\shared::putGunInHand and animscripts\shared::lookatentity, which confirms that the shared file is part of the level-facing surface, not only the AI-internal surface.

Animation tree selection

AI files select a tree at compile time:

#using_animtree ("generic_human");

Every %name after that directive is resolved against generic_human until another directive changes the selection. A name missing from the selected tree is a compile error, not a runtime fallback.

Turret scripts use a second tree for the turret entity. In mg42/prone.gsc and mg42/stand.gsc the file starts with generic_human for the AI body functions, then repeats a directive before the turret section:

#using_animtree("mg42");

init(owner)
{
    self UseAnimTree(#animtree);
    // ...
}

The init(owner) function runs on the turret entity because the body script starts it with turret thread init(self). The body functions before the switch play generic_human references such as %proneMG42gunner_fire. The functions after it play mg42 references such as %proneMG42gun_fire_forward_medium.

mg42/common.gsc contains no tree directive and no % reference. It is the shared fire and aim loop that calls the body script’s callbacks.

The turret init functions keep a reference count:

init(owner)
{
    self UseAnimTree(#animtree);

    if (isdefined(self.animTreeUsage))
        self.animTreeUsage++;
    else
        self.animTreeUsage = 0;

    owner waittill("killanimscript");

    if (self.animTreeUsage)
    {
        self.animTreeUsage--;
        return;
    }

    self.animTreeUsage = undefined;
    self stopuseanimtree();
}

The count prevents one user from stopping the tree while another user still holds it. b30cal/prone.gsc and b30cal/stand.gsc use the same pattern with #using_animtree("30cal"). Their b30cal/common.gsc also has no tree directive.

UseAnimTree and stopuseanimtree are observed methods. The recovered source does not define them. #animtree is the compiler-side registry index described in Animation trees (.atr).

Observed animation methods

The following names and call forms appear in the supplied corpus. Argument names describe the observed positions, not a recovered signature.

Observed call form

Purpose in the scripts

setAnimKnob(anim, weight, blendTime[, rate])

Set one animation knob with a blend.

setAnimKnobAll(anim, rootNode, weight, blendTime[, rate])

Set a knob over a node and its descendants.

setAnimKnobRestart(anim[, weight, blendTime, rate])

Set a knob and restart the animation. Observed with one argument and with four. Turret calls use both forms; AI body calls use four.

setAnimKnobAllRestart(anim, rootNode, weight, blendTime, rate)

Set a whole-subtree knob and restart.

setFlaggedAnimKnob(notifyName, anim, weight, blendTime, rate)

Set a knob and associate a completion notification.

setFlaggedAnimKnobAll(notifyName, anim, rootNode, weight, blendTime, rate)

Whole-subtree flagged knob.

setFlaggedAnimKnobRestart(notifyName, anim, weight, blendTime, rate)

Flagged knob with restart.

setFlaggedAnimKnobAllRestart(notifyName, anim, rootNode, weight, blendTime, rate)

Flagged whole-subtree knob with restart.

setAnim(anim[, weight, blendTime[, rate]])

General animation set. Observed with two, three, and four arguments.

setFlaggedAnimRestart(notifyName, anim, weight, blendTime, rate)

Flagged animation with restart. Appears only in a commented-out call in the supplied corpus.

clearanim(node, blendTime)

Clear a node.

getAnimTime(node)

Read animation progress. Scripts compare it with values such as 0.5.

UseAnimTree(#animtree)

Switch an entity to a compiled tree index.

stopuseanimtree()

Release the switch.

setTurretAnim(anim)

Set the AI actor’s turret-body animation.

startscriptedanim(notifyName, origin, angles, anim, animMode, root)

Start a scripted animation sequence.

setLookAt(position[, speed])

Aim the look controller at a position.

setLookAtAnimNodes(straight, left, right)

Set the look animation nodes.

setLookAtYawLimits(max, limit, blendTime)

Set yaw limits for the look controller.

stopLookAt(speed)

Stop the look controller.

SetProneAnimNodes(minAngle, maxAngle, downAnim, straightAnim, upAnim)

Set the prone ground-orientation animations.

EnterProne(blendTime)

Enter prone orientation blending.

ExitProne(blendTime)

Leave prone orientation blending.

UpdateProne(anim1, anim2, weight, blendTime, rate)

Blend the prone orientation pair.

trackScriptState(label, reason)

Record the current behavior state for debugging.

animMode(mode), traverseMode(mode), OrientMode(mode[, value])

Behavior mode changes used throughout the corpus.

setAnim needs a warning. The two-argument call self setanim(%body, 0.2) and the four-argument call self setanim(%shoot,0.0,0.2,1) do not share a documented positional meaning in the recovered source. Read the call site and the animation comment before reusing either form.

Set3FlaggedAnimKnobs is not an engine method. It is a wrapper in corner.gsc:

Set3FlaggedAnimKnobs(animFlag, animArray, weight, blendTime, rate)
{
    self SetAnimKnob(animArray["left"], weight, blendTime, rate);
    self SetFlaggedAnimKnob(animFlag, animArray["middle"],
                            weight, blendTime, rate);
    self SetAnimKnob(animArray["right"], weight, blendTime, rate);
}

finishLookAt is also a GSC helper, not an engine method. It resets the eye animations and calls stopLookAt. It lives in look.gsc.

Pose and movement helpers

SetPoseMovement.gsc builds a two-dimensional function table:

anim.SetPoseMovementFnArray["stand"]["stop"]  = StandStop;
anim.SetPoseMovementFnArray["stand"]["walk"]  = StandWalk;
anim.SetPoseMovementFnArray["stand"]["run"]   = StandRun;
anim.SetPoseMovementFnArray["crouch"]["stop"] = CrouchStop;
// ... crouch walk, crouch run, prone stop, prone walk, prone run

The public function is:

SetPoseMovement(desiredPose, desiredMovement)
{
    // fill empty arguments from the current state
    [[anim.SetPoseMovementFnArray[desiredPose][desiredMovement]]]();
}

It accepts empty strings for “leave this part of the state alone”. A move from prone to walk or run with an empty desired pose becomes crouch first. A current pose of "wounded" is an error for this helper, because the wounded behavior owns that state.

utility.gsc supplies the common start-of-behavior setup:

initialize(animscript)
{
    initAnimTree(animscript);
    UpdateAnimPose();
}

initAnimTree puts the gun in the right hand, clears %body, sets %body and %shoot, updates the combat memory, and records the behavior name in self.anim_script.

UpdateAnimPose handles prone setup and teardown:

if (self.desired_anim_pose == "prone")
{
    self SetProneAnimNodes(-45, 45, %prone_legsdown,
                           %prone_legsstraight, %prone_legsup);
    self EnterProne(0.5);
}
else
{
    self ExitProne(0.5);
}

The helper also reconciles self.anim_pose and self.desired_anim_pose. A script can set self.desired_anim_pose before calling UpdateAnimPose to request a pose change.

Notetrack bridge

Animation notetracks are the bridge from an xanim asset back into GSC. The shared handler is:

self waittill(animName, note);

animName is the animation or flag name the script is waiting on. note is the notetrack string. DoNoteTracks(animName, customFunction, debugIdentifier) loops until it receives "end", "finish", or "undefined".

The handler recognizes these note families:

Note family

Effect

end, finish, undefined

Return from DoNoteTracks.

anim_pose = "stand", "crouch", "prone", "back"

Update self.anim_pose. Prone transitions also update orientation and prone nodes.

anim_movement = "stop", "walk", "run"

Update self.anim_movement.

anim_aiming = 1, anim_aiming = 0

Set alertness to "aiming" or "alert".

anim_alertness = casual, alert, aiming

Set alertness directly.

anim_gunhand = "left", "right", "none"

Move the weapon model.

gravity on, gravity off

Call animMode("gravity") or animMode("nogravity").

bodyfall large, bodyfall small

Play a ground-type bodyfall sound.

attach clip right, detach clip right

Placeholders in the stock script. They have empty bodies.

rechamber, reload done

Clear the rechamber flag and, for reload done, refill the clip.

anim_melee = "left", "right"

Update self.anim_meleeState.

footstep, step, footscrape

Play a ground-type step or scrape sound.

swish small, swish large

Play a melee swing sound. The stock mapping is inverted: swish small plays melee_swing_large and swish large plays melee_swing_small.

dropgun

Drop the AI weapon.

swap taghelmet to tagleft

Detach the helmet and attach it to the left weapon tag.

Anything else

Call the optional customFunction(note).

DoNoteTracksForever repeats the handler and guards against a note that returns too quickly. It is used with a kill string and also ends on "killanimscript".

The notetrack names themselves live in the xanim assets. The recovered source does not contain those assets, so this page can document the handler, not the complete note vocabulary of every animation.

Scripted animations

scripted.gsc is the adapter between the level command animscripted and the engine call startscriptedanim.

init(notifyName, origin, angles, theAnim, animMode, root)
{
    self.codeScripted["notifyName"] = notifyName;
    self.codeScripted["origin"] = origin;
    self.codeScripted["angles"] = angles;
    self.codeScripted["anim"] = theAnim;
    self.codeScripted["animMode"] = animMode;
    self.codeScripted["root"] = root;
}

main()
{
    self endon("end_sequence");
    self startscriptedanim(self.codeScripted["notifyName"],
                           self.codeScripted["origin"],
                           self.codeScripted["angles"],
                           self.codeScripted["anim"],
                           self.codeScripted["animMode"],
                           self.codeScripted["root"]);
}

The header states that the stock level command calls this script only for AI. For a script model, the level script calls startscriptedanim directly.

Two stock details matter:

  • init defaults animMode to "normal" and root to %body when the caller omits them.

  • main overwrites the stored root with %body before the call, marked // TEMP! in the source. A caller-supplied root is therefore not honored by this stock adapter.

The recovered campaign script bastogne1_anim.gsc uses animscripted extensively, which confirms the level-script path.

Turret scripts

The turret scripts are split into two layers.

The AI body layer selects generic_human:

#using_animtree("generic_human");

main()
{
    self desired_anim_pose = "crouch";
    animscripts\utility::UpdateAnimPose();

    self desired_anim_pose = "prone";
    self.anim_movement = "stop";

    turret = self GetTurret();
    turret thread init(self);
    thread animscripts\mg42\common::main(
        ::DoShoot, ::DoRecover, ::DoAim, turret);
}

The body layer calls setTurretAnim with generic_human references:

self setTurretAnim(%proneMG42gunner_fire);
self setAnimKnobRestart(%proneMG42gunner_fire, 1, 0.2, 1);

The turret layer selects mg42 or 30cal, switches the turret entity with UseAnimTree(#animtree), and plays the turret’s own references:

turret setAnimKnobRestart(%proneMG42gun_fire_forward_medium);

The two trees contain different names by design. generic_human contains the gunner body poses, and mg42 contains the turret gun poses. Do not move a name from one tree to the other without checking both script layers.

Debugging

The corpus uses several debug-only facilities:

  • /# ... #/ blocks are developer-only. The compiler strips them outside developer mode.

  • [[anim.println]] prints a message.

  • [[anim.locSpam]] prints a location or state marker.

  • [[anim.assert]] stops the server when its condition is false. The comment in init.gsc notes that the stock scripts have no stack traces, so the message must identify the call site.

  • trackScriptState labels the current behavior state.

  • UpdateDebugInfo maintains a debug display.

  • animscriptent selects a debug entity.

  • anim.doSpam enables location spam.

anim.assert and anim.locSpam are GSC function references, not engine natives. [[...]] is the GSC function-pointer call syntax used by the corpus.

Authoring guidance

  1. Start the file with #using_animtree for the tree that contains every animation you plan to use.

  2. Add the animation nodes to that tree before loading the script. A missing node is a compile error.

  3. Use the stock main() shape: trackScriptState, endon, utility::initialize, then a behavior loop.

  4. Use thread for non-blocking sub-behaviors and waittill for their completion.

  5. Use endon("killanimscript") so a behavior can be cancelled.

  6. Use utility::SetPoseMovement or the pose table for pose changes, rather than setting self.anim_pose alone. The helper plays the transition.

  7. Use DoNoteTracks for animation notes. Return from it on "end" or "finish".

  8. For a turret, select the narrow tree in the turret’s init and keep the AI body layer on generic_human.

  9. Balance UseAnimTree with stopuseanimtree through the animTreeUsage reference count.

  10. Do not assume an animation method exists because it appears in the corpus. The recovered source does not define the AI method set.

Known gaps

Gap

Consequence

AI animation method implementations

Argument meaning, defaults, return values, and error behavior are not established by the recovered source. The call forms above come from stock call sites.

AI behavior selector

The recovered source does not show which script starts combat, walk, cover_*, or the other behaviors.

Notetrack vocabulary

The notes handled by shared.gsc are known. The complete set of notes inside every xanim asset is not.

Turret tree contents

mg42.atr and 30cal.atr are in the corpus, but the engine-side turret animation selection is outside the recovered AI method set.

Map-specific scripts

The attachment includes scripts that are not in pakuo00.pk3. A stock installation may not contain every behavior named here.

Implementation basis

Source

Role

animscripts/init.gsc

Global and per-actor animation state initialization.

animscripts/shared.gsc

Shared level-facing helpers and the notetrack bridge.

animscripts/utility.gsc

initialize, initAnimTree, UpdateAnimPose, pose selection.

animscripts/SetPoseMovement.gsc

Pose and movement function table and transition helpers.

animscripts/corner.gsc

Set3FlaggedAnimKnobs wrapper and cover-corner behavior.

animscripts/look.gsc

Look controller, look animation nodes, finishLookAt.

animscripts/combat.gsc

Example combat entry point and behavior loop.

animscripts/walk.gsc

Example movement entry point and pose selection.

animscripts/cover_stand.gsc

Example cover behavior using SetPoseMovement.

animscripts/scripted.gsc

Level animscripted adapter and startscriptedanim call.

animscripts/mg42/common.gsc

Shared turret fire and aim loop and callback dispatch.

animscripts/mg42/prone.gsc

MG42 prone body calls, mid-file mg42 tree switch, and UseAnimTree reference count.

animscripts/mg42/stand.gsc

MG42 standing body calls, mid-file mg42 tree switch, and UseAnimTree reference count.

animscripts/b30cal/common.gsc

Shared 30cal turret fire and aim loop and callback dispatch.

animscripts/b30cal/prone.gsc

30cal prone body calls, mid-file 30cal tree switch, and UseAnimTree reference count.

animscripts/b30cal/stand.gsc

30cal standing body calls, mid-file 30cal tree switch, and UseAnimTree reference count.

pakuo00.pk3

Base animscripts/ package contents.

pakuo01.pk3

Source of mp/playeranim.script; no animscripts entries.

bastogne1.gsc and bastogne1_anim.gsc

Campaign level calls to shared helpers and animscripted.

src/scripting/script_anim_runtime.c

Compiler-side #using_animtree, %, and #animtree behavior.