145 lines
No EOL
7 KiB
Markdown
145 lines
No EOL
7 KiB
Markdown
# Egg Identification Investigation
|
|
|
|
## Goal
|
|
|
|
Add a reliable egg browser to the map renderer and clarify what the game means by an egg "ID".
|
|
|
|
## Short Answer
|
|
|
|
- Fresh-game startup is hardcoded to map `1`, egg `0x1e`.
|
|
- That startup egg is not read from `CRUSADER.CFG` or another external mission map.
|
|
- For Crusader teleport eggs, the destination number that gameplay matches against is the low byte of the item `quality` field.
|
|
- The broader egg families use different payload fields depending on egg type, so the renderer should not assume that every egg-family item uses the same number source.
|
|
|
|
## Fresh-Game Default Start Egg
|
|
|
|
The existing reverse-engineering note in [docs/first-mission-map-selection.md](k:/ghidra/Crusader_Decomp/docs/first-mission-map-selection.md) already establishes the startup path:
|
|
|
|
- normal new game calls `Teleporter_CreateProcessDirect(1, 0x1e, 1)`
|
|
- the controlling call site is in `Game_Start`
|
|
- this is a code-selected default, not a config-file mapping
|
|
|
|
That means the known first-map spawn uses teleport egg id `0x1e` on map `1`.
|
|
|
|
## ScummVM Crusader Egg Model
|
|
|
|
The ScummVM Crusader engine in `engines/ultima/ultima8` maps Crusader egg families as follows:
|
|
|
|
- family `3` = `SF_GLOBEGG`
|
|
- family `4` = `SF_UNKEGG` (usecode trigger egg)
|
|
- family `7` = `SF_MONSTEREGG`
|
|
- family `8` = `SF_TELEPORTEGG`
|
|
|
|
Relevant files:
|
|
|
|
- [shape_info.h](k:/misc/scummvm/engines/ultima/ultima8/gfx/shape_info.h)
|
|
- [item_factory.cpp](k:/misc/scummvm/engines/ultima/ultima8/world/item_factory.cpp)
|
|
- [egg.cpp](k:/misc/scummvm/engines/ultima/ultima8/world/egg.cpp)
|
|
- [egg.h](k:/misc/scummvm/engines/ultima/ultima8/world/egg.h)
|
|
- [monster_egg.h](k:/misc/scummvm/engines/ultima/ultima8/world/monster_egg.h)
|
|
- [monster_egg.cpp](k:/misc/scummvm/engines/ultima/ultima8/world/monster_egg.cpp)
|
|
- [teleport_egg.h](k:/misc/scummvm/engines/ultima/ultima8/world/teleport_egg.h)
|
|
- [teleport_egg.cpp](k:/misc/scummvm/engines/ultima/ultima8/world/teleport_egg.cpp)
|
|
- [current_map.cpp](k:/misc/scummvm/engines/ultima/ultima8/world/current_map.cpp)
|
|
|
|
One structural detail matters here: generic trigger eggs and teleport eggs inherit from `Egg`, but `MonsterEgg` is its own `Item` subclass instead of an `Egg` subclass. That helps explain why Crusader monster eggs do not line up perfectly with the generic egg-field conventions.
|
|
|
|
## Which Field Holds The Number?
|
|
|
|
There is not one universal answer for every egg-family item.
|
|
|
|
### Teleport eggs
|
|
|
|
Teleport eggs are the important case for spawn/start locations.
|
|
|
|
ScummVM uses:
|
|
|
|
- `TeleportEgg::getTeleportId()` = `(_quality & 0xFF)`
|
|
- `MainActor::teleport(mapNum, teleport_id)` to move to a destination egg
|
|
- `CurrentMap::findDestination(id)` to find a teleport egg target with matching low-byte quality
|
|
|
|
It also distinguishes two teleport egg roles:
|
|
|
|
- `frame != 1` = active teleporter trigger
|
|
- `frame == 1` = destination marker
|
|
|
|
So for renderer purposes, the gameplay-relevant teleport egg id is the low byte of `quality`, not `mapNum`.
|
|
|
|
### Generic/usecode eggs
|
|
|
|
ScummVM's generic egg intrinsics expose:
|
|
|
|
- `Egg::I_getEggId()` -> `getMapNum()`
|
|
|
|
So family `4` eggs use `mapNum` as their generic egg id in the engine interface.
|
|
|
|
### Monster eggs
|
|
|
|
ScummVM's monster egg accessor exposes:
|
|
|
|
- `MonsterEgg::I_getMonId()` -> `getMapNum() >> 3`
|
|
|
|
That is a separate meaning from teleport ids.
|
|
|
|
There is an important Crusader-specific wrinkle for renderer work:
|
|
|
|
- the generic ScummVM `MonsterEgg` model stores monster shape in `quality & 0x7FF` and activity in the low three bits of `mapNum`
|
|
- but exported No Remorse `0x024F` frame `0` monster eggs often have `quality = 0` and still carry non-zero `npcNum` values that line up with useful DTABLE rows
|
|
- concrete exported examples include Remorse map 69 (`npcNum = 2` and `npcNum = 6`) and Remorse map 2 (`rawNpcNum = 5`)
|
|
- No Remorse frame `1` `0x024F` entries also exist, but the checked cases were zeroed placeholders with no useful `npcNum`, `mapNum`, or `quality` payload
|
|
- exported No Regret `0x024F` currently diverges earlier: its shape definition resolves to `family: 0`, `kind: terrain` rather than `family: 7`, `kind: egg`, so the renderer should not assume that Remorse monster-egg semantics carry over unchanged
|
|
|
|
So the renderer keeps two interpretations side by side for family `7` eggs:
|
|
|
|
- the egg-family label remains `monster id = mapNum >> 3`, matching the engine intrinsic surface
|
|
- the NPC preview for evidence-backed `0x024F` frame `0` eggs comes from `npcNum`, because that field matches observed spawn identity better than the zero-heavy `quality` field in current Remorse exports
|
|
|
|
### Glob eggs
|
|
|
|
Glob eggs expand glob contents using the item `quality` value as the glob reference.
|
|
|
|
## Renderer Decision
|
|
|
|
The new map-renderer egg UI treats all egg-family items as eggs, but labels each one with the field that matches its ScummVM family behavior:
|
|
|
|
- family `8`: teleport id = `quality & 0xFF`
|
|
- family `4`: egg id = `mapNum`
|
|
- family `7`: monster id = `mapNum >> 3`
|
|
- family `3`: glob id = `quality`
|
|
|
|
That keeps the viewer useful for teleport/start-point work without flattening all egg families into one misleading scheme.
|
|
|
|
For the NPC preview overlay, the renderer now makes one additional evidence-backed exception:
|
|
|
|
- `0x024F` frame `0` monster eggs with a non-zero `npcNum` get the same DTABLE-backed blue NPC preview as `0x04D0` spawners
|
|
|
|
That exception is intentionally narrower than the generic egg browser labels. It reflects the currently verified Remorse data, not a blanket claim that every family `7` egg in every game uses `npcNum` as its authoritative spawn row.
|
|
|
|
## Current Cross-Game State Of `0x024F`
|
|
|
|
- No Remorse: exported shape definition resolves to `family: 7`, `kind: egg`, and scene items produce `egg.type = monster-spawn`
|
|
- No Regret: exported shape definition currently resolves to `family: 0`, `kind: terrain`, and scene items do not produce egg metadata
|
|
|
|
That is the strongest current reason to keep the new monster-egg preview support scoped to the evidence-backed Remorse path instead of enabling it globally for both games.
|
|
|
|
## Weapon Room `250`
|
|
|
|
The current renderer catalog data already contains egg-oriented notes that include `250` in the known egg-id lists for Crusader egg shapes, which is consistent with the user-observed convention that egg `250` is the weapons room/test room marker.
|
|
|
|
Those catalog references are evidence for the workflow and UI, but the renderer should still treat them as conventions layered on top of the raw egg-family decoding above.
|
|
|
|
## Outcome For The Feature
|
|
|
|
The egg browser added to the renderer now:
|
|
|
|
- lists every egg-family item in the loaded map
|
|
- shows the decoded id appropriate to that egg family
|
|
- can center the camera on a chosen egg and pin-select it
|
|
- can draw zoom-stable id labels over eggs in the viewport
|
|
|
|
## Remaining Uncertainty
|
|
|
|
Two things are still worth keeping in mind:
|
|
|
|
- family `7` Crusader handling is still marked in ScummVM as partly suspicious because those records can also behave container-like
|
|
- `250 = weapons room` is a strong workflow convention, but this investigation did not add a new executable-side proof beyond the catalog evidence and known reverse-engineering notes |