-
Notifications
You must be signed in to change notification settings - Fork 0
Pawn Props
Auto-generated from the repo docs by
tools/sync_wiki.sh— edit the source Markdown in the repo, not this wiki page.
Status: SOLVED (2026-07-16) — a custom prop renders in a pawn's hand in-game. The third injection axis, after units and districts. Proven end-to-end the same night the district pipeline shipped: Humankind's Slingers — bare-handed since release — now carry an actual sling (a free Sketchfab model), positioned in the hand.
Extended (2026-07-19): HAND PROPS ON CUSTOM SKELETONS — the Combine soldier carries an M60. A second, fully runtime attachment route for models injected by the unit axis (whose donors — vehicles — have no weapon slots, and whose skeletons are ours, not the game's): see §"Hand props" below. Weapon in the soldier's hand, correct grip through idle/run/stance/fire, in its own gunmetal skin.
Unlike the unit and district axes, this one rides the game's own data path: a pawn definition's attachment slots already reference equipment assets by GUID, and pawn definitions are ordinary moddable data. The plugin only has to cross one gate (below).
PresentationPawnDefinition.Attachements[] = { SlotName ("Weapon_RightHand_0"), Fragment.Guid }
→ PresentationPawnFragmentMesh (ScriptableObject — the EQ_* assets)
.ModelPrefab (GameObjectReference) → matched against a REGISTERED MeshCollection.SourcePrefab
.ModelName → GetFxMeshIndex(name) inside that collection
.MaterialRef (Guid) → must have an OutputLayerEntry (borrow a vanilla weapon's)
.CastShadow / .tags → tags only flag Bow/Shield animation variants; default is fine
→ a RIGID mesh glued to the slot's bone (no skinning), GPU-encoded at pawn spawn
MeshCollection is itself trivial: { prefab (the SourcePrefab key), skeleton (null for a rigid prop), skinnedMeshInfos[] = { MeshName, FxMeshContent { Guid → an FxMesh, ImportAngles } } } — the FxMeshContent encoding
fields fill themselves at GetMeshIndex time, so authoring only needs the FxMesh GUID. Skeleton derives from
MeshCollection, which is why the same registration API serves both.
Proof shortcut (zero code): point a vanilla pawn's weapon slot at an existing EQ fragment (Slingers +
EQ_DLC_04_Weapon_Boomerang_01) in the SDK, rebuild the mod — the slot honors mod data entirely.
Tools ▸ HAF ▸ Prop Lab authors the whole chain from a model file:
-
Dump (the authoring template): paste any vanilla fragment's GUID — the Asset Picker's info panel shows it in
32-hex form, accepted directly (nibble-swap each byte, then four little-endian int32 = Amplitude
{a,b,c,d}; the same encoding as image references). The dump lists every field; itsMaterialRefis the one you'll borrow (the Default button fills the sharedEQ_DLC04_Weaponsmaterial, verified working). -
Bake prop chain: static bake (
UniversalBaker.Build—pawnDescriptionis registry-only, so no dummy pawn) → bone-free FxMesh (DistrictBaker.BakeFxMeshwithmergeSubMeshes: true) →<name>_Collection.asset+EQ_<name>_Fragment.asset, GUIDs printed (collection GUID → clipboard). - Assign the fragment to the pawn's slot via the Asset Picker (it indexes project assets; the blank 3D preview there is normal — the editor can't resolve collections the game registers at runtime).
Settings persist in EditorPrefs; the embedded preview shows the baked prop (decimation damage is visible before a relaunch).
| Key | Meaning |
|---|---|
PropRegister |
master enable |
PropCollectionGuids |
semicolon-separated a,b,c,d GUIDs of our MeshCollections |
PropCollectionNames |
matching asset names (vestigial — see trap 3's correction) |
Hk_PropRegister (Harmony postfix on AnimationManager.AnimationLoad) registers our collections via the public
RegisterMeshCollection right after the game registers its own — before any pawn definition resolves its fragments —
and re-arms per session. An Update-tick registration survives as a late-repair safety net only — since 2026-07-21 it
is armed by that first AnimationLoad (before the mod bundle mounts, every catalog request is a guaranteed miss
that LogErrors into the Amplitude diagnostics — it used to paint 64+ red lines per boot) and paced to ~1 attempt/s.
- The mammoth herd. If the fragment's collection isn't registered when the pawn definition loads, the definition FAILS its Load, never gets a pawn id, and its units render as pawn definition 0 — a mammoth. A unit of stacked mammoths means "collection not registered", nothing else.
-
Registration timing. Pawn definitions resolve fragments inside the loading chunk — an Update-tick loses that
race by construction. Hence the
AnimationLoadpostfix. -
Amplitude's asset catalog misses mod-bundle MeshCollections by GUID⚠ MISDIAGNOSIS, corrected 2026-07-21: the catalog resolves mod-bundle MeshCollections by GUID fine once the bundle is mounted. The "gap" was our ownParseGuidCsvsplitting on'-'and silently stripping the sign off negative GUID components — the catalog was always being asked for a corrupted GUID. The by-name fallback (PropCollectionNames, viaAssetBundle.GetAllLoadedAssetBundles) never actually worked either — Amplitude mounts community bundles through its own asset provider, invisible to Unity's loaded-bundle list — and survives only as dead weight (removal candidate). Lesson: when a workaround's comment claims an engine gap, re-verify the input before believing it. -
The pawn-fragment GPU encoder draws only submesh 0. A multi-material bake splits submeshes — the two-material
sling rendered cords but no pouch.
BakeFxMesh(mergeSubMeshes: true)flattens them (safe: the packed atlas already unified the UVs). - Decimation eats thin sheets. The sling's leather pouch survived only at Target-tris 20000 (1500 collapsed it entirely). Props are small; check the Prop Lab preview after each bake.
-
Re-bake GUID drift. Unity usually recycles the GUID for delete+recreate at the same path within a session,
but not across editor restarts — re-pick the fragment on the pawn slot after a re-bake (a stale slot GUID = trap 1).
⚠ A stale
PropCollectionGuidscfg entry is NOT harmless (corrected 2026-07-21: the name fallback never worked) — update the cfg GUID after any re-bake that changed it, or the collection silently fails to register (= trap 1). -
Orientation/position iteration. ⚠ CORRECTED 2026-07-19: editing the
<name>_FxMeshasset's Import Angles does NOT work — the baked angle field doesn't survive the mod bundle (in-game the class default(-90,0,0)applies; see §Hand props). Author orientation with the Prop Lab's Rotation offset (baked into vertices, preview-visible) and position with Position offset; test one axis at a time with a deliberately large value to learn the frame mapping, then dial in. ThehandPropAnglesregistry override is the relaunch-only knob.
-
Own textureSOLVED 2026-07-19 for hand props: the plugin paints the prop's own<name>_Atlason a private clone of the borrowed layer, repainted per tick (see §Hand props). Vanilla-slot fragments still borrow. -
Registry: prop recipes now persist per prop in
haf_props.json(Edit existing / New / Remove in the Prop Lab); the vanilla-slot runtime remains config-key driven (PropCollectionGuids/Names).
The vanilla slot path above needs an attachment slot in the pawn description (GetSlotIndex −1 silently drops
the attachment) — and a unit-axis model's donor is typically a vehicle with no weapon slots, while its rendered
skeleton is OURS (renamed b###_ bones). The hand-prop route sidesteps all of it: the plugin constructs the
FragmentEntry itself at repoint time and glues the Prop-Lab mesh to a bone of the injected skeleton.
Authoring (all in the two Labs):
-
Prop Lab: bake the weapon (
M60) — Size relative to the pawn (a size-2 soldier carries a ~1.25 gun), Target-tris ~2000, the shared weapon material (Default). Orientation is authored with Rotation offset (baked into the vertices, visible in the preview) and placement with Position offset — both re-bake knobs. -
Animation Lab → Hand prop: pick the baked prop from the combobox (name + collection GUID fill themselves),
optional bone substring (default
R_Hand, matched case-insensitively against the renamed bones). Save (no bake) → rebuild the mod.
Registry (per model): handPropName, handPropGuid, handPropMat ("" = the shared EQ_DLC04_Weapons
material), handPropBone, and handPropAngles — a hand-edited "x,y,z" runtime rotation override stamped onto
the FxMesh asset before encoding ("" stamps zero; see the third discovery below). Override iteration =
change + relaunch only.
The three engine discoveries that made it work (all decompiled + field-verified):
-
The GPU pawn descriptor snapshots fragments at registration. Appending to
FragmentEntriesafter that leaves the renderer drawing the old fragment count (glued-yet-invisible). The game's own full rebuild is UNSAFE mid-load (it skips not-yet-loaded definitions WITHOUT reserving their slot — every later pawn type shifts onto the wrong fragments/bones; in the field this scattered the recon drones and the howitzer). The plugin instead patches ONLY its own definition: existing fragment slots copied to the buffer tail + the prop appended there +descriptor[defId]repointed at the new contiguous block. -
Weapon materials are streamed proxies. The EQ layer's material holds 64×64
_Proxytextures; Amplitude can reset the material after a one-shot paint (the skin flip-flopped between sessions). The prop atlas is painted on a private clone of the layer (unit-retexture isolation — real DLC weapons untouched) and repainted every tick with a ReferenceEquals fast-path, exactly like the unit retexture recovery. -
Baked FxMesh import angles do not survive the mod bundle — in-game the asset reports the CLASS DEFAULT
(-90,0,0), silently tipping every prop over vs. the preview. The plugin therefore ALWAYS stamps the angles at load (the registry override, else zero), making in-game orientation equal the baked vertices.
Limits: the fragment record has no scale channel — the runtime scale knob moves the glue position but cannot
resize the mesh (match sizes at bake). One hand prop per model entry for now.
Get started
- Getting Started
- Installation
- Troubleshooting
- Authoring State and Deployment
- Mod Editor version.xml Recovery
- Building
- Backup
Author models and behavior
- Editor Tools
- Factory Manual
- Vehicle Lab Quickstart
- Animated Models
- Animation Pitfalls
- Textures
- Unit Size
- Unit Combat Behavior
- Formations
- Pawn Props
- Projectiles
- Game Sound Lab
- Firing on Attack
- Turn Ease
- Facing Persistence
- Donor Clip Flight
Districts and wonders
Ship and operate
Internals and project