Skip to content

v0.9.10 -- Keep a follower out of the fight, leash it, and play an animation at a target

Latest

Choose a tag to compare

@marthofdoom marthofdoom released this 29 Sep 20:03
· 28 commits to main since this release
  • A mod can now keep an NPC out of combat for a while. Claim kIntent_CombatReentryDeny with a number of seconds in APMF_Param.fval (0 means 10, at most 120). While that time runs, the game cannot start a fight for it: when it tries (the NPC sees an enemy, gets hit, an ally is fighting, a script asks), the game's own combat start says no, before it draws a weapon or does anything else.
  • It is the other half of a retreat. The mod stops the fight once with the game's own StopCombat, and the game can no longer pull the NPC straight back in. Harbinger never stops a fight itself. An NPC that is already fighting keeps fighting, but the game cannot add new foes to its fight while the time runs.
  • A mod's own combat entry still works. A kIntent_CombatEntry request for the NPC goes through. When that fight ends, the rest of the window keeps the NPC out again.
  • It ends by itself when the time runs out or the NPC dies, and the log says which. Nothing is undone because nothing was written. The time counts from the mod's own request, so a request that takes over from another mod's gets only what is left of its own time.
  • Harbinger checks its own work. The first time the game starts any fight, the log says the check was seen. If an NPC gets into combat while its window runs anyway, the log says DENY MISSED.
  • [CombatReentryDeny] bCombatReentryDeny is new in APMF.ini, default 1. Set it to 0 and every request is refused.
  • New API revision (v15). It adds the intent and nothing else. Check abiVersion >= 15 before asking for it. An older Harbinger refuses the request. A mod built against an older revision is unaffected.
  • Both runtimes. The spot in the game's combat start was read on the 1.6.1170 and 1.5.97 binaries and is byte-checked at startup. Any other build is refused by name. VR is refused. The startup check now covers 179 addresses.
  • Ran in the 2026-09-28 field session with MFO's retreat. The seat was seen at startup, every deny was logged as refused, and no deny was missed.
  • A mod can now leash an NPC in combat. Claim kIntent_PursuitLeash with an anchor actor in APMF_Param.target (usually the player) and a radius in APMF_Param.fval. While the NPC is farther than the radius from the anchor, the game's own moves toward its target (closing in, chasing, flanking, repositioning, a ranged NPC closing to range) and around its search area when the target is lost are refused when that goal is farther from the anchor still.
  • A chase that already started is stopped too. The game runs a whole chase inside one move, so Harbinger also ends a running move once the NPC crosses the radius, the same way the game ends one whose path failed.
  • The NPC keeps fighting. Attacks, spells, blocks, dodges and every other move run as the game decides. A goal nearer the anchor is always allowed. Harbinger does not walk the NPC back. Past the radius a step back toward the anchor can be refused too, so the NPC may stand where it is.
  • Its own claim. The leash does not share a claim with the combat-action deny, so another mod's attack or cast deny on the same NPC does not replace it.
  • Two moves are not leashed: the game pulling the NPC back into the fight's area, and a search walking to a door. The log and the docs say why.
  • It ends with the claim: release, a save load, a new game, or the NPC unloading. An anchor that is dead, unloaded or in another cell holds nothing back.
  • [PursuitLeash] bPursuitLeash is new in APMF.ini, default 1. Set it to 0 and every leash request is refused.
  • New API revision (v16). It adds the intent and nothing else. Check abiVersion >= 16 before asking for it. An older Harbinger refuses the request. A mod built against an older revision is unaffected. An earlier draft of v16 carried the leash as a combat-action bit, kCombatActionCat_Pursuit. It was never released and is gone from the header.
  • Both runtimes. The fourteen moves and the game's own way of ending one were read on the 1.6.1170 and 1.5.97 binaries and are checked at startup. The startup check now covers 185 addresses.
  • Ran in the 2026-09-28 field session with MFO. Leashes were set and released, and the game's own moves past the radius were refused.
  • A mod can now play a chosen animation at a target. Claim kIntent_Idle with an IDLE record in APMF_Param.form and, if it needs one, the reference to play it at in APMF_Param.target. Harbinger plays it once with the game's own call, the same one the game's scripts use. A follower can pick a lock with IdleLockPick or hand something over with IdleGive.
  • Harbinger plays it and lets go. One play per request. A Repoint with an idle plays it again. The game checks the idle's own conditions and can say no. Then the request ends and the log says why.
  • Release only resets a pose that is still held. A one-shot animation ends by itself. Release sends the reset only when the animation has not ended, so it never pulls the NPC out of whatever it is doing by then. It is never sent to an NPC sitting or lying in furniture.
  • The log says whether the animation really played. Harbinger reads the NPC's behaviour graph at the call and counts a play only when the graph raises one of that idle's own entry events. None within 3 seconds of game time ends the request, and the mod sees the idle did not play. An idle that cannot play on that NPC's graph at all ends it at once. Where Harbinger cannot tell from the graph, the log says UNCONFIRMABLE and the request stays.
  • A crouching NPC stands for the animation. The game has one standing lockpick clip and no kneeling one. The NPC goes back to sneaking when it ends.
  • It ends by itself when the animation cannot be played, when the game refuses it, or when the NPC dies. The log says which. Release, a save load, a new game or the NPC unloading end it too.
  • The old form-free idle (no form) is unchanged.
  • [Idle] bIdleV2 is new in APMF.ini, default 1. Set it to 0 and every request that names an idle is refused.
  • [Idle] bIdleConfirm is new in APMF.ini, default 1. Set it to 0 and Harbinger skips the graph read: every play is logged UNCONFIRMABLE and none is ended for not playing.
  • New API revision (v17). It adds the form and target to the idle and nothing else. Check abiVersion >= 17 before setting a form. An older Harbinger does not refuse it: it plays its old default-pose reset instead.
  • Both runtimes. The game's play call was read on the 1.6.1170 and 1.5.97 binaries and is checked at startup. The startup check now covers 186 addresses.
  • The idle itself ran in the 2026-09-28 field session (IdleGive played and was confirmed). That session also showed the old check calling a lockpick confirmed on unrelated combat events when it never played. The entry-event check above is the fix. It is CI verified only.
  • Harbinger keeps its last five logs. At launch APMF.log moves to APMF.log.1, the older ones move up to APMF.log.5 and the oldest is dropped. Before, every launch wiped the log. A crash or freeze session's log now survives a few relaunches. A log that cannot be moved is noted in the new log and startup goes on. Seen working in the field.
  • A second passive gate probe. When a walk ends blocked, Harbinger now also logs the navmesh under the spot and under the destination, and the doors, activators and obstacles along the straight line between them, with their animation state and collision. It changes nothing. [Travel] bGateProbe2 is new in APMF.ini, default 0 (off). Turn it on only for a gate field test. It ran in the 2026-09-28 field session.
  • A passive probe for the bow a melee follower keeps picking. For an NPC whose equipment a mod governs, Harbinger now logs how the game's combat AI admits a bow or crossbow, what its equip steps try to equip, whether it can reach its target, and which combat moves run while the mod owns the hands. It changes nothing. [Probe] bRangedSelect is new in APMF.ini, default 0 (off). Turn it on only for a field test. Not field-run yet.