Guides/08

Weapon animations

Give a Fallout 4 weapon new animations and conditional variants with a .bhv file.

esx behavior builds Fallout 4 behavior graphs natively. It does not use the Creation Kit, the Havok SDK or a compatibility runtime. You write one .bhv file that describes the behavior of one weapon. esx finds the graphs, folders, events and records in the game data, and makes everything else. You can replace each of its choices in the same file.

A .bhv file controls animation only: clips, playback speed and event-driven clip selection. The weapon record and its data (damage, capacity, ammo, parts, recipe) are in your plugin. Make them with the record, batch or rhai commands.

What you need

  • Finished animation clips (.hkx) for the Fallout 4 skeleton. esx does not convert or export animations.
  • A seed plugin that contains your weapon record. The editor ID of the record is the weapon name in the .bhv file.
  • An installed Fallout 4. esx reads the vanilla graphs from Fallout4 - Animations.ba2 and the records from the load order of the seed plugin.

Folder layout

MyGun/
  mygun.bhv
  mygun.lock                  # written by the first build
  animations/
    first_person/
      WPNReload.hkx
      WPNReload1.hkx
    third_person/
      WPNReload.hkx
      WPNReload1.hkx

esx looks for animations/ next to the .bhv file, not in the current directory.

A mod source

mod MyGun {
  plugin "MyGun.esp"
  prefix MyGun                       # starts the generated graph, variable and folder names
}

weapon MyGunWeapon like Anims44 {
  races HumanRace, PowerArmorRace    # the default
  reload {
    speed reloadSpeedMult * 1.5      # or: speed 1.5
    when ammo == 1 play WPNReload1   # the first matching line plays
    when empty play WPNReloadEmpty
  }
  equip { when IsMoving == 1 play WPNEquipWalk }
}

like Anims44 names the vanilla animation keyword to copy. The weapon gets the subgraph rules, graphs and folders of that keyword. Without other lines, the weapon moves like the vanilla .44 revolver.

Each block in the weapon block changes one action:

  • speed binds the playback speed of the action’s clips to an expression.
  • when CONDITION play CLIP adds a variant. The game plays CLIP instead of the vanilla clip when the condition is true.

Build

esx behavior explain reload --like Anims44
esx behavior clips mygun.bhv --plugin MyGun.esp
esx behavior build mygun.bhv --plugin MyGun.esp -o build/main

explain shows the graphs, states and clips that a vanilla action uses, and the free variant events. clips shows, for each view, the folder that supplies each animation name, and the names that no folder has. build writes a new directory with a copy of the plugin and a Meshes tree. esx writes the directory only when the full build succeeds. -o is necessary, unless you give --dry-run.

To write into a directory that exists, add --overwrite:

esx behavior build mygun.bhv --plugin MyGun.esp -o build/main --overwrite

esx then adds the new files and keeps each file that is identical. esx does not replace a file. If a file would change, the build stops, writes nothing, and lists the paths. Delete those files, or use a new directory.

Pack the Meshes files into an archive:

(cd build/main && find Meshes -type f) > build/files.txt
esx archive pack build/main --file-list build/files.txt -o "build/MyGun - Main.ba2"

Install MyGun.esp and MyGun - Main.ba2 in Data.

build also writes behavior.lock.json into the output directory. This file lists each file of the build with its SHA-256. After you pack, verify reads each file from the archives and from the Data folder that you give, and compares the hashes:

esx behavior verify build/main/behavior.lock.json --archive "build/MyGun - Main.ba2" --data build/release --fail-on-missing

The report has the counts ok, missing and mismatched, and one item for each file. With --fail-on-missing, a file that is missing or that has other bytes gives exit code 7. Without --archive and --data, verify reads the folder of the lock file. This checks the build directory.

The first build writes mygun.lock with the hash of each vanilla graph. A later build stops when the game files change. Check the source, then build with --update-lock. The lock file is next to the source. To keep it in a different location, give --lock PATH to check, plan, expand, clips and build:

