AI animation scripts (animscripts/*.gsc)¶
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 |
|---|---|---|
|
41 |
Base behaviors and shared helpers: |
|
17 |
Ledge, fence, and platform traversal behaviors. |
|
3 |
Scripted sequence playback, including vehicle and truck-ride cases. |
|
3 |
MG42 turret behavior. |
|
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_animtreeselects the tree for every%reference that follows it in the file.self endon("killanimscript")lets the AI cancel the behavior.trackScriptStatetags the current script state for the AI debug view.utility::initializeclears 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 |
|---|---|---|
|
|
Current body pose. |
|
|
Current movement family. |
|
|
Selected idle set. |
|
|
Special animation mode. |
|
|
Which hand holds the weapon model. |
|
Current behavior name |
Set by |
|
|
Alertness pose family. |
|
|
Melee state. |
|
|
Weapon rechamber flag. |
|
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, andanim.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.
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 |
|---|---|
|
Set one animation knob with a blend. |
|
Set a knob over a node and its descendants. |
|
Set a knob and restart the animation. Observed with one argument and with four. Turret calls use both forms; AI body calls use four. |
|
Set a whole-subtree knob and restart. |
|
Set a knob and associate a completion notification. |
|
Whole-subtree flagged knob. |
|
Flagged knob with restart. |
|
Flagged whole-subtree knob with restart. |
|
General animation set. Observed with two, three, and four arguments. |
|
Flagged animation with restart. Appears only in a commented-out call in the supplied corpus. |
|
Clear a node. |
|
Read animation progress. Scripts compare it with values such as
|
|
Switch an entity to a compiled tree index. |
|
Release the switch. |
|
Set the AI actor’s turret-body animation. |
|
Start a scripted animation sequence. |
|
Aim the look controller at a position. |
|
Set the look animation nodes. |
|
Set yaw limits for the look controller. |
|
Stop the look controller. |
|
Set the prone ground-orientation animations. |
|
Enter prone orientation blending. |
|
Leave prone orientation blending. |
|
Blend the prone orientation pair. |
|
Record the current behavior state for debugging. |
|
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 |
|---|---|
|
Return from |
|
Update |
|
Update |
|
Set alertness to |
|
Set alertness directly. |
|
Move the weapon model. |
|
Call |
|
Play a ground-type bodyfall sound. |
|
Placeholders in the stock script. They have empty bodies. |
|
Clear the rechamber flag and, for |
|
Update |
|
Play a ground-type step or scrape sound. |
|
Play a melee swing sound. The stock mapping is inverted: |
|
Drop the AI weapon. |
|
Detach the helmet and attach it to the left weapon tag. |
Anything else |
Call the optional |
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:
initdefaultsanimModeto"normal"androotto%bodywhen the caller omits them.mainoverwrites the stored root with%bodybefore 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 ininit.gscnotes that the stock scripts have no stack traces, so the message must identify the call site.trackScriptStatelabels the current behavior state.UpdateDebugInfomaintains a debug display.animscriptentselects a debug entity.anim.doSpamenables 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.
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 |
Notetrack vocabulary |
The notes handled by |
Turret tree contents |
|
Map-specific scripts |
The attachment includes scripts that are not in |
Implementation basis¶
Source |
Role |
|---|---|
|
Global and per-actor animation state initialization. |
|
Shared level-facing helpers and the notetrack bridge. |
|
|
|
Pose and movement function table and transition helpers. |
|
|
|
Look controller, look animation nodes, |
|
Example combat entry point and behavior loop. |
|
Example movement entry point and pose selection. |
|
Example cover behavior using |
|
Level |
|
Shared turret fire and aim loop and callback dispatch. |
|
MG42 prone body calls, mid-file |
|
MG42 standing body calls, mid-file |
|
Shared 30cal turret fire and aim loop and callback dispatch. |
|
30cal prone body calls, mid-file |
|
30cal standing body calls, mid-file |
|
Base |
|
Source of |
|
Campaign level calls to shared helpers and |
|
Compiler-side |