248 lines
No EOL
10 KiB
Markdown
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 |