esx behavior build build/source/mygun.bhv --lock mygun.lock --plugin MyGun.esp -o build/main

esx behavior expand mygun.bhv --plugin MyGun.esp prints the graph-level source that esx makes. Read it to see each choice.

Variants

A when … play line copies each state that the action enters, puts the new clip in the copy, and adds an IDLE record that selects the copy. esx tests the lines in order, and the first matching line plays.

Condition Meaning
ammo == 5 GetLoadedAmmoCount == 5. Also !=, <, <=, >, >=.
empty ammo == 0
partial ammo > 0
IsSneaking == 1 Any condition function.
HasPerk(Gunslinger01) == 1 Parameters: a number, a value name, or the editor ID of a record.
Target.GetIsID(Player) == 0 Run on Subject (the default), Target, CombatTarget or LinkedReference.

Join conditions with and and or. As in the Creation Kit, or joins a condition to the next one before and applies: a or b and c means (a or b) and c.

GetLoadedAmmoCount counts the round that the weapon fires. The first shot from a full six-round cylinder has ammo == 6. For a reload, the count is the rounds before the reload.

A variant needs an event that no other part of the game uses. esx takes a free event for you. To name the event, add send EVENT to the line. Each variant needs its own event: two lines that send one event stop the build (EVENT_REUSED).

Carrier events

The IDLE record of a variant sends an event, and the event starts the copied state. The game gives an event to a weapon graph only when the root graph of the view declares the name of the event. A new name does not get to the weapon graph. Thus esx uses names that the vanilla root graphs declare and that no other part of the game uses: no vanilla weapon graph, IDLE record, clip annotation, Papyrus script or Fallout4.exe text. These names are the carrier events.

The first-person names are testCam, testBigBoy, BodyCameraEnte, PCapEnter, PCapExit, to_PoseE and nine more. They are remains of test content in the game. A carrier name has no meaning for your weapon. For example, testBigBoy does not start a test. It only carries the selection of one variant.

In the build report, each event of lowering.variants has a carrier value:

Value Meaning
"carrier": true esx chose the event from the free vanilla events.
"carrier": false You named the event with send.

A lowering.notes line lists the carrier events of the build. esx behavior explain reload --like Anims44 lists the free events of each perspective before you build. Name an event with send when a weapon has more variants than free events, or when you want a specified name. The root graph of each view that uses the variant must declare the name (EVENT_NOT_IN_ROOT), and the weapon graph must not use it (EVENT_DECLARED).

Actions

These actions are built in:

Action Record Event Clips that a variant replaces
reload ActionReload reloadStart WPNReload
bolt ActionBoltCharge boltChargeStart WPNBoltCharge, WPNBoltChargeSighted
fire ActionFireSingle attackStart WPNFireSingleReady (and A, B), WPNFireSingleSighted
melee ActionMelee meleeAttackGun WPNMelee
equip ActionDraw weapEquip WPNEquip
unequip ActionSheath unEquip WPNUnEquip
throw ActionThrow grenadeThrowStart WPNGrenadeThrow

A variant plays one clip for all the action’s clips, or one clip for each, in order:

bolt { when empty play WPNBoltLast, WPNBoltLastSighted }

Declare other actions with an action block. Run explain with the same values first:

action fireauto {
  record ActionFireAuto            # the action record whose IDLE tree selects it
  event attackStartAuto            # the event that its default IDLE sends
  clip WPNFireAutoReady            # the clips that a variant replaces
}
esx behavior explain fireauto --like AnimsSubmachineGun --event attackStartAuto --clip WPNFireAutoReady

A block with the name of a built-in action changes only the fields that it gives:

Field Effect
state MACHINE STATE [in GRAPH] Copy only this state. Use it when esx does not find the state that you want.
speed VARIABLE The vanilla variable that binds the clips’ playback speed. A speed line in the weapon block needs it.
alias off Do not copy the other routes of the action’s event (a restart, a nested route).
idles root The first-person IDLE tree of the action is in the root graph (as for throw).

