- 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>
17 KiB
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/ 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:
- Insert alphabetically and renumber everything after it (what MSL did between releases —
compare
MechLabHeaders_BK.hwhereM_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). - 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/):
- Geometry
.erffiles, one per body part, plus_DAM.erfdamaged variants (see Gameleap/mw4/Content/Mechs/Argus/ — note its parts are namedm3_*: 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>.animscriptstate script. - Textures registered in the texture pool (see Step 4).
2D assets (this is what the Finished HUDS from J&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).hsh\hud\<name>.bmpand radar art underhsh\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 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) |
<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 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".
- MechLabHeaders.h — add
#define M_<Name> <id>, bumpLastMechIDandNoMechID(and noteM_CameraShip/EmptyMechIDfloat aboveLastMechIDautomatically). 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
(
#included by shell scripts and by content.dataparsing) Gameleap\mw4\Content\ShellScriptsDev\MechLabHeaders.h(dev-scripts mirror)Gameleap\code\mw4\Code\mw4print\MechLabHeaders.h(the mw4print tool keeps its own copy)
- mwconst.abi — the same
M_*constants for the ABL scripting language (AI/bots useGetMechType()against these). - 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.
| File | Table | Content |
|---|---|---|
| Mech.cpp:339 | 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 |
| mechspecs.cpp:4 | g_aMechSpecs[NoMechID] |
{ DNL_<NAME>, topSpeed, tonnage, IDS_ML_CH_<class>, DNL_<tech>, IDS_ML_<NAME>_INFO } |
| huddamage.cpp:55 | 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 | texuv2[65][11][4], offset2[65][11][2] |
Same paper-doll zones for the target MFD bmp (bump the 65s) |
| render.cpp:546 | mechnames[] |
Base filename list for the 2D art atlas (hsh\mfd\<name>.bmp) |
| MWApplication.cpp:18447 | s_paMechNames[] |
Name list used by the CTCL/dedicated path |
GameLobby.cpp:26 + 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 + resource.h (scriptstrings project) |
DNL_<NAME> "<Display Name>", IDS_ML_<NAME>_INFO "<mechlab blurb>" |
ScriptStrings.dll; IDs mirrored in Content/ShellScripts/ScriptStrings.h for the scripts |
MissionLangScript.rc + resource.h (MissionLang project) |
IDS_<NAME> |
MissionLang.dll; ID number mirrored in 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:
- Tables/MechChassisTable.tbl —
<Display Name>=Mechs\<Name>\<Name>.data. Row order = Mech ID order (parallel array; the mechlab reads class/tech per row viaTBL_GetResNames). - Tables/MechTable.tbl — same key →
...\<Name>.instance. (The.mptMechPak variants of both tables are commented out incore.build— the.tbls are live; keep the.mpts in sync only if reviving MekPak support.) - core.build (packs
resource\core.mw4) — the mech block:(The//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>.instanceMWTable=lines near the end of core.build already pull in the two.tbls.) - textures.build (packs
resource\textures.mw4) — oneFile=Mechs\<Name>\<part>.erfline for every erf, including_DAMand the cage. - props.build (packs
resource\props.mw4) —File=Mechs\<Name>\<Name>.animscript. - textures/textures.hint — texture-pool
entries:
[hud\<name>]for the cockpit damage tga (put the tga inContent\textures\HUD\), plus body textures and optional[footsteps\<name>_*]entries. hsh\bitmaps — drophsh\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/ (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
- Rebuild code (VC6 — see
CLAUDE.mdSTEP 3):Release gives the shippingmsdev 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.logMW4.exe; Profile givesMW4pro.exe, which you need because only LAB_ONLY builds can run-build.ScriptStrings.dll/MissionLang.dllrebuild as dependencies. - Repack resources — run
build-env\build-resources.ps1(wrapsMW4pro.exe -build; also handles moving DDrawCompat'sddraw.dllaside). The.depfiles make it incremental;core.mw4,textures.mw4,props.mw4repack because their.buildmanifests changed. Full details: build-env/RESOURCE-BUILD.md. - Redeploy the game —
build-env\deploy-mw4.ps1(freshC:\VWE\firestorm\MW4with new exe + DLLs + repackedresource\*.mw4+ shellscripts +hsh\). - The editor needs no changes (it reads the same content tree in place), but a repacked
core.mw4means re-running it will pick the new chassis up for missions.
Step 7 — Verification checklist
- Game launches (the
gosASSERTon ID-count consistency fires immediately if a list is off). - Mechlab: chassis appears in the list with correct name/class/tech/tonnage; info text shows;
hardpoints match the
.damageslot counts; default loadout matches.subsystems; armor screen bars sit on the artwork. - 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/offsetUVs), targeting another instance shows the MFD image. - Destroyed: kill it — wreck (
_destroyed) appears. - Multiplayer smoke test: name shows in lobby/roster (
NetLobbyarrays), both clients agree. - Editor: place the mech in a mission,
-reportruns 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)