Stuff
This commit is contained in:
parent
ee33f94b4b
commit
f92d1504fa
547 changed files with 37597 additions and 0 deletions
248
docs/usecode-equipment-system.md
Normal file
248
docs/usecode-equipment-system.md
Normal file
|
|
@ -0,0 +1,248 @@
|
|||
# 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
|
||||
Loading…
Add table
Add a link
Reference in a new issue