Third person

Put third-person clips with the same names in animations/third_person/. esx then changes the third-person graphs too. A view with no folders of its own keeps the vanilla animation, and a note in the build report says so:

reload: power_armor_third_person, third_person keep the vanilla animation for WPNReload1 (their folders do not have the clips)

When a view has folders of its own but not a variant’s clip, the view also keeps the vanilla animation, and esx gives a VARIANT_VIEW_SKIPPED warning. A misspelled file name is the usual cause.

When you have clips for some views only, say so with a views line in the action block:

reload {
  views first_person, power_armor_first_person
  when ammo == 5 play WPNReload1
}

The other views keep the vanilla animation. esx then gives a note for them, and no warning. Each view in the line must have the clips of each variant. Views that share a graph must be in the line together: with Anims44, first_person and power_armor_first_person share one graph. The line applies to the variants only. A speed line still applies to each view.

Some actions have no third-person state in a template. With Anims44, bolt and melee keep the vanilla animation in third person.

First person and third person use different events. esx takes up to 15 free first-person events and 9 free third-person events for each weapon. explain lists them. For more variants, name the events with send.

Animation names and folders

The game finds a clip by its file name. A clip node in a graph asks for a name, for example WPNReload. The game plays the first WPNReload.hkx in the folder list of the view. Letter case does not matter.

So each file must have the vanilla name that the template’s graph asks for: WPNEquip, WPNReload, WPNFireSingleReady and so on. A file with another name plays only when a when … play NAME line asks for it.

The folder list of a view has three parts, in this order:

  1. animations/<view>/ next to the .bhv file. esx mounts it at Actors\<prefix>\<weapon>\<View>.

  2. The folders in the animations block of the weapon:

    animations {
      first_person "Actors/MyStudio/MyGun/FirstPerson"
      third_person "Actors/MyStudio/MyGun/ThirdPerson"
    }
  3. The vanilla folders of the like keyword.

A view can take single clips from a folder, without the other files of that folder. Start the line with the view, then write clip, the clip names, from and the folder:

animations {
  first_person "Actors/MyStudio/MyGun/FirstPerson"
  first_person clip WPNIdleSighted, WPNFireSingleSighted from "Actors/MyStudio/MyGun/FirstPerson/SightsGlow"
}

esx copies each file into the folder of part 1. Thus a single clip has precedence over each listed folder. You do not have to copy the file into animations/<view>/. A clip must have one source only. A clip line and a file in animations/<view>/ with the same name stop the build.

A listed folder is relative to Meshes. Its clips come from the game data, or from a directory that you give with --asset-dir DIR. That directory has the same layout as Data, for example DIR/Meshes/Actors/MyStudio/MyGun/FirstPerson/WPNReload.hkx.

The views are first_person, third_person, power_armor_first_person and power_armor_third_person. Power-armor first person also searches the first-person folders, as the vanilla weapons do.

Clips from another mod

The Data folder of another mod can have the clips that your source names, with its own graphs and other files. --asset-dir of behavior build accepts finished clips only, so you cannot give that folder directly. plugin collect-assets with --behavior makes a folder that you can give:

esx --data '/path/to/Fallout 4/Data' plugin collect-assets build/MyGun.esp \
  --asset-dir input/donor-mod --behavior mygun.bhv \
  --exclude-archive 'Fallout4 - *' --exclude-archive 'DLC*.ba2' -o build/inputs
esx behavior build mygun.bhv --plugin build/MyGun.esp --asset-dir build/inputs -o build/main

The command collects the assets of the plugin records, and these clips of the source:

  • the clips in each listed folder, and in the subfolders that the template uses for actor types, for example Player;
  • the files of the clip NAME from lines;
  • the clips in animations/<view>/ next to the source.

It does not collect the graphs, the AnimTextData files or the other folders of that mod. The manifest of the report lists each file with its source path and, after the copy, its SHA-256. With --dry-run, esx copies nothing and gives the hash of the source clips only.

