DOMINATION

Custom Multiplayer Maps

To author the map itself (editor tools, terrain, elevation, validation), see the Map Authoring Guide. This page covers only the multiplayer packaging, upload, and approval flow.

Custom maps work end-to-end online: the server validates and stores uploaded packages, and Unity clients automatically download, hash-verify, and install an approved custom map when a match using it starts.

Download / install flow (client)

  1. A match starts on a custom map the client does not have (or has an outdated copy of — the installed package no longer hashes to the match mapHash).
  2. The client requests { "type": "downloadMap", "mapId": "..." } and shows "Downloading map: NAME...". The server replies with mapPackage containing the exact stored package text.
  3. The client verifies sha256(packageText) equals the match mapHash (rejecting anything tampered/stale), writes the package to persistentDataPath/multiplayer_maps/<id>.package.json, and extracts the descriptor (falling back to metadata) to persistentDataPath/maps/<id>.json — the user custom-map directory MapLoader already searches.
  4. The interrupted match start resumes automatically; the usual loadingReady map-hash check then runs as for any map.

Failures (hash mismatch, malformed package, disk errors, offline) surface as clear errors in the multiplayer screen and never load an unverified map.

Include a descriptor object in the package (full MapDescriptor schema, the same JSON a built-in map file uses) — it is what the client actually loads.

Package Format

The first safe package format is a JSON .domap envelope, not an executable Unity package:

{
  "metadata": {
    "id": "desert_oil_conflict",
    "displayName": "Desert Oil Conflict",
    "version": "1.0.0",
    "creatorName": "Jesse",
    "requiredGameVersion": "0.1.0",
    "maxPlayers": 4,
    "supportedModes": ["1V1", "2V2", "FFA"],
    "w": 96,
    "h": 72,
    "spawnPoints": [
      { "id": "p1", "gx": 4, "gy": 4 },
      { "id": "p2", "gx": 80, "gy": 58 }
    ],
    "resourceNodes": [
      { "type": "ore", "gx": 12, "gy": 10, "rx": 2, "ry": 2 }
    ]
  },
  "previewPngBase64": "",
  "files": []
}

Upload protocol message:

{
  "type": "uploadMap",
  "package": { "metadata": { "...": "..." } }
}

Validation Rules

The server rejects maps with:

  • invalid or unsafe ids/names
  • incompatible game version
  • fewer spawn points than maxPlayers
  • missing resource nodes
  • missing bounds
  • oversized package
  • absolute paths
  • ../ path traversal
  • external URL-looking paths
  • executable/script/plugin extensions such as .exe, .dll, .so, .sh, .js, .py, .lua, .jar

Approved maps are stored under:

server-data/maps/approved/<map-id>/metadata.json   (registry entry)
server-data/maps/approved/<map-id>/package.json    (exact bytes served by downloadMap;
                                                    sha256 of this file IS the mapHash)

Rejected uploads are recorded under:

server-data/maps/rejected

Match Compatibility

Before match start, the server requires every lobby to use a registered map id/hash. Unity clients verify the built-in map descriptor hash before loading. Custom maps are auto-downloaded and verified against the same approved package hash before the match can start.

Known limitations

  • The lobby CREATE dropdown and the host's in-lobby map dropdown list the server's registry (custom maps suffixed "(CUSTOM)") when connected, so approved custom maps are hostable without a prior download — joiners fetch them automatically at match start.
  • Loading has a server-side timeout (LOADING_TIMEOUT_SECONDS, default 90s): a client stalled in download/loading forfeits and the match starts or ends without it.