Warcraft III Unit Animation Properties And Transformation Forms
Required Animation Names
Warcraft unit profile data may provide animProps (uani, Required Animation Names). OpenRealm copies that authored value into the per-unit animation_props set when SP_SpawnUnit() initializes a unit. SetUnitAnimation retains the logical animation request separately in animation_request; animation lookup then combines the request tags with the unit's active Required Animation Names.
AddUnitAnimationProperties mutates the active per-unit tag set and immediately reselects the retained logical animation family. This state is inline edict data, so ordinary WC3 save/load persists it with the unit record.
Numbered model sequences such as Walk, Walk 2, and tagged walk variants are one logical family. Movement keeps the selected variant while that sequence is active, then selects a same-syncpoint variant when the cycle completes. Changing the logical animation or required properties still performs a fresh selection; movement ticks must not restart a numbered walk every frame.
The stock Medivh model/data combination is unusual: the raven-form unit requests alternateex, while the shared model exposes Alternate sequences. Animation selection therefore falls back from a required alternateex tag to alternate only when no matching AlternateEx sequence exists. A genuine AlternateEx model remains distinct.
Raven Form Orders
WC3 exposes two stock abilities that use the ravenform / unravenform order pair: Medivh Crow Form (Amrf) and Druid of the Talon Storm Crow Form (Arav). Each ability's object data owns its own transformation endpoints:
- AbilityData Data A (
Data11in ROC,DataA1in TFT) is the base unit type; - AbilityData
UnitID1is the raven/alternate unit type.
The immediate orders ravenform and unravenform first choose the stock transform ability whose authored Data A / UnitID endpoints contain the current unit, then transform the existing edict between those two authored types. The edict/JASS handle is retained rather than replacing the unit with a new entity. Rebinding the type reruns the normal unit-data initialization, including model, movement layer, collision, attacks, authored Required Animation Names, and other presentation state, while preserving current health/mana ratios and temporary combat bonuses.
Preplaced alternate-form campaign units are valid transformation endpoints even if they did not pass through OpenRealm's runtime ability-add path before the map script issues unravenform. This is required by the Prologue campaign Medivh raven: the map starts with the alternate unit and later orders that same unit to return to the base form.
The type rebind still happens on the script-issued order so the existing edict immediately owns the destination unit data, but presentation then runs the shared model's authored morph sequence as an active umove_t. Human-to-raven temporarily removes alternate-form animation properties to select the untagged Morph clip, restores the raven alternateex requirement while that clip is active, and enters the raven stand family when it completes. Raven-to-human plays Morph Alternate and clears the alternate-form properties at completion before entering the ordinary stand family. The morph move explicitly starts at the selected sequence's first frame, including for cinematic units that may otherwise be paused.
M_MoveFrame() restarts the animation that is active after an end callback returns. This matters for morph completion: an end callback can replace Morph Alternate with Stand, and resetting the frame to the completed morph's start would make the newly rebound human model sample an unrelated frame for one tick, producing a visible blank between forms.
Full Amrf/Arav cast time, transformation effects/sounds, duration/buff-driven automatic reversion remain separate compatibility work. The post-morph takeoff now interpolates the destination flying form's authored fly height over the ability's Data C duration; landing remains an immediate form change.
Ability Ownership
skills/s_raven.c registers a_raven_form for both Amrf and Arav. The ability owns endpoint resolution,
command-card toggling, ravenform/unravenform orders, both morph umove_t records, completion callbacks, and takeoff.
G_TransformUnitType remains the generic in-place type-rebind operation shared with other transformation abilities.
ability_t.orders/.order declare immediate orders; FindAbilityByOrder resolves them without spell-name branches
in m_unit.c. Persistent .update callbacks run through S_RunAbilityUpdates in monster_think. InitAbilities
builds a unique callback list once, so two rawcodes using one handler do not tick it twice and each unit tick avoids
scanning the full ability registry. If an order replaces forward Morph before its end callback runs, the Raven update
starts the pending ascent without replacing that order. Callbacks own eligibility and pause policy: Raven's ascent continues after Move
replaces the morph order, including while paused, preserving #398's independent timer behavior. Animation itself
still pauses. Morph moves have no acquisition think callback, so idle AI cannot replace a morph with Attack.
The takeoff values are inline edict.raven state, explicitly described by raven_fields in g_save.c and covered
by ROC/TFT round-trip tests. The generic currentmove relocation retains ability-owned morph moves.
Investigation Notes
-
398 (
These now belong to the ability; generic animation completion remains indd551065) introduced morph/takeoff inm_unit.cand a direct Raven update inmonster_think.M_MoveFrame. - The animation regression trace showed
old=morph alternate old_start=0 new=stand alternate new_start=1000; restarting the completed clip selected the wrong frame after the callback. -
Live ROC Prologue01 originally rejected
nmdm:AmrfloadedUnitID1=nmdm, but its base ID was zero. Archive inspection showedData11=nmed; the DDX schema loaded ROC Data11..Data34 only as numbers.BZ_AB_ROWnow populates both numeric and FOURCC views, as the TFT DataA..DataI schema already did. Do not patch Raven with hardcoded unit IDs or per-format column lookup. -
Prologue01 orders Move 0.5 seconds after
ravenform. The interruption trace showedmove=walk,anim=Stand Alternate,height=0with takeoff still pending: replacingcurrentmovebypasses the Morph end callback. The ability update now starts ascent when the pending morph has been replaced.
Verification
Focused unit tests install both synthetic Amrf and unrelated Arav object data and verify that the Medivh-style endpoint resolves through Amrf, then verify that:
ravenformchanges the same unit from Data A to UnitID and adopts the alternate type'sanimProps;unravenformrestores the Data A unit type and clears the alternate type's authored properties;- a preplaced alternate-form endpoint can execute
unravenformwithout first being transformed by OpenRealm. - Raven Form holds the transformed unit on its support surface through
Morph, then interpolates to its authored fly height.
The mdxgen morph fixture supplies distinct base/alternate Stand, Walk, and Morph clips at the original
Units/Creeps/Medivh/Medivh.mdx archive path. It is generated into the fixture MPQ only. Dispatch tests cover
registered orders, toggle state, nonmatching units, ordinary-unit update no-ops, morph completion, ascent during
Move/pause, reversal, and both data-column schemas.
Use the automated reproducers for functionality checks; they replace repeated launches of Prologue01:
make test-wc3-engine WC3_PATTERN='wc3_unit.*'
make test-wc3-engine WC3_PATTERN='wc3_combat.mmoveframe*'
make test-wc3-engine WC3_PATTERN='wc3_slk.roc_ability_data_preserves_object_ids_and_numbers'
make test-wc3-engine WC3_PATTERN='wc3_save.*'
make test
These headless fixture tests exercise the same order, callback, interruption, and persistence situations as the campaign. If another gameplay failure is reported, extend the reproducer rather than relying on another manual replay. See test-first verification.
The original live-archive reproducer is retained for an uncovered visual/platform question only; identify that gap before
using it (repeat with -tft if the gap depends on archive variants):