Skip to content

v0.9.8 -- Travel to a point, blocked legs, gait, and a startup address check

Choose a tag to compare

@marthofdoom marthofdoom released this 25 Sep 02:27
· 92 commits to main since this release
  • Casting at a point ships switched off. The follower does not animate when Harbinger casts from a marker, and every action needs a proper animation. So this is a building block for an animated version, not a finished feature, and no mod should ship on it alone. bPositionCast is 0 and every request is refused with that reason in the log. Set it to 1 to try it. Walking a follower to a point does not depend on it.
  • A mod can now cast a spell at a spot on the ground. Send a cast request with kCastFlag_AtPosition and a point in APMF_Param.pos, and Harbinger drops an invisible marker there and casts the spell from it in the follower's name. The follower does not animate and its hands are not touched. The marker is deleted a frame later. It only works for spells that apply their effect at the spot directly. A spell that launches a projectile (a rune, a trap, a lobbed shot) is refused by name, because the game only places that kind of projectile from the player's crosshair.
  • Why the marker casts and not the follower. The game places a Target Location spell cast by an NPC at that NPC's own hand. It never looks at the target it was given, on either runtime, so telling a follower to cast at a marker lands the spell at the follower. A caster that is the marker lands at the marker. The call is the same one the game's own Papyrus Spell.RemoteCast makes.
  • Summons are refused, by name, and that is the game's rule. The game only ever applies a summon to the actor that cast it, so a marker cannot summon anything. An NPC's own summon already lands in front of it, because the game picks that spot itself. Self, Touch, Aimed and Target Actor spells are refused too (use an ordinary cast request with an actor target), and so are concentration spells and the three spell types RemoteCast refuses. Every refusal is one line in the log saying why.
  • A position cast never casts over the owner of the follower's casting. If another cast claim on that follower outranks the request, the request is refused on the spot and the log names the claim. A wrong spell is refused when the cast is about to happen, with the reason in the log. The marker goes in the cell that holds the point, so a point just across a cell border outdoors works, and a point in a cell that is not loaded is refused by name.
  • A position cast is not a claim. It never enters the control map, so none of the cast seats can ever read it as an actor target, and it holds no facet. The handle it returns is a label for the log. IsClaimLive is false for it and Release does nothing. Harbinger charges no magicka for it. That is the mod's call.
  • A mod can now send a follower to a spot on the ground. Travel takes a point as well as an object or a cell. Set kTravel_ToPosition, leave the form empty and put the point in APMF_Param.pos. Harbinger places an invisible marker there and the follower walks to it the same way it walks to anything else: same stopping distance, combat still cancels it, and it still gives up after two minutes of getting nowhere. That is the game's own way to say "go here", since a package cannot hold coordinates.
  • The marker lives exactly as long as the walk. It is deleted when the walk ends for any reason: arrival, combat, release, a new destination, getting stuck, or the follower unloading. Point the walk somewhere new and the old marker goes once the new one is in place. Across a load the walk is not restored (it never was), and the package is pointed back at its default so it cannot aim at a marker from the old world.
  • A save never keeps Harbinger's markers. Harbinger writes down every marker it has placed and not yet deleted, in its own co-save. Load a save made mid-walk or mid-cast and those markers are deleted as the game finishes loading. Only markers the record proves are Harbinger's are touched: the right ID, still an XMarker, standing exactly where it was placed. Anything else is left alone. A marker whose area is not loaded at that moment stays on the list and goes on a later load. A save from 0.9.7 or older has nothing to clean up.
  • Two new questions a mod can ask. They answer and change nothing. FindEmptySpace walks a distance from an actor or a point along a heading and returns a spot with the clearance asked for, snapped to the ground. It refuses a spot behind a wall, over a drop, on top of someone's head or inside a crowd, and says which. FindHostilesInSpace lists the live actors inside a sphere that the game itself considers hostile to a given actor, nearest first, capped at 64. The hostility test is the game's own, the one Papyrus IsHostileToActor calls. There is no faction list in Harbinger.
  • The queries run on the main thread only. Called from anywhere else they do nothing and say so. MFO's main-thread pump runs on that thread, so it calls straight in. SKSE's AddTask is not that thread.
  • [PositionCast] bPositionCast is new in APMF.ini, default 0 (off). Set it to 1 to allow position casts.
  • New API revision (v11). It adds FindEmptySpace and FindHostilesInSpace, their structs, and the kCastFlag_AtPosition and kTravel_ToPosition bits. Check abiVersion >= 11 before calling either query or setting either bit. An older Harbinger ignores the cast bit and makes an ordinary cast claim, and it refuses a travel request with no form. A mod built against an older revision is unaffected.
  • Both runtimes. Every call these paths make was read on the 1.6.1170 and 1.5.97 binaries before it was used, and both features refuse any other build by name. VR is refused.
  • Looking up a form off the main thread is safe now. A mod can call Harbinger from any thread, and several paths look up forms by ID. The library underneath read the game's list of forms without taking its lock. If the game added a form at the same moment (a spawned actor, a placed marker), the read could land on memory the game had just moved and crash, or come back empty. It now holds the game's own lock for the lookup, the same way the game does. Nothing else changes. A lookup only waits while the game is in the middle of adding or removing a form.
  • A walk now ends when the follower is stuck behind something. When the game cannot move an actor along its path, it puts the actor in its own "Movement Blocked" package. A closed lever gate held two followers there for 40 seconds in the field, then the walk gave up after two minutes. Now the walk ends after 3 seconds of Movement Blocked, and the log says where the follower stopped. Only time the game is running counts, so a menu open in the middle cannot end a walk that was only bumping. Short bumps of about a second, which healthy walks have, do not end it, and a one-poll flicker does not restart the count. Harbinger does not fight the game's answer. It stops and lets the mod decide.
  • Harbinger says whether a person was in the way. When a walk ends blocked, Harbinger looks for a live actor right in front of the follower: the player, another follower, or anyone else. The mod gets that actor and its kind. Someone in the way will probably move, so a looter should take another item and come back. Nobody in the way means a closed gate or a wall, and the item should wait until a door or lever changes something. No timer.
  • A mod can now ask why a walk stopped. Harbinger ends a walk but never drops the mod's claim, so a stopped walk looked the same as a package another mod took away. GetTravelLegState answers for an actor: walking, arrived, blocked (with the spot and whoever stood in front), combat, destination gone, stuck, actor gone, failed or released. It also names the claim the walk belongs to, so a mod can tell its own walk from another mod's. It works from any thread and changes nothing.
  • A refused new destination now stops the old walk. If a mod points a walk at something Harbinger cannot walk to, the follower used to keep walking to the old place with nothing to say so. Now the old walk ends and the state reads failed, naming the refused destination.
  • A mod can now pick the walking speed. Set kTravel_SpeedSet and one of Walk, Jog, Run or FastWalk. Harbinger writes it into the walk's package with the flag the game needs before it uses the speed at all. The game reads the speed when the package starts, so a new walk uses it. Changing the speed of a walk already under way takes effect when the package next starts, and the log says so. Without the bit the walk runs, as before.
  • A passive gate probe. When a walk ends blocked, Harbinger logs the doors and activators near the spot, with their flags and the game's own open or closed state. For the next ten minutes it logs open and activate events near that spot, and reads the gate's state again right after and 4 seconds later. It only writes log lines. This is how the next version will learn what a closed gate looks like to the game.
  • New API revision (v12). It adds GetTravelLegState, the APMF_TravelLegInfo struct and the gait bits. Check abiVersion >= 12 before calling it or setting a gait bit. An older Harbinger ignores the gait bits and walks at its own speed without a word. A mod built against an older revision is unaffected.
  • Built on CommonLib mit-3.7 F1b (registry 3.7.0#4). The startup self-check now also verifies the two lock functions on both runtimes, and the two calls the gate probe makes. The log line reads 175/175.
  • Not field-run. CI verified only.