# Packaging & Shipping Once your map is built, validated, and baked, it's time to publish it to the world. Tactical Shooter uses the **PCK** (Godot Resource Pack) format — the map is packaged as a `.pck` file, uploaded to a registry server, and clients download it automatically when they join a server running that map. ## Pipeline Overview ``` ┌──────────┐ ┌──────────────┐ ┌──────────────┐ ┌────────────┐ │ pack_map │ → │ .pck + .json │ → │ Map Registry │ → │ Client │ │ .gd │ │ files │ → │ Server │ → │ Downloader │ └──────────┘ └──────────────┘ └──────────────┘ └────────────┘ (editor (artifacts) (HTTP serving) (autoload) tool) ``` ## Step 1: Package the Map The `pack_map.gd` script (at `scripts/map_packaging/pack_map.gd`) is a Godot editor tool that exports a `.tscn` scene as a standalone `.pck` resource pack. ### Usage From the **map template project** (not the main game project): ```bash cd maps/my_first_map/ godot --headless --script /path/to/scripts/map_packaging/pack_map.gd \ --map=res://de_my_map.tscn ``` > Replace `/path/to/` with the actual path to the tactical-shooter repository > root, or copy `pack_map.gd` into your map project's `scripts/` directory. ### What It Does 1. Loads the specified `.tscn` scene 2. Recursively collects all dependencies (materials, textures, meshes, etc.) 3. Exports them to `user://packed_maps/de_my_map.pck` 4. Writes a metadata file `user://packed_maps/de_my_map.json` ### Output Files ``` user://packed_maps/ ├── de_my_map.pck # Binary resource pack (~1–10 MB for a typical map) └── de_my_map.json # JSON metadata (machine-readable) ``` **Metadata example** (`de_my_map.json`): ```json { "map_name": "de_my_map", "source_scene": "res://de_my_map.tscn", "godot_version": { "major": 4, "minor": 2, "string": "4.2" }, "packed_at": "2026-06-25T14:30:00" } ``` ### Requirements - The map scene must use **local resources** only (relative `res://` paths) - External dependencies (game shared assets like weapon models, player models) should stay in the base game — the `.pck` is **additive** content - The `.pck` contains only the map's unique assets, not the entire game ### From the Editor You can also run `pack_map.gd` from the Godot editor: 1. Open your map project in Godot 2. Open your map scene (`de_my_map.tscn`) 3. **Project → Tools → Pack Current Map** (if configured) 4. The script detects the active scene and exports it ## Step 2: Set Up the Map Registry Server The registry server is a lightweight Python HTTP server (`scripts/map_packaging/map_registry_server.py`) that: - Serves `.pck` files for download - Provides a JSON `/maps` endpoint listing available maps - Auto-scans the maps directory every 5 seconds - Computes and serves SHA-256 checksums for integrity verification ### Starting the Server ```bash cd /path/to/tactical-shooter # Default: port 8090, maps from ./packed_maps/ python3 scripts/map_packaging/map_registry_server.py # Custom configuration python3 scripts/map_packaging/map_registry_server.py \ --port 8080 \ --maps-dir /data/tactical-shooter-maps \ --verbose ``` **Environment variables:** | Variable | Overrides | Default | |----------|-----------|---------| | `MAP_REGISTRY_PORT` | `--port` / `-p` | 8090 | | `MAP_REGISTRY_MAPS` | `--maps-dir` / `-d` | `./packed_maps/` | ### API Endpoints | Endpoint | Method | Description | |----------|--------|-------------| | `GET /` | — | Server info and endpoint documentation | | `GET /maps` | — | JSON list of all available maps with metadata | | `GET /maps/.pck` | — | Download map `.pck` binary | | `GET /maps/.json` | — | Download per-map metadata | | `OPTIONS /maps` | — | CORS preflight | **Example responses:** ``` GET /maps ``` ```json { "maps": [ { "name": "de_dust2", "size": 5432100, "version": 1, "description": "Classic competitive map", "checksum_sha256": "a1b2c3d4...", "packed_at": "2026-06-25T14:30:00" } ], "server_name": "Tactical Shooter Map Registry", "server_version": "1.0.0", "map_count": 1 } ``` ``` GET /maps/de_dust2.pck ``` → Returns the `.pck` binary file as an octet-stream download. ### Deployment Options - **Local dev:** Run on the same machine as the game (default `127.0.0.1:8090`) - **Server host:** Run on a public server alongside the master server - **Docker:** Wrap in a Docker container for easy deployment - **Systemd service:** Create a service file for production hosting **Systemd service example:** ```ini [Unit] Description=Tactical Shooter Map Registry After=network.target [Service] Type=simple User=tactical WorkingDirectory=/opt/tactical-shooter ExecStart=/usr/bin/python3 scripts/map_packaging/map_registry_server.py \ --port 8090 --maps-dir /data/tactical-maps Restart=on-failure [Install] WantedBy=multi-user.target ``` ## Step 3: Upload Your Map 1. Copy the `.pck` and `.json` files to the registry server's maps directory: ```bash cp user://packed_maps/de_my_map.pck /data/tactical-maps/ cp user://packed_maps/de_my_map.json /data/tactical-maps/ ``` 2. The server auto-detects new files within 5 seconds 3. Verify via `curl http://your-server:8090/maps` ## Step 4: Client Downloads The client uses `MapDownloader` (autoload at `client/scripts/map_downloader.gd`) to fetch and load maps at runtime. ### How It Works 1. Player joins a server that specifies a map name 2. The client checks its local cache (`user://maps/manifest.json`) 3. If the map is not cached, `MapDownloader.fetch_map_list()` polls the registry server to verify the map exists 4. `MapDownloader.download_map(name)` downloads the `.pck` to `user://maps/` 5. `ProjectSettings.load_resource_pack()` loads it into the game 6. The server changes to the new level ### Map Caching ``` user://maps/ ├── de_dust2.pck # Cached map binary ├── de_inferno.pck # Another cached map └── manifest.json # Cache index ``` **Cache behaviour:** - Maps are cached to disk after download (survives restarts) - The manifest tracks version numbers for staleness checks - `MapDownloader.remove_map(name)` clears a specific map - `MapDownloader.clear_cache()` removes all cached maps ### Configuration The `MapDownloader` autoload can be configured in-game: ```gdscript # Set the registry server URL (override default http://127.0.0.1:8090) MapDownloader.registry_url = "http://maps.myserver.com:8090" ``` The URL can also be set via the `MAP_REGISTRY_URL` environment variable, which is read in `_ready()`. ### Signals ```gdscript # Register for map lifecycle events MapDownloader.map_list_loaded.connect(_on_map_list) MapDownloader.map_download_progress.connect(_on_download_progress) MapDownloader.map_download_complete.connect(_on_download_done) MapDownloader.map_loaded.connect(_on_map_loaded) ``` ## Full Pipeline Example ```bash # 1. Package from the template project cd maps/my_first_map godot --headless --script ../../scripts/map_packaging/pack_map.gd \ --map=res://de_my_map.tscn # 2. Start registry server cd ../.. python3 scripts/map_packaging/map_registry_server.py --port 8090 & # 3. Deploy map cp ~/.local/share/godot/app_userdata/MapTemplate/packed_maps/de_my_map.pck \ scripts/map_packaging/packed_maps/ cp ~/.local/share/godot/app_userdata/MapTemplate/packed_maps/de_my_map.json \ scripts/map_packaging/packed_maps/ # 4. Verify curl -s http://localhost:8090/maps | python3 -m json.tool # 5. In-game: connect to server and play the map ``` ## Next Steps - [FAQ & Troubleshooting](06-faq-and-troubleshooting.md) — common issues