Collection is not permission. A file of another mod stays the work of its author. Make sure that you have the permission for each source in the manifest before you distribute a copy.

The build report

Use --format json to read the report. For a mod source, it has these parts:

Part Content
lowering.variants For each variant: the condition, the clips, and the event that selects it in each graph. clip_files lists the file that supplies each clip, the views that use it, its duration and its annotations.
lowering.notes The speed bindings, the views that keep the vanilla animation, the single clips of each view, and the carrier events.
lowering.selected_clips For each single clip: the view, the source file, and the path of the copy in the build.
warnings Probable mistakes, for example VARIANT_VIEW_SKIPPED and CLIP_ANNOTATION_MISSING. Text mode prints them on stderr.
attachment Messages of the IDLE step.

behavior check and behavior build read each variant clip. You do not have to run behavior clip-info on each file.

Warning Meaning
VARIANT_VIEW_SKIPPED A view has animation folders of its own, but no folder has the clip of a variant. The view keeps the vanilla animation, and the build continues. If the clip must play in that view, correct the file name: the file is NAME.hkx in animations/<view>/ or in a listed folder of the view. If you have no clip for that view, add a views line to the action block. esx then gives a note and no warning.
CLIP_ANNOTATION_MISSING A variant clip of a reload action has no ReloadComplete annotation. The clip plays, but the game does not refill the magazine. esx compares the name without letter case.
CLIP_UNREADABLE A variant clip file is not a clip that esx can read. The report has the reason in clip_files[].error.
SOUND_NOT_FOUND A clip has a SoundPlay.<editor ID> annotation, and no sound record with that editor ID is in the plugin or its masters. The game plays no sound there. Add the sound record, or rename the annotation with a sounds block.
WEAPON_BOLT_ACTION The weapon record has the Bolt Action flag, which it usually keeps from the vanilla weapon that you copied. The game then plays the bolt clips after each shot. The mod has its own fire clips but not each bolt clip, so a vanilla bolt clip plays on your weapon. Clear the flag in the weapon record, or add the clip that the warning names.

The attachment messages list the vanilla IDLE records that the game tests before your variants. Such a record keeps its priority. For example, in third person, RaiderSneakEquip (IsSneaking == 1) comes before the equip variants. So an equip variant with IsSneaking == 1 does not play in third person. Use another condition.

Read a behavior mod of another author

Two commands read a graph file that another tool made. They need no source file.

esx hkx diff TheirGunBehavior.hkx
esx hkx events TheirGunBehavior.hkx --event reloadStart

hkx diff finds the vanilla graph that the file comes from, and lists the added, removed and changed nodes, events and variables. Nodes are matched by name, so the object numbers of the two files do not have to agree. Use --base FILE to name the base graph yourself.

hkx events lists what uses each event in the graph. waits is a transition that the event starts. sends is a notify event or a clip trigger. The unused list has the events that no part of the graph uses. Such an event can still have a sender outside the graph: an IDLE record, a clip annotation, a Papyrus script or another graph.

Graph-level control

A mod source can also contain graph-level statements. They change the graph copies after esx makes its changes:

variables { EquipSpeed: float = 1.2 }
graph GunBehavior { in clip(WPNEquip) { bind playbackSpeed = EquipSpeed } }
graph WeaponBehavior { in clip(WPNEquip) { set cropStartAmountLocalTime = 0.1 } }
select ActionSighted for GunBehavior { when IsSneaking == 1 send testBigBoy }

A graph ID is the file name of a template graph without the extension. explain lists the IDs for each view. set works on these clip members: animationName, playbackSpeed, cropStartAmountLocalTime, cropEndAmountLocalTime, startTime and enforcedDuration.

Not ready

  • The third-person holster has no complete game test. In the tests, the vanilla third-person holster also played no animation.
  • The power-armor views have no game test.
  • You cannot put your variants in front of a vanilla IDLE record that the game tests first.
  • esx does not make synchronized animation pairs, or stance and speed tables.