16 · Shipping a map
Wiring a map to a gametype, and getting it onto other people’s machines.
The headers the editor does not write
Save Mission writes the object tree. It does not write the metadata comments. You add those by hand,
above the //--- OBJECT WRITE BEGIN --- marker, or the map is invisible.
// MissionTypes = CTF
// DisplayName = Slapdash
//--- MISSION QUOTE BEGIN ---
//Thunder is good, thunder is impressive; but it is lightning that does the work.
// -- Mark Twain
//--- MISSION QUOTE END ---
//--- MISSION STRING BEGIN ---
//[CTF]400 points to win
//[CTF]Flag located outside base
//Vehicle-centric mission
//High visibility
//--- MISSION STRING END ---
//--- OBJECT WRITE BEGIN ---
new SimGroup(MissionGroup) { … };
| Line | Parsed by | Consequence if missing |
|---|---|---|
// MissionTypes = CTF DM Hunters |
buildMissionList() |
The map never appears in any host menu |
// DisplayName = Slapdash |
buildMissionList() |
Menu shows the raw filename |
MISSION QUOTE block |
Loading screen | No flavour text |
MISSION STRING block |
Loading screen | No briefing; [CTF] prefix scopes a line to one gametype |
The match is on exact prefixes — "// MissionTypes = " is 18 characters, "// DisplayName = " is 17
[script]. One space either side of the =.
Because the editor rewrites everything inside the OBJECT WRITE block, put the headers above it and they survive re-saves.
Object naming conventions
Gametype code finds mission objects by name, using nameToID(). If you are authoring a map for a stock
gametype, the names are a contract.
From Slapdash.mis [script]:
Team1generatorLarge1 Team1flag1 Team1StationInventory1
Team1SensorMediumPulse1 Team1StationInventory2 Team1generatorLarge2
team1vehiclestation
with the structural grouping:
MissionGroup
├── MissionArea, Sun, TerrainBlock, NavigationGraph, Sky
├── RandomOrganics
└── Teams
├── Team1
│ ├── spawnspheres → SpawnSphere ×n
│ └── base0 → generators, flags, stations, sensors, interiors
└── Team2
The pattern is Team<N><ObjectType><Index>. Classic’s minivstationx.cs relies on it directly
[script]:
nametoid(team1vehiclestation).station.setTransform("-180.737 264.173 73.9045 0 0 -0.999913 0.0206931");
Deviate from the naming and stock gametypes will not find your objects. Build the group structure with the World Editor’s instant group (12 · World Editor) rather than fixing it afterwards.
Per-map gametype settings
MissionGroup’s own dynamic fields carry per-map configuration, prefixed by gametype [script]:
new SimGroup(MissionGroup) {
musicTrack = "lush";
CTF_scoreLimit = "4";
cdTrack = "2";
CTF_timeLimit = "25";
powerCount = "0";
Add these through the Inspector’s Dynamic Fields Add button (12 · World Editor). A map supporting several gametypes carries a set per type.
If you are writing a gametype, this is your configuration surface — read
MissionGroup.<Type>_<setting> and document the field names so mappers can set them.
cleanNonType — one file, several gametypes
loadMissionStage2 calls [script]:
MissionGroup.cleanNonType($CurrentMissionType);
which removes objects that do not belong to the current gametype. That is how one .mis supports several
types from a single object tree — tag objects for a gametype and they are stripped when a different one
loads.
What has to reach the client
This is where map distribution differs sharply from script modding. Datablocks are transmitted to clients; map files are not (Datablocks).
| File | Client needs it? |
|---|---|
.mis |
Yes — clients load their own copy |
.ter |
Yes |
.dif interiors |
Yes, if not stock |
Terrain textures, .dml |
Yes, if not stock |
<Name>_<crc>.ml lightmaps |
Yes — keyed to the mission CRC |
.nav |
Server only — bots run server-side |
.spn |
Yes |
So a custom map is a client-side download, unlike a server-side gameplay mod. Plan for it: either
distribute a package players install, or rely on the QoL patch’s enableAssetDownloads
(Packaging) — which is user-toggleable, so ship
a package regardless.
Packaging
Mirror the base layout inside your mod or a .vl2:
MyMaps/
├── missions/
│ └── CanyonRun.mis
├── terrains/
│ ├── CanyonRun.ter
│ ├── CanyonRun.nav
│ └── CanyonRun.spn
├── lighting/
│ └── CanyonRun_<crc>.ml
├── interiors/ (if custom)
└── textures/terrains/ (if custom)
Then zip it as a .vl2 (Packaging) — standard PKZIP, no
compression required.
Clear stale _<crc>.ml files first; iterating on a mission leaves a trail of dead lightmaps.
Testing checklist
| Test | Catches |
|---|---|
| Map appears in the host menu | Missing or malformed // MissionTypes = |
| Each declared gametype actually loads | Objects the gametype needs but the map lacks |
| Bots enabled | Missing or stale .nav |
| A second client connects | Missing client-side assets — the failure mode you cannot see when hosting |
| Play to a mission change | Objects leaking, or spawn logic that only works on first load |
| Walk the boundary | Undefined or wrong MissionArea |
| Check footstep audio on each surface | Terrain material sound types (14) |
| Fly to the ceiling | flightCeiling set sensibly |
The second-client test is the one people skip and the one that matters most for maps, because every asset resolution problem is invisible to the host.
Distribution notes
State in your readme:
- Which gametypes the map declares.
- Whether it needs a mod (a map for a Construction fork is not a vanilla map).
- Whether bots are supported — i.e. whether you shipped a
.nav. - Which custom assets are included, and that clients need them.
Maps for a specific mod belong with that mod. Several Construction forks ship their own map sets —
CCM’s ConFortWars series and TCCM’s TCCMBattlegrounds [mod-script] — and those maps declare
gametypes that only exist in those mods.
Related
- 10 · Mapping — the workflow this concludes
- 15 · Lighting, navigation & spawn data — generating what you ship
- Missions — the
.misformat in full - Gametypes — what a gametype expects from a map
- Packaging —
.vl2building and distribution