Crusader_Decomp/docs/usecode-equipment-system.md
2026-03-25 23:32:36 +01:00

248 lines
No EOL
10 KiB
Markdown

# Crusader USECODE Equipment System
## Purpose
This note records the current evidence-backed read of Crusader's surviving `equip` / `unequip` system.
The short answer is: yes, a real inherited equipment-style event system survived into Crusader, but by this point it has been generalized far beyond RPG inventory handling. In Crusader, `equip` and `unequip` are standard item usecode events that many classes reuse for actor setup, turret arming, trap activation, alarm-state changes, and environmental control.
## Short version
The funny interpretation is half true and half misleading.
What is true:
- Crusader still has first-class `equip` and `unequip` item events.
- The live binary has dedicated intrinsics for them.
- The extracted USECODE corpus has a large number of slot `0x0A equip` and slot `0x0B unequip` bodies.
- Actor-like classes such as `NPC` and `MONSTER` really do implement `equip` bodies.
What is misleading:
- this is not yet proof of a fully intact Ultima-style paper-doll equipment subsystem
- many `equip` bodies are plainly being used as generalized state-change hooks rather than literal "put item on actor" logic
- alarms, sentries, guns, floors, conveyors, cameras, and hazard objects all reuse the same event vocabulary
Current best read:
Crusader inherited the Ultima 8 event names and event slots, then repurposed them into a broad object-activation vocabulary. The old RPG-flavored name `equip` survived, but its semantics widened into `apply mode / arm / initialize / attach behavior / activate state`.
## Compiled-side proof
The live `CRUSADER.EXE` session already has named intrinsic handlers for these events:
- `10a0:2a35` = `Item_Equip`
- `10a0:2a68` = `Item_Unequip`
- nearby sibling `10a0:2b30` = `Item_EnterFastArea`
The decompiler output for `Item_Equip` is the key proof:
```c
word __cdecl16far Item_Equip(int *pitemno,uint dest)
{
res = Usecode_ItemCallEvent(pitemno,0x400,UC_Equip,...);
if (res != 0) {
return in_stack_0000fffc;
}
return 0;
}
```
`Item_Unequip` is parallel:
```c
word __cdecl16far Item_Unequip(int *pitemno,int val)
{
res = Usecode_ItemCallEvent(pitemno,0x800,UC_Unequip,...);
if (res != 0) {
return in_stack_0000fffc;
}
return 0;
}
```
That proves several things immediately:
1. `equip` and `unequip` are real compiled-side usecode events, not parser inventions.
2. They are gated by dedicated owner-row capability masks: `0x400` for equip and `0x800` for unequip.
3. The intrinsic does not directly manipulate an inventory table itself. It forwards into a generic item-event dispatcher.
The central dispatcher decompiles as `Usecode_ItemCallEvent`. Its current Ghidra decompile explicitly comments it as a generic owner-row mask gate:
- compute item usecode class id
- consult the owner-row table behind the runtime at `0x6611`
- test the supplied capability mask
- if the row supports the event, create a usecode process for the requested event
That is the architectural core of the surviving system: `equip` and `unequip` are class capabilities, not hardwired game-engine special cases.
## Cross-check against the engine source lineage
ScummVM's Ultima8/Crusader code preserves the same event interpretation.
In `engines/ultima/ultima8/world/item.cpp`:
- `callUsecodeEvent_equipWithParam(param)` is explicitly documented as event `A`
- `callUsecodeEvent_unequipWithParam(param)` is explicitly documented as event `B`
That source also shows the event neighborhood:
- event `9` = release
- event `A` = equip
- event `B` = unequip
- event `C` = combine
- event `E` = called from anim
- event `F` = enter fast area
- event `10` = leave fast area
- event `11` = cast
This strongly supports the idea that Crusader inherited an Ultima-style item event vocabulary wholesale and kept using it even after the gameplay moved away from a classic RPG.
## How widespread it is in the extracted corpus
The current exported pseudocode corpus contains:
- `77` slot `0x0A equip` entries
- `50` slot `0x0B unequip` entries
That is far too widespread to be accidental or restricted to a tiny one-off subsystem.
The distribution is also telling:
- actor/NPC classes: `NPC`, `MONSTER`
- turret/weapon classes: `BASEGUN`, `SENTRY`, `GATGUN*`, `WALGUN*`, `GOVGUN*`
- alarm/hazard/environment classes: `ALARMBOX`, `ALARMHAT`, `FFFLOOR`, `FLAMEBOX`, `STEAMBOX`
- cameras and movers: `CAM_*`, `EYECAM*`, `CONVEY_*`, `ROLL_*`, `HOVER*`
So the names survived as a general event interface across many object families.
## What `equip` means in practice
### 1. General activation or state application
Many non-actor classes use `equip` exactly the way a modern engine might use `activate`, `arm`, `enable`, or `set mode`.
Representative example: `BASEGUN::equip`
```text
set_info(0x0211, *(arg_06));
process_exclude();
spawn class_0A1A_slot_24(pid, arg_0A, ..., arg_06);
suspend;
```
Parallel example: `SENTRY::equip`
Its exported pseudocode is the same structural pattern as `BASEGUN::equip`, and `SENTRY::unequip` mirrors the same shutdown logic as `BASEGUN::unequip`.
That is not wearable-item behavior. It is a clean arm/disarm lifecycle.
### 2. Alarm-state or environmental mode switching
`ALARMBOX::equip` and `ALARMHAT::equip` are good examples of the generalized meaning.
They both begin with:
```text
set_info(0x0211, *(arg_06));
process_exclude();
```
Then they branch by frame/state and spawn or equip helper objects in specific response modes.
`ALARMHAT` is especially illustrative because it calls `Item::I_equip(pid, 0x17, item)` and `Item::I_equip(pid, 0x15, item)` on nearby `shape 0x04D0` helper objects. That is exactly where the older RPG-flavored naming sounds absurdly literal, but the behavior is really `push nearby helper into alarm response mode`.
### 3. NPC and monster setup
Actor-facing classes really do implement `equip`, but the current evidence still points to scripted setup and state application rather than visible inventory slot management.
Representative example: `NPC::equip`
The exported body does not look like a paper-doll or inventory routine. It looks like a setup dispatcher keyed by `arg_0A`:
- `arg_0A == 10`: reset, teleport to egg, gather several values, create or legalize the NPC/item context, then spawn `class_0A11_slot_29(...)`
- `arg_0A == 30`: read several values with `Intrinsic00DF(...)`
- `arg_0A == 31`: suspend
- `arg_0A == 1/2/3`: explicit but empty branches
That reads much more like `apply initialization mode` or `run NPC setup variant` than `equip helmet`.
Representative example: `MONSTER::equip`
`MONSTER::equip` checks frame/state, stamps `0x0211`, then spawns `class_0A1E_slot_2D(pid, var, monster1, arg_06)` for a range of mode values. Again, that looks like a dispatcher for monster behavior setup or activation, not a visible inventory pane.
## What `unequip` means in practice
`unequip` is the paired shutdown or detachment event.
Representative example: `BASEGUN::unequip`
```text
set_info(0x0212, *(arg_06));
process_exclude();
if (Item.getStatus(arg_06) & 4) {
spawn class_0A1A_slot_27(arg_06);
}
```
`SENTRY::unequip` is the same pattern.
That strongly suggests `unequip` has become the general reverse transition for classes that use `equip` as `arm` or `enable`.
Environmental example: `FFFLOOR::unequip`
This body does not resemble inventory logic at all. It scans nearby family-`6` objects, filters by overlap, and then spawns an actor-facing helper `class_0A11_slot_2D(retval, *(arg_06), item)` before suspending.
So by the time we reach Crusader, `unequip` has clearly broadened into `deactivate / detach / reverse state / cleanup side effects`.
## What survived from the RPG ancestor
The surviving part is not just the word choice. The system architecture survived too:
- item classes advertise event capabilities through owner-row mask bits
- the engine exposes generic intrinsics that dispatch into class-specific usecode handlers
- the event numbers remain organized in an Ultima-style item-event vocabulary
- actor and item classes both participate in the same event model
That is exactly the kind of subsystem that makes sense as an inherited RPG engine layer which later got repurposed for an action game.
## What we do not have yet
We do **not** yet have proof of a fully intact classic RPG equipment layout such as:
- fixed visible paper-doll slots
- a clean actor inventory table indexed by helmet/armor/weapon slots
- universal actor gear attachment semantics across all NPCs
The current evidence is stronger for:
- an inherited event vocabulary with `equip` / `unequip`
- class-specific scripted setup and mode application
- actor and environmental reuse of the same event names
So the safe statement is:
Crusader definitely has a hidden equipment **event system**.
Crusader may also retain deeper actor-equipment semantics under that layer, but that is not yet proven from the current pass.
## Current best model
The most defensible current model is:
1. Ultima-style item events `A = equip` and `B = unequip` survived into Crusader.
2. The compiled engine still dispatches them through generic item-event intrinsics.
3. Owner-row capability bits decide which classes support those events.
4. USECODE then interprets `equip` and `unequip` per class.
5. In Crusader, many classes reinterpret those events as `activate/deactivate`, `arm/disarm`, `initialize/teardown`, or `switch mode`, while actor-facing classes also use them for setup-like behavior.
## Good follow-up targets
The best next places to deepen this are:
1. compare more actor-facing equip bodies (`NPC`, `MONSTER`, `ANDROID`, `AVATAR`) against each other
2. find compiled-side consumers of the value passed as the `equip` parameter to see whether it maps to actor slots, modes, or stance/state enums
3. recover more names around the helper slots repeatedly spawned from equip bodies, especially the `0A11`, `0A1A`, and `0A1E` families
4. check whether any actor-only classes expose both equip and unequip in ways that look like real gear attach/detach rather than generic initialization
5. decode more loop-selector idioms so actor/environment equip bodies become less VM-shaped
6. cross-check ScummVM Crusader actor/inventory code for surviving slot semantics that may still map to the retail binary