Characters, the player and the camera
The character object and its key fields, the player, the camera pipeline and what the screen actually reads, near-camera dissolve, the character controller with its collision layers and trigger volumes, Wraith World and the wraith companion model, weapons and holders, runtime material slots, render IDs, mounts, and Talion's material controller.
The character object
Every character - the player, NPCs, creatures - is a CCharacter. AI characters are the NPC class (vt_0x1FB3640, 0x3180 bytes; see AI);
the player is a CPlayerObj (vt_0x1F96818).
| Offset | Meaning |
|---|---|
| +0x48 (FactionMember) | a captain's spawned character is FM+0x48 |
| +0xA0 | the body pose |
| +0xAC | position, 3 floats in cm (Y up) |
| +0xB8 | rotation, a unit quaternion (x, y, z, w); forward = q * (0, 0, 1) |
| +0xC8 | scale |
| +0xE2 | bit 0: visible |
| +0x110 | the skeleton object; [[char+0x110]+0x1C0] is its model animation instance (see Animation) |
| +0x120 | the behaviour-interface base subobject |
| +0x22E0 | the face ([[[char+0x22E0]+0x60]+0x60]+0xA0 = the face pose) |
| +0x2340 | the visual |
| +0x2348 | the visual component, which holds the inventory |
| +0x23B8 | follower byte |
| +0x2410 | the alignment (Character/Alignments) |
| +0x26A8 | the combat object (see Combat) |
| +0x2BF8 | u32 index into the sighting matrix (see AI) |
| +0x2C21 | set while a queued teleport is pending |
| +0x294C | forced dissolve mode; +0x298C / +0x29A0 target and smoothed dissolve |
The player is [base+0x2797808], the first entry of the player list [base+0x2A34C68]; the client
shell [base+0x26FFA98] holds it at +0x888. Player-only fields: +0x2F10 AI-spectator
invisibility, +0x2F88 the movement chain, +0x2F90 the camera / dissolve object, +0x2FA0 the
combat component, +0x2FA8 vision mode, +0x2FC0 the scene component, +0x3420 the screen camera
copy, +0x2578 the mount being ridden. A bone's world position comes from the pose and the bone
hashes: a skeleton object's runtime skeleton [[[skel+0x10]+0x18]+0x10] holds +0x40 the bone count
and +0x78 the bones' name hashes in pose order (Talion 128 bones, the face rig 241).
The camera
owner = [[[base+0x26FFAA8]+0x6CB0]+0x6728 + i*8] (vt_0x1FD99F8; the local player's entry has +0xDEA8 set)
owner+0xDEA8 -> the camera modifier data (md)
owner+0x5F0 -> the modifier list (sentinel; +0x5F8 first node; node = modifier + 0x10, next = node + 8;
modifier +0x08 owner, +0x20 its GameDB record, +0x28 state, 2 = finished or skipped)
md+0x66C position, +0x678 rotation quaternion, +0x690 / +0x694 horizontal / vertical FOV (radians)
md+0x650 / +0x688 the previous frame's transform / lens
A frame (0x2FC6B8): 0x51BF3C(md, dt) prepares from the previous camera and the focus; 0x51BCA8 runs
each modifier's vf7 on md, validating after each; the finalizer 0x51BE7C stores the result into the
output object md+0x768 (+0x3A4 transform, +0x3C0 start transform, +0x3F4 / +0x3F8 lens).
The screen reads that stored copy, not the live md: 0x2FEA2C -> 0x51AC00 -> 0x51AC30 -> 0x4E0CFC
copies it to player+0x3420, which drives the view, camera transitions and the lens. The live md is
the camera's state for the next frame. The gameplay camera Camera/Behavior::Camera_Primary has 12
modifiers (Adjustables, Lens, SetRoot, SmootherBegin, Distance, OrbitControl, AvoidObstacles, Pitch,
Yaw, Roll, Smoother, Collide); only the collision modifier (vt_0x1DB5FE0) calls SetCameraPos
0x51C394. Presentation cameras are the *_BS / *_BR modifier families.
The debug camera manager [base+0x2A86860] (vt_0x22F4A90, a state machine; built at client start by
0x17CD2EC, read by the screen copy 0x4E0CFC) takes a mode from DebugCamera.FreeCamControls
(handler 0x17CFF54: "Cinematic", "ClipMode" or "Marketing" stored as 0 / 1 / 2 at +0x118).
DebugCamera.Track (0x17D0074) follows a named object.
Near-camera dissolve. Camera/CharacterDissolve (34 records: hide and fade radii and lerps) is
set by camera behaviours (setter 0x1567E8, controller +0x240) and by presentation elements (1,380 of
3,548 set Dissolve.Settings, controller +0x248). 0x6C4E60 builds two capsules from the camera
along its forward axis; the classifier 0x30A368 -> 0x309128 -> 0x308BE0 returns show, fade or hide
from the capsules against each body's capsules (a camera inside a body forces hide), unless the
character's forced mode overrides it (3 volume decides, 0 always show, 2 always hide; set by
presentations' HideActors / ExcludeActors and HideAllCharacters 0x19B43B4). The result drives the
material constant k_fDissolve (0x3093F4); at 1.0 the visible bit clears. Every result reads one
table on [player+0x2F90] (vt_0x1F67370): +0x258 show {0.0, 0.2}, +0x260 hide {1.0, 0.1}, +0x268
fade {0.76, 0.2}, copied once when the player is initialised.
The character controller
ctrl = [[[player+0x2F88]+0x08]+0x28] (the movement object vt_0x22F7738 sits on the way; one
controller per character, 94 live in one scene). The controller is vt_0x1DB4DE0: +0x38 owner,
+0x40 the hknpCharacterProxy (vt_0x1DBDA20: +0x3C filter info, +0x80 position in metres,
+0xB0 velocity in m/s), +0x81 support state (2 = standing), +0xE4 collision filter, +0xEC
gravity on. Slots: 5 SetPosition (cm), 6 SetVelocity, 8 Step (-> 0x399F18 integrate), 11
SetGravityEnabled 0x3F1900, 12 IsGravityEnabled, 13 refresh support, 20 SetCollisionFilter 0x3F14B4
(writes the controller's and the proxy's filter), 24 CopyFrom. The controller object is replaced
during play (across a death or respawn), so it is reached through the chain on every use. The
character controller's gravity (Client/PlayerMovement: -2000 at the movement object's +0x54) is
separate from the rigid-body world's.
The proxy. The hknpCharacterProxy holds its shape at +0x28 (an hknpCapsuleShape on 17 of
17 live proxies) and the physics world at +0x40. +0x50 .. +0x8F is its transform: three
rotation columns, then the position at +0x80 (w = 0), which equals the character's +0xAC / 100
(0.0 cm apart on the player). +0x90 / +0xA0 are the minimum and maximum corners of a box around
the shape, relative to the position (0.73 x 1.89 m up to 0.99 x 2.56 m for orcs and humans, 3.5 x
6.16 m for the largest creature in one scene). The integrator 0x399F18 (controller slot 8) turns the
velocity into the step's displacement, +0xC0 = +0xB0 x dt (dt from the step information +0x08),
casts the capsule from +0x80 to +0x80 + +0xC0, and keeps the velocity it started from in
+0xD0, so a value written to +0xB0 is what the next step moves by. +0x120 is the up vector
(0, 1, 0). The settings that follow are the same on all 17 proxies, so they are not per character:
| Offset | Value | Use in the integrator |
|---|---|---|
| +0x130 | 0.05 | added to +0x134 for the cast tolerance; also multiplied by 0.5 in a later step |
| +0x134 | 0.1 | added to +0x130 for the cast tolerance |
| +0x138 | 10.0 | |
| +0x13C | 50 (u32) | added, with 10, to the manifold size +0x20 to size the plane buffer |
| +0x140 | 10.0 | passed to the solver as the w of its input vector |
| +0x144 / +0x148 | 100.0 / 100.0 | |
| +0x14C | 1.0 | |
| +0x150 | 10 (u32) | the bound of the cast loop |
Collision layers. The world's collision filter is the object at [hknpWorld+0x480] (vt_0x22C2530,
an engine subclass of hknpGroupCollisionFilter): two bodies collide when table[layerA] & (1 <<
layerB) (table at filter +0x24, symmetric). The player's proxy is on layer 7 (row 0x0030003D);
every AI proxy is on layer 1 (row 0x00000001, the world only). Hit capsules use layer 6. Every
character also carries a sensor body on layer 28 (filter 0x1C) exactly at its position. Trigger
rows: 27 = 0x10000001 (layers 0 and 28), 28 = 0x48000001 (0, 27, 30), 30 = 0x10000001 (0, 28). The
world's body array is [hknpWorld+0x28] (15,360 entries of 0xB0: +0x30 position in metres,
+0x60 shape, +0x6C filter info with the layer in bits 0-4, +0x70 id); about 4,300 bodies are
live in a region.
Trigger volumes. Script triggers are Havok trigger bodies: the trigger class table at 0x2A61E48
(TriggerSpatialBase constructor 0x1A52C88, TriggerSpatialShape 0x65575C / vt_0x1DC1228,
TriggerSpatialDims 0x65599C / vt_0x1DC1660). TriggerSpatialDims vf45 0x3DAD68 creates the trigger
through the physics system on layer 27 (31 for corpse triggers); TriggerSpatialBase vf21 0x3DA8AC
filters by script tags, the player, excluded character types, mount and rider. Layer 27 meets only
layers 0 and 28, so triggers react to characters' sensor bodies. The levels place out-of-environment
kill and teleport volumes this way (for example OOEKillVolumes / OOEKV_* under Minas Ithil and
the fortress walls' *.OOETSS / *.OOE_SO script objects, which teleport the player back): a
character under the world dies by trigger, not by height. The kill itself is CCharacter::Die
0x19B3EE0 (100,000 damage, the float at 0x202ECBC; ApplyDamageInternal 0x2E4BE8). The engine's
own world extents (+-5 km / +-3 km at 0x26C6588) only assert.
Height predicates. PredicateHeightAttackerIncluded (evaluator 0xEDD5F0) measures the height
above ground with a ray cast from the character's position down to 100,000 cm (0x77A6D0) and tests it
against a Combat/Predicates/Height range (On_Ground 0 - 50 cm, Jumping 40 - 5,000 cm). Every
ground ranged attack carries On_Ground; 1,320 compiled attacks use it, 318 of them AI. An attack's
AnimationData.EnableCollision / EnableGravity (combat +0x683 / +0x686) override the movement
component's collision scale (0x41BEB4) and gravity (0x41C558) while it runs. The engine also has a
native spectator movement state (NativeState.SpectatingState, SpectatorSpeedMultiplier1/2 10 and
0.1).
The player clock [[base+0x2702548]+8]+0x18 stops in the pause menu and while the game is
unfocused; [base+0x2702590] keeps running.
Wraith World and the wraith companion
Wraith World is a weapon mode: Combat/PlayerWeaponModes/VisionMode -> Client/VisionMode::VM_Wraith;
0x6A6CE4 -> 0x6A7E90(vision = [player+0x2FA8], mode): +0x28 the mode record, +0x44 active, an
amount that eases from +0x48 to the target +0x4C over the mode's 1 s ramp (ShaderVisionMode,
read by 0x115EF98). The game changes nothing on the player's own pieces: Talion's solid shaders cut
his body away with the same reveal map the wraith draws with (tRevealMapArray, k_fRevealValue).
Orc and human shaders carry only k_fDissolve.
The wraith form is the player's companion model (Combat/CompanionModel): a copy of the player
(CombatCompanionModelInstance vt_0x1DBD198; manager vt_0x1DCC8F8 at [[player+0x26A8]+0x80]: +0x38
body instance, +0x60 head instance, +0x68 companion record) built from a SimpleModel. Wraith
moves add echoes that clone it (Combat/Echo::QuickThrow = three PC_Echo with
CopyPlayerCompanion, material set Echo). Monolith's wraith pieces carry a default set of
translucent wraith materials (wraith_game.fxo, fed the character's own textures) plus depth proxies
(wraith_proxy.fxo) and an Echo set (wraith_riddick.fxo).
Material sets are compiled. 0x1B6190C builds a cache at start-up at [GM+0x6DD0]+0x18: one
entry per Model record of 0x48-byte compiled sets. Editing a record later changes nothing there, and
asking for a set index past an entry's count returns null.
Visuals, pieces and weapons
A visual (vt_0x1DA9610) has a spec at [visual+0xB8] (23 piece slots at +0x28) and a skeleton at
[visual+0x90]. SetPieceSlot 0x4C62EC(spec, slot, piece, armour design, visual) swaps one slot live
(the engine's live armour equip). A SimpleModel builder (0x4C5194) sets body, head, hair,
accessories, flair and helmet from its entry; piece slots 8-12 come only from items. Pieces follow the
host's pose through 0xF5A290 (pieces at skel+0x108 into the host's pose list); visibility 0x1BEE8C
(skeleton, flag) is the engine's "hide the base model" (the material controller's HideBaseModel
transform calls exactly that); 0x58BCF0 sets the material-set type.
Weapons are separate objects (vt_0x1DAA9D0: wielder +0x20, material controller +0x60, an
inline mesh at +0xB0) drawn by per-slot holders [vc + 0x38 + slot * 0x28] (vt_0x1DB1F68:
+0x140 the item, +0x148 / +0x150 the built models, +0x1F1 shown). A weapon skeleton has three
bones (root, handle, tip). 0x3FD228 builds only an empty holder; an equipped, drawn weapon is rebuilt
by the engine's refresh 0x45ABE4 per holder: 0x3FD21C (hide: destroys every model) then 0x3FD204
(show: builds from the item as it is now); the equip setup (vf35 0x3FCF70) does the same pair. The
weapon's look chain: Inventory/EquippedWeaponData.Model -> _StructuresInventoryItemModel.Models ->
_StructuresInventoryModel.ModelRecord -> a Model -> its default material set. Equipped weapon slots
are vectors at vc+0x4D0 + i * 0x18 (0 sword, 1 dagger, 2 bow); an item's +0xA9 is "equipped",
+0x40 its equipped-weapon data and +0x2C its holder index. The weapon TYPE comes from the item's
Inventory/Items.WeaponType, copied into the built weapon object (+0x188, by 0x3FCF70): the active
weapon is [char+0x2348] -> slot [vc+0x728] -> [vc + idx * 0x28 + 0x38] -> +0x188.
Runtime material slots. A piece's material object (piece+0x48, vt_0x1FFE3E8) has one table of 48
slots of 0x18 bytes from +0x1E8: slots 0-15 solid, 16-31 translucent (+0x368), 32-47 depth proxy
(+0x4E8); a slot's first field is the .mat resource. The setter is the object's vtable slot 47,
0x17AF0C(object, kind, index, resource): kind 0 solid, 1 translucent, 2 depth proxy; it references
the resource and posts to the render command ring, so it belongs on the main thread.
SetMaterialSet 0x4DC938(skeleton, type) re-applies a set's solid entries.
Render IDs (SetRenderID 0x1BF144(visual, id), through the skeleton's vf4 0x1BF3AC onto body,
head, pieces and attachments): the player 255, his wraith copy 254, a player-aligned mount 212,
followers 210, enemies 220. In-world characters stay in material-set type Wraith permanently (the
type's override is the pale vision glow); no shipped Model carries a Wraith-typed set (set types in
use: default 2,644, GhostWeapon 133, Echo 78, HierarchyUnknown 65, PlayerCompanion 17, Death 11).
Mounts. The ridden mount is [player+0x2578], its rider [mount+0x25A0], its visual
[mount+0x2340]. A mount has no material controller (its combat object's +0x390 is 0).
Talion's material controller
ctrl = [[player+0x26A8]+0x390] (no vtable). It animates the shader parameters of Talion and of his
wraith double; no other character has one (0 of 43 spawned characters).
| Offset | Meaning |
|---|---|
| +0x00 / +0x08 | its link in the global list of animating controllers (mgr+0x1370, walked by 0x58B44C from the world tick 0x1F6EC4) |
| +0x30 | owner |
| +0x60 | the Combat/FX/MaterialController/Setup record (Setup::Player) |
| +0x80 | an attack-linked state |
| +0x88 / +0x90 | handle references to Talion's parameter block (self) and the double's (child) (vt_0x1DC47B8) |
| +0x98 / +0xB0 | the parameter entries (0x40 each) |
| +0x100 | the active state nodes (node: +0x00 state record, +0x10 expiry) |
A parameter entry: +0x00 the parameter name's hash, +0x04 the priority of its driving state,
+0x08 an easing object (only while easing), +0x10 / +0x14 start and end time, +0x18 the
transform driving it, +0x20 default, +0x28 current, +0x30 from, +0x38 to. Value objects (float
vt_0x1DC8930, int vt_0x1F9FF30, ARGB vt_0x1F9FE70; value at +0x08) push through their slot 2 to the
handle's setter (SetParamFloat 0x3094FC, int 0x57AC84, SetParamVector 0x47E4540), each posting one
render command.
SetSetup0x739BC0(ctrl, setup) builds the entries (0x73A5E8: theRestoreDefaultTransformvalue if a transform names the parameter, else its initial value) and returns immediately when the setup is already current, so the setup record is read only when it changes.- The update 0x58B488 -> 0x58B850 eases entries (current = lerp(from, to)) and pushes; an idle entry pushes nothing.
AddState0x58E320(ctrl, state, ...) is refused when the state's tag is in the setup'sStateTagRestrictions(0x58BAFC); an entry follows a state only when its priority is at least the current driver's.RemoveState0x58B370 removes every node of a record; each parameter it drove eases to the next remaining state's value, or to its default. A state that drives a parameter the setup does not list creates an entry whose default is that state's value. The script bindings areCPlayerObj::AddMaterialState0x19BB020 /RemMaterialState0x19BEF8C.- After every equip the engine re-attaches the blocks (0x7A7738: the self block to the player and the held weapon, the child block to the double), because a piece added later is set up by 0x4C6EFC without them.
The 168 material states are parameter states; none hides a model or swaps cloth. WraithRadiance,
Vanish, WraithPing, Domination, Tower and GameChanger set Talion's k_fRevealValue 0 and the double's
k_fRevealArm 1 and k_fOpacity 1 (the body cut away, the double shown); TalionDeathSwitch shows
both; HandOfGod and Stealth draw a fainter double; _Curse sets k_fCurse 1; Player_Eltariel,
TalionWraith and DarkTalion are colour sets.
The wraith shader (wraith_game.fxo, skeletal pixel shader): the material constant buffer cb3
follows the .mat parameter order (cb3[9] fixed-reveal width and active, reveal width and arm;
cb3[10] fixed-reveal arm, reveal map index, dissolve, fresnel minimum; cb3[11] fresnel maximum,
alpha floor, opacity, opacity minimum; cb3[12].x bloom scale). Reveal = saturate((sample + (W + 1) x
Arm - 1) / W) x (1 - Dissolve) with W the reveal width; alpha is roughly fresnel x reveal x
k_vObjectColor.w x opacity x diffuse alpha. k_vObjectColor is a global shader-constant object
(0x2A39FA0, value at +0x40) filled per draw (0x254B80) from a packed colour at draw item +0x4C.
Birds and other flying creatures
Crows, bats, seagulls and the desert raptors are not characters. Each is an AvianObject (vt_0x1DBBD30,
0x518 bytes, derived from the generic game object, without a character controller, behaviour graph or
inventory) spawned by a placed world object, an AvianMarker (vt_0x1FA4CD0). A marker owns at most one bird.
Definitions
Avian/Definition holds 14 species records: Crow, Crow_Eat, bat, bat_Day, bat_DaySleep, bat_AnyTime,
Seagul, Seagull_Eat, raptor_Desert, raptor_Desert_Day, raptor_Desert_Night, raptor_Desert_Night_Clear,
testing and testingBenchmark. A definition names the model (Model.Cha_Crow, Cha_Bat, Cha_Seagull,
Cha_RaptorDesert), a scale range (crows 1.0-1.5, the others 0.9-1.1), a weather / time-of-day predicate
(Weather/Predicates/Forecast.AnyTimeNoRain, NightNoRain, DayNoWeather ...), a combat tree
(Hunting_Crow, Hunting_Bat, Hunting_Seagull, Hunting_Avian), a render-ID setup, seven animation
slots each with a weighted variant list (perched, eating, take-off, bank left, bank right, flap, landing),
perch and flight durations (average and variance), take-off height and distance, climb, descent, turn and
lateral rates, a death effect, a respawn time and the group-sound settings. A crow perches 9 +/- 6 s and
flies 60 +/- 11 s; bats fly 900 s; every species respawns after 33-120 s.
The manager
CAvianMgr is a singleton at [base+0x2A86878] (constructor 0x19C7D8C, destructor 0x19C7E00):
| Offset | Meaning |
|---|---|
| +0x00 | the markers ({begin, end, u32 capacity, u8 owned}) |
| +0x18 | the live birds |
| +0x30 | group-sound entries (no vtable; +0x00 = the definition) |
| +0x48 | enabled |
| +0x4C | the sweep cursor |
System.SetEnabledAvians (0x1BAAB9C) writes +0x48; disabling also removes every marker's bird
(0x575F98). Each frame on the main thread the client shell's update (0x1A864C) calls 0x441A10: the marker
sweep 0x4418A8, then the group sounds 0x441A3C. The sweep is round-robin: it processes
max(1, markers / 10) markers per frame from the cursor (the divisor is the static 0x267355C), so every
marker is visited about every ten frames.
Markers
A marker reads its world properties when loaded (0x1A41DF4; defaults in brackets, distances in cm):
| Offset | Property |
|---|---|
| +0x120 | position |
| +0x140 | AvianDef, the definition record |
| +0x148 | StartEnabled |
| +0x14C / +0x150 | ActivationRadius [2000] and its square |
| +0x154 / +0x158 | DeactivateRadius [2500; raised to 1.1 x ActivationRadius when not larger] and its square |
| +0x15C | ActivationPercent [100] |
| +0x160 / +0x161 / +0x162 | Unique, AlwaysFlying, Eating |
| +0x164 / +0x168 / +0x16C | the script event ids AvianSpawnScript, AvianKilledScript, AvianOutOfRangeScript (P919 of the names) |
| +0x170 / +0x174 | FearDistance [300] and its square |
| +0x178 / +0x17C / +0x180 | FlightBottom [100], FlightTop [200], FlightRadius [100] |
| +0x188 | the definition's render-ID setup |
| +0x198 / +0x19C | an orbit centre is set / its position, copied from the object named by OrbitMarker (0x1A41C34) |
| +0x1A8 / +0x1AC | FlyOff [false], FlyOffRange [4000] |
| +0x1D0 | the bird |
| +0x1D8 | the respawn time (ms) |
| +0x1DD / +0x1DE | activated / the spawn roll passed |
Per marker the sweep calls 0x43AE38 with the player's position. A disabled marker, or one whose definition's
weather predicate fails against [GameManager+0x6D98], loses its bird. Distances are horizontal (X and Z)
and measured to the player only. On entering the activation radius the marker rolls once:
random(0, 99.9) < ActivationPercent; on success it spawns (0x1A41FFC: the definition's model, a scale
drawn from the scale range and stored at the bird's +0xC8, class AvianObject, created through the object
server). While activated, a marker with no bird respawns once the engine clock passes +0x1D8. Beyond the
deactivation radius the bird is removed and the out-of-range script event fires. When a bird is destroyed
the marker sets +0x1D8 = now + RespawnTime x 1000 (0x575ED4; 5 s when the field is absent); a respawn time
of 0 or less clears the roll instead, so nothing returns until the player leaves and comes back.
In a live desert region 301 markers (raptor_Desert_Day 157, raptor_Desert 63, raptor_Desert_Night 57,
raptor_Desert_Night_Clear 24) used activation radii of 120-130 m, a deactivation radius of 140 m, fear
distances of 30-40 m and activation chances of 25, 38 and 40 percent; 250 were AlwaysFlying. Of 28 markers
whose radius covered the player, 21 were activated and 6 had passed the roll, giving 6 birds, each linked
both ways (marker+0x1D0 and bird+0x390), flying 7-26 m from its marker at 0.5-6 m above it, with scales
0.92-1.07.
The bird
| Offset | Meaning |
|---|---|
| +0xAC / +0xB8 / +0xC8 | position, rotation, scale |
| +0x120 | the hittable interface (vt_0x1DBBBD8) |
| +0x390 / +0x398 | the marker / the definition |
| +0x40C | the player is inside the marker's fear distance |
| +0x410 | flight state |
| +0x4D0 | a combat object of the definition's Hunting_* character type |
| +0x500 / +0x508 | a pending attack and its tree node |
| +0x510 | dead |
The update (vf18, 0xEF820) advances the state machine at +0x410:
| State | Function | Behaviour |
|---|---|---|
| 0 perched | 0x1A5B6B8 | perched (or eating) animations; leaves after the perch time or when the player comes within the fear distance of the marker |
| 1 take-off | 0x1A5B8A0 | take-off animation and climb from the take-off height and distance |
| 2 flying | 0xEFC48, steering 0xEFD10 | flies for the flight time around the marker or its orbit centre above FlightBottom; turns pick the bank animations, straight flight the flap animations (threshold pi/16); a FlyOff marker's bird is destroyed |
| 3 descending | 0x1A5B3E8 | landing animation down to the landing height; fear sends it back to 2 |
| 4 landing | 0x1A5B508 | touchdown, then 0 (or 2 on fear) |
| 5 attacking | 0x1A5B390 | plays a pending attack |
AvianMarker.Script_StartAttack (0x1A41FBC -> 0x1A5B160) picks an attack from the bird's combat tree for a
target and enters state 5. The four bird trees (Hunting_Crow, _Avian, _Bat, _Seagull) have no
attack nodes in a running game's merged database (132 of 281 combat trees do), so the call finds no
attack.
Killing a bird. A hit goes through the hittable interface's slot 8 (0x1A5AA58), which runs the same damage
evaluation as for characters (0x2E3B6C) and kills only when the damage is greater than 0. Death (0x1A5B01C)
sets +0x510, fires the marker's AvianKilledScript event, plays the definition's death effect, posts combat
event 0xB and destroys the object; the hit routine also calls the hit-counter increment 0x2DC668. The ranged
targeting predicate WraithRanged_Targeting lists all six Hunting_* character types (avian, bat, crow,
rat, seagull, spider).
Group sounds. 0x441A3C counts the birds of each definition within GroupSoundDistance, perched and in
flight separately, and picks the none / low / high loop from the LowCount / HighCount thresholds.
Open questions
Presentation/Settings.HideCharacterRadiusand its sphere query 0xF4DD50.- The role of the registered class name
AvianOrbitMarker. - Whether FlightRadius is the radius of a circling path or a wander bound, and how FlightTop enters the flight target (the steering 0xEFD10 is only partly decoded).
- The fields at
+0x30to+0x3Cof a group-sound entry. - What
InventoryItemUISpawner(vt_0x1FA4E78, 0x1A8 bytes, baseGameBase, constructor 0x1A4258C) does with the properties it reads: SpawnCount, SpawnPoint, SpawnOnNavMesh, NightTime, RespawnDelay and RespawnWaitForBubbleReset.
Shadow of War (Steam), game version 1.21. An unofficial fan research project, not affiliated with or endorsed by Warner Bros. Games, Monolith Productions or Middle-earth Enterprises. Middle-earth: Shadow of War belongs to its owners.