Files
firestorm/ADDING-A-MECH.md
CydandClaude Fable 5 e84d6e603c Document mech + map/mission workflows; README folder map + clone warning
- ADDING-A-MECH.md: full workflow for adding a 'Mech chassis (MSL ADD MECH
  touch points, positional Mech IDs, scriptaddmech.xls generator, hardpoints
  in .damage, rebuild/repack/deploy steps)
- ADDING-A-MAP.md: full workflow for maps (MapCreator terrain) vs missions
  (MW4Ed2-authored), .nfo MP registration, user vs stock routes, pitfalls
- CLAUDE.md: reference pointers to both new docs
- README.md: annotated folder map; warning to read RECOVERY.md before cloning

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-01 21:15:52 -05:00

260 lines
17 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Adding a new 'Mech chassis — full workflow
How to add a new playable 'Mech chassis to BattleTech: FireStorm (MW4/Gameleap 5.03 engine).
Reconstructed 2026-07-01 from the repo itself; the previous maintainer ("MSL", MekTek-era)
marked **every registration point** with the comment **`// MSL ADD MECH`** — when in doubt:
```
grep -ri "MSL ADD MECH" Gameleap\
```
(~41 files; the authoritative checklist is the union of those sites, which this doc walks through.)
## The big picture
A chassis is spread across **three layers**, and all three must agree on one number — the
**Mech ID** (`M_<Name>`), an index into parallel arrays everywhere:
| Layer | What lives there | What it takes to apply changes |
|---|---|---|
| Engine code (`Gameleap\code`) | ID defines, per-mech stat/UI tables, string resources | Rebuild `MW4.exe` (+ `MW4pro.exe`, `ScriptStrings.dll`, `MissionLang.dll`) |
| Content source (`Gameleap\mw4\Content`) | The mech's asset folder, tables, build manifests, shell scripts | Repack resources (`-build` via a LAB_ONLY exe) + redeploy |
| Loose runtime assets (`Gameleap\mw4\hsh`) | 2D bitmaps (selection image, target-MFD, HUD) | Copy files (loaded from disk, not packed) |
Two spreadsheets in [BTFrstrm/](BTFrstrm/) support the process:
- **`MechInfo_5.04.xls`** — the design source of truth (stats for every chassis).
- **`scriptaddmech.xls`** (Frank Galatis) — a **generator workbook** with one sheet per target file
(`MechLabHeaders`, `Conlobby.script`, `Netlobby.script`, `chassis.script`, `infobox.script`,
`MechBay_Main.script`, `weapons.script`). It emits the tab-delimited per-mech rows you paste
into those files, keeping all the index-aligned arrays consistent. Use it — hand-editing 10+
parallel arrays is how IDs drift.
### ⚠️ Mech IDs are positional — decide placement first
Mech IDs are assigned **alphabetically** today (`M_Annihilator`=0 … `M_Zeus`=64, `NoMechID`=65),
and every table below — the `.tbl` files, `mechspecs.cpp`, `huddamage.cpp`, `coord.cpp`,
shell-script arrays — is a **parallel array indexed by that ID**. Two options when adding:
1. **Insert alphabetically and renumber everything after it** (what MSL did between releases —
compare `MechLabHeaders_BK.h` where `M_Argus`=0 vs. current =4). Cleanest UI ordering; the
generator xls exists precisely to make the renumber safe. Cost: saved player variants and
old netgame compatibility break (acceptable between FireStorm releases).
2. **Append at the end** (new mech = old `NoMechID`). Preserves existing IDs, but the chassis
list is no longer alphabetical and every list/table must still grow by one.
Either way, **all clients in multiplayer must run the same tables** — the mech index travels
over the wire (`m_nMechIndex`).
---
## Step 0 — Produce the art (external pipeline)
This is the long pole. Everything else is bookkeeping.
**3D assets** (per body part, exported from 3ds Max via the plugins in
[Gameleap/mw4/Binaries/3DSPlug-ins/](Gameleap/mw4/Binaries/3DSPlug-ins/)):
- Geometry `.erf` files, one per body part, **plus `_DAM.erf` damaged variants** (see
[Gameleap/mw4/Content/Mechs/Argus/](Gameleap/mw4/Content/Mechs/Argus/) — note its parts are
named `m3_*`: part meshes can be shared/reused across chassis).
- A cockpit **cage** erf (`<name>_cage.erf`).
- Collision: `<Name>_Skeleton.obb` (hierarchical) + `<Name>_Skeleton_SOLID.obb`.
- Skeleton/armature with the standard joint naming (`joint_luarm`, `joint_torsobelow`, …) and
**weapon-mount sites** (`site_lmissileport`, `site_rugunport`, `site_light`, `site_eyepoint`, …).
- Animations: `animation\*.mw4anim` (walk/run/fall/getup set) + a `<Name>.animscript` state script.
- Textures registered in the texture pool (see Step 4).
**2D assets** (this is what the [Finished HUDS from J&J/](Finished%20HUDS%20from%20J&J/) folder
holds for ~13 pending chassis — MFD + Radar art):
- `hsh\Mechs\<name>.bmp` — mech-selection portrait (mechlab/lobby).
- `hsh\MFD\<name>.bmp` — target-damage MFD image (engine tiles these into a 1024×1024 atlas at
128×128 per mech — [render.cpp:1375](Gameleap/code/CoreTech/Libraries/GameOS/render.cpp#L1375)).
- `hsh\hud\<name>.bmp` and radar art under `hsh\radar\`.
- `Content\textures\HUD\<name>.tga` — the in-cockpit damage-readout texture (paper-doll).
- (Optional) footstep decal textures (`footsteps\<name>_default`, `_dirt`, … in the texture pool).
**Practical route:** clone the folder of an existing chassis with the same body plan, get it
in-game end-to-end, then swap in real art piece by piece.
---
## Step 1 — Create the content folder
`Gameleap\mw4\Content\Mechs\<Name>\` plus a `<Name>_destroyed\` sibling (the wreck model has its
own smaller `.data`/geometry). Files, using
[Argus](Gameleap/mw4/Content/Mechs/Argus/) as the template (all text, INI-style, `!Include`-able):
| File | Purpose |
|---|---|
| `<Name>.data` | The chassis definition: `Class=MechWarrior4::Mech`, `MechID=$(M_<Name>)`, `TechType`, `NameIndex=$(IDS_<NAME>)`, tonnage/trade value, `CanLoadECM/AMS/JumpJets/...` flags, death entity, OBB refs, lighting, effects |
| `<Name>.instance` | Runtime instance: pulls together Model/Armature/Subsystems/DamageObjects, power/armor/speed/heat ratings shown in UI |
| `<Name>.armature` | Joint hierarchy: rotations/translations/child links for every `joint_*`/`site_*` |
| `<Name>.contents` | Maps each joint to its part model in `armaturedata\*.data` (which reference the `.erf` meshes) |
| `<Name>.subsystems` | **Default loadout**: `[Engine]`, `[Armor]` (zone multipliers), `[Sensor]`, `[Torso]`, heat sinks, and the stock weapons — each weapon section names its `WeaponSubsystems\...` model, mount `Site=`, `InternalLocation=`, ammo |
| `<Name>.damage` | Damage zones per body part: armor values, splash scaling, **and the mechlab hardpoints**`OmniSlots=`/`BeamSlots=`/`MissileSlots=`/`ProjectileSlots=` per zone define what the mechlab lets you fit |
| `<Name>.engine` | Engine subsystem: heat sinks, upgrade steps (`MPSPerUpgrade`, `TonsPerUpgrade`) |
| `<Name>.torso` | Torso twist/pitch joints, speeds, radii (uses `$()` macros from [MechTorso.defines](Gameleap/mw4/Content/Defines/MechTorso.defines)) |
| `<Name>.animscript` | Animation state machine |
| `armaturedata\`, `animation\`, `armaturevideo\` | Per-part `.data`, `.mw4anim` clips, support data |
| `*.erf`, `*_DAM.erf`, `*.obb` | Geometry + collision (from Step 0) |
`$(M_<Name>)` and `$(IDS_<NAME>)` resolve because
[ProjectSpecific.include](Gameleap/mw4/Content/ProjectSpecific.include) pulls in
`Defines\MissionLang.defines` and `ShellScripts\MechLabHeaders.h` for all content parsing.
---
## Step 2 — Assign the Mech ID (three places, must match EXACTLY)
The header itself warns: *"IF YOU CHANGE THESE, YOU MUST ALSO CHANGE THE CONSTANTS IN
MWCONST.ABI!!! YOU MUST ALSO CHANGE THE CHASSIS LOOKUP TABLE IN MECH.HPP/MECH.CPP"*.
1. **[MechLabHeaders.h](Gameleap/code/mw4/Code/MW4/MechLabHeaders.h#L112)** — add
`#define M_<Name> <id>`, bump `LastMechID` and `NoMechID` (and note `M_CameraShip`/
`EmptyMechID` float above `LastMechID` automatically). This file exists as **byte-identical
copies** that must stay in sync:
- `Gameleap\code\mw4\Code\MW4\MechLabHeaders.h` (compiled into the game)
- [Gameleap/mw4/Content/ShellScripts/MechLabHeaders.h](Gameleap/mw4/Content/ShellScripts/MechLabHeaders.h)
(`#include`d by shell scripts and by content `.data` parsing)
- `Gameleap\mw4\Content\ShellScriptsDev\MechLabHeaders.h` (dev-scripts mirror)
- `Gameleap\code\mw4\Code\mw4print\MechLabHeaders.h` (the mw4print tool keeps its own copy)
2. **[mwconst.abi](Gameleap/mw4/Content/ABLScripts/mwconst.abi#L470)** — the same `M_*` constants
for the ABL scripting language (AI/bots use `GetMechType()` against these).
3. **Engine lookup tables** — Step 3 below.
---
## Step 3 — Engine code tables (rebuild required)
All in `Gameleap\code`, each marked `// MSL ADD MECH`; every array is sized by
`NoMechID`/`LastMechID+1`, so the compiler catches a missed row once the define is bumped.
There is also a runtime guard: `gosASSERT(NoMechID == (LastMechID - FirstMechID + 1))` at
[MW4Shell.cpp:14213](Gameleap/code/mw4/Code/MW4/MW4Shell.cpp#L14213).
| File | Table | Content |
|---|---|---|
| [Mech.cpp:339](Gameleap/code/mw4/Code/MW4/Mech.cpp#L339) | `Mech::s_mechSlots[NoMechID]` | **Execution slot** — memory/animation slot; new mechs *share* a slot with a similar chassis (e.g. Argus shares with Catapult). Slot enum in [MW4.hpp:228](Gameleap/code/mw4/Code/MW4/MW4.hpp#L228) |
| [mechspecs.cpp:4](Gameleap/code/mw4/Code/MW4/mechspecs.cpp#L4) | `g_aMechSpecs[NoMechID]` | `{ DNL_<NAME>, topSpeed, tonnage, IDS_ML_CH_<class>, DNL_<tech>, IDS_ML_<NAME>_INFO }` |
| [huddamage.cpp:55](Gameleap/code/mw4/Code/MW4/huddamage.cpp#L55) | `texturename[]`, `texuv[][11][4]`, `offset[][11][2]` | Cockpit damage paper-doll: texture-pool name (`hud\<name>`) + UV rects/offsets for the 11 damage zones of your HUD tga |
| [coord.cpp:2](Gameleap/code/CoreTech/Libraries/GameOS/coord.cpp#L2) | `texuv2[65][11][4]`, `offset2[65][11][2]` | Same paper-doll zones for the **target MFD** bmp (bump the `65`s) |
| [render.cpp:546](Gameleap/code/CoreTech/Libraries/GameOS/render.cpp#L546) | `mechnames[]` | Base filename list for the 2D art atlas (`hsh\mfd\<name>.bmp`) |
| [MWApplication.cpp:18447](Gameleap/code/mw4/Code/MW4/MWApplication.cpp#L18447) | `s_paMechNames[]` | Name list used by the CTCL/dedicated path |
| [GameLobby.cpp:26](Gameleap/code/mw4/Code/mw4dedicatedui/GameLobby.cpp#L26) + `DedicatedUI.hpp` | `mechlist[64][256]` etc. | Dedicated-server UI mech list sizing |
**String resources** (two DLL projects + their content-side mirrors):
| Where | Add | Feeds |
|---|---|---|
| [StringResource.rc](Gameleap/code/mw4/Code/scriptstrings/StringResource.rc) + `resource.h` (scriptstrings project) | `DNL_<NAME> "<Display Name>"`, `IDS_ML_<NAME>_INFO "<mechlab blurb>"` | `ScriptStrings.dll`; IDs mirrored in [Content/ShellScripts/ScriptStrings.h](Gameleap/mw4/Content/ShellScripts/ScriptStrings.h) for the scripts |
| [MissionLangScript.rc](Gameleap/code/mw4/Code/MissionLang/MissionLangScript.rc) + `resource.h` (MissionLang project) | `IDS_<NAME>` | `MissionLang.dll`; ID number mirrored in [MissionLang.defines](Gameleap/mw4/Content/Defines/MissionLang.defines) (used by `NameIndex=$(IDS_<NAME>)` in the `.data`) |
---
## Step 4 — Content registration (tables, build manifests, texture pool)
All in `Gameleap\mw4\Content`, all marked `// MSL ADD MECH`:
1. **[Tables/MechChassisTable.tbl](Gameleap/mw4/Content/Tables/MechChassisTable.tbl)** —
`<Display Name>=Mechs\<Name>\<Name>.data`. **Row order = Mech ID order** (parallel array;
the mechlab reads class/tech per row via `TBL_GetResNames`).
2. **[Tables/MechTable.tbl](Gameleap/mw4/Content/Tables/MechTable.tbl)** — same key →
`...\<Name>.instance`. (The `.mpt` MechPak variants of both tables are **commented out** in
`core.build` — the `.tbl`s are live; keep the `.mpt`s in sync only if reviving MekPak support.)
3. **[core.build](Gameleap/mw4/Content/core.build)** (packs `resource\core.mw4`) — the mech block:
```
//FireStorm:<Name>
data=mechs\<name>\<name>.engine
data=mechs\<name>\<name>.torso
data=mechs\<name>\<name>.data
data=mechs\<name>_destroyed\<name>_destroyed.data
armature=mechs\<name>\<name>.contents
subsystems=mechs\<name>\<name>.subsystems
damage=mechs\<name>\<name>.damage
instance=mechs\<name>\<name>.instance
```
(The `MWTable=` lines near the end of core.build already pull in the two `.tbl`s.)
4. **[textures.build](Gameleap/mw4/Content/textures.build)** (packs `resource\textures.mw4`) —
one `File=Mechs\<Name>\<part>.erf` line for **every** erf, including `_DAM` and the cage.
5. **[props.build](Gameleap/mw4/Content/props.build)** (packs `resource\props.mw4`) —
`File=Mechs\<Name>\<Name>.animscript`.
6. **[textures/textures.hint](Gameleap/mw4/Content/textures/textures.hint)** — texture-pool
entries: `[hud\<name>]` for the cockpit damage tga (put the tga in `Content\textures\HUD\`),
plus body textures and optional `[footsteps\<name>_*]` entries.
7. **`hsh\` bitmaps** — drop `hsh\Mechs\<name>.bmp`, `hsh\MFD\<name>.bmp`, `hsh\hud\<name>.bmp`
(loose files, loaded from disk at runtime — no packing step).
---
## Step 5 — Shell scripts (mechlab + lobby UI)
In [Gameleap/mw4/Content/ShellScripts/](Gameleap/mw4/Content/ShellScripts/) (and mirrored in
`ShellScriptsDev\` if you use the dev shell). These ship as text with the deployment and are
parsed at runtime — no exe rebuild, but a redeploy. **Generate the rows with
`BTFrstrm\scriptaddmech.xls`** — each sheet matches one file:
| Script | Per-mech data to extend |
|---|---|
| `MechBay\chassis.script` | `mech_num`, `class[]` (weight class per ID), tech arrays |
| `MechBay\gs_chassis.script` | `mech_num`, `create_limit` (arrays fill via `TBL_GetResNames` callback from the .tbl) |
| `MechBay\weapons.script` | `mech_num`, hardpoint label arrays (`special1_label[]`, …) and on-screen locations per zone (`head_location[]`, `leftarm_location[]`, …) |
| `MechBay\armor.script` | `mech_num`, armor-bar screen positions per zone (`head_bar[]`, …) |
| `MechBay\infobox.script` | `LAST_MECH_INFO_ID` + `temp_mech_id[]` info-string ↔ mech-ID mapping |
| `MechBay\MechBay_main.script` | `mech_name[]` (localized `DNL_*` per ID) |
| `ConLobby.script` | `MAX_ALLOWED_MECHS`, allowed-mech roster arrays, `ROOKIEMECH*` picks |
| `NetLobby.script` | `mechlist[]` / `chassis_names[]` (localized names, index-aligned) |
---
## Step 6 — Rebuild and repack
1. **Rebuild code** (VC6 — see `CLAUDE.md` STEP 3):
```
msdev Gameleap\code\mw4\Code\MechWarrior4.dsw /MAKE "MW4Application - Win32 Release" /OUT build.log
msdev Gameleap\code\mw4\Code\MechWarrior4.dsw /MAKE "MW4Application - Win32 Profile" /OUT buildpro.log
```
Release gives the shipping `MW4.exe`; **Profile gives `MW4pro.exe`, which you need because
only LAB_ONLY builds can run `-build`**. `ScriptStrings.dll` / `MissionLang.dll` rebuild as
dependencies.
2. **Repack resources** — run `build-env\build-resources.ps1` (wraps `MW4pro.exe -build`; also
handles moving DDrawCompat's `ddraw.dll` aside). The `.dep` files make it incremental;
`core.mw4`, `textures.mw4`, `props.mw4` repack because their `.build` manifests changed.
Full details: [build-env/RESOURCE-BUILD.md](build-env/RESOURCE-BUILD.md).
3. **Redeploy the game** — `build-env\deploy-mw4.ps1` (fresh `C:\VWE\firestorm\MW4` with new
exe + DLLs + repacked `resource\*.mw4` + shellscripts + `hsh\`).
4. The **editor** needs no changes (it reads the same content tree in place), but a repacked
`core.mw4` means re-running it will pick the new chassis up for missions.
## Step 7 — Verification checklist
1. Game launches (the `gosASSERT` on ID-count consistency fires immediately if a list is off).
2. Mechlab: chassis appears in the list with correct name/class/tech/tonnage; info text shows;
hardpoints match the `.damage` slot counts; default loadout matches `.subsystems`; armor
screen bars sit on the artwork.
3. Instant action: mech spawns, animates (walk/run/fall/getup), weapons fire from the right
sites, cockpit damage paper-doll regions light up correctly (the `texuv`/`offset` UVs),
targeting another instance shows the MFD image.
4. Destroyed: kill it — wreck (`_destroyed`) appears.
5. Multiplayer smoke test: name shows in lobby/roster (`NetLobby` arrays), both clients agree.
6. Editor: place the mech in a mission, `-report` runs clean.
## Quick-reference: every touch point
```
ART Content\Mechs\<Name>\ + <Name>_destroyed\ (erf/obb/armature/anim + 9 text configs)
hsh\Mechs|MFD|hud\<name>.bmp Content\textures\HUD\<name>.tga
IDS code\mw4\Code\MW4\MechLabHeaders.h Content\ShellScripts\MechLabHeaders.h
Content\ShellScriptsDev\MechLabHeaders.h code\mw4\Code\mw4print\MechLabHeaders.h
Content\ABLScripts\mwconst.abi
CODE MW4\Mech.cpp (exec slots) MW4\mechspecs.cpp (stats)
MW4\huddamage.cpp (paper-doll UVs) GameOS\coord.cpp (MFD UVs)
GameOS\render.cpp (2D art list) MW4\MWApplication.cpp (name list)
mw4dedicatedui\GameLobby.cpp|DedicatedUI.hpp
STRINGS scriptstrings\StringResource.rc|resource.h Content\ShellScripts\ScriptStrings.h
MissionLang\MissionLangScript.rc|resource.h Content\Defines\MissionLang.defines
TABLES Content\Tables\MechChassisTable.tbl Content\Tables\MechTable.tbl
MANIFESTS Content\core.build Content\textures.build
Content\props.build Content\textures\textures.hint
SCRIPTS ShellScripts\MechBay\{chassis,gs_chassis,weapons,armor,infobox,MechBay_main}.script
ShellScripts\{ConLobby,NetLobby}.script (+ ShellScriptsDev mirrors)
TOOLS BTFrstrm\scriptaddmech.xls (row generator) BTFrstrm\MechInfo_5.04.xls (design stats)
```