# New Map Import — Arkania Pilot Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Import one pilot map (Arkania, map number 83) end-to-end into OpenMU server, the PC MUnique client, and make it visible/editable in the web admin panel. **Architecture:** OpenMU-native map: a C# `BaseMapInitializer` subclass registers map number 83 into `GameConfiguration.Maps`; its terrain comes from an embedded `.att` resource; an update-plugin applies the same to an existing DB. The map is seeded EMPTY (no monsters/NPCs) plus one spawn gate and a `/move` warp for reachability. The web admin panel needs no code changes — the map appears automatically once in the DB. PC client gets the `World83`/`Object83` asset folders. **Tech Stack:** C# (.NET), OpenMU `Persistence.Initialization`, Docker build, MUnique MuMain PC client (Data folder assets). ## Global Constraints - Server repo: `AdamuSw` at `D:\OpenMU\MU Client_Mobile 1.04d - Season 6E3\AdamuSw`, work on branch `feature/new-maps-import` (already created). - Pilot map: **Arkania**, **map number 83** (client `World83`). Confirm the name against the pack screenshots before finalizing; if it turns out World83 is a different map, change only the `Name`/class name. - **Terrain resource naming is OFF-BY-ONE:** OpenMU map number `N` loads embedded resource `Resources/Terrain{N+1}.att`. For Arkania (83) that is **`Terrain84.att`**, whose CONTENT is the pack's `Terrain83.att` (world-83 attributes). Verified against Aida (33→Terrain34.att), Vulcanus (63→Terrain64.att). - Terrain resources are NOT globbed by the csproj — each is listed explicitly (`` + ``). New `.att` MUST be added to both lists. - Update-plugins are applied to existing DBs ONLY via AdminPanel → Updates (`DataUpdateService`); never call `SaveChanges` inside a plugin. Fresh/zero DB auto-seeds via the map registration. - Server build is via Docker: `docker build -f Startup/Dockerfile -t adamu-openmu:dev .` from the `AdamuSw` root. Local `dotnet build` is expected to fail at the source-generator pre-step (known, per the Imperial-items work) — use the Docker build as the compile gate. - Pack source: `C:\Users\efpa\Downloads\Arkania Acheron Debenter Uruk Ferea\Arkania Acheron Debenter Uruk Ferea`. - PC client Data root: `D:\AdaMu\AdaMu\Data`. --- ### Task 1: Add the Arkania terrain resource **Files:** - Create: `AdamuSw/src/Persistence/Initialization/Resources/Terrain84.att` (copied from pack `Server Side/Data/Terrain/Terrain83.att`) - Modify: `AdamuSw/src/Persistence/Initialization/MUnique.OpenMU.Persistence.Initialization.csproj` (two lines: a `` and an ``) **Interfaces:** - Produces: embedded manifest resource `MUnique.OpenMU.Persistence.Initialization.Resources.Terrain84.att`, consumed by `TerrainUpdateHelper.UpdateTerrainFromResources` for map number 83. - [ ] **Step 1: Copy the terrain file (pack Terrain83.att → resource Terrain84.att)** Run (Git Bash): ```bash SRC="/c/Users/efpa/Downloads/Arkania Acheron Debenter Uruk Ferea/Arkania Acheron Debenter Uruk Ferea/Server Side/Data/Terrain/Terrain83.att" DST="/d/OpenMU/MU Client_Mobile 1.04d - Season 6E3/AdamuSw/src/Persistence/Initialization/Resources/Terrain84.att" cp -f "$SRC" "$DST" stat -c '%s %n' "$DST" ``` Expected: prints `65539 .../Terrain84.att` (matches OpenMU's other `.att` sizes). - [ ] **Step 2: Verify the header matches OpenMU's terrain format** Run: ```bash xxd "/d/OpenMU/MU Client_Mobile 1.04d - Season 6E3/AdamuSw/src/Persistence/Initialization/Resources/Terrain84.att" | head -1 xxd "/d/OpenMU/MU Client_Mobile 1.04d - Season 6E3/AdamuSw/src/Persistence/Initialization/Resources/Terrain1.att" | head -1 ``` Expected: both start with `00ff ff04` (same 3-byte header family). If Terrain84.att differs structurally, STOP — the pack file may be a different format. - [ ] **Step 3: Register the resource in the csproj** In `MUnique.OpenMU.Persistence.Initialization.csproj`, add next to the existing `Terrain*.att` entries: In the `` group (near ``): ```xml ``` In the `` group (near ``): ```xml ``` - [ ] **Step 4: Verify both csproj lines are present** Run: ```bash grep -n "Terrain84.att" "/d/OpenMU/MU Client_Mobile 1.04d - Season 6E3/AdamuSw/src/Persistence/Initialization/MUnique.OpenMU.Persistence.Initialization.csproj" ``` Expected: exactly two lines — one `` and one ``. - [ ] **Step 5: Commit** ```bash cd "/d/OpenMU/MU Client_Mobile 1.04d - Season 6E3/AdamuSw" git add src/Persistence/Initialization/Resources/Terrain84.att src/Persistence/Initialization/MUnique.OpenMU.Persistence.Initialization.csproj git commit -m "feat(maps): add Arkania (map 83) terrain resource Terrain84.att" ``` --- ### Task 2: Create the Arkania map initializer and register it **Files:** - Create: `AdamuSw/src/Persistence/Initialization/VersionSeasonSix/Maps/Arkania.cs` - Modify: `AdamuSw/src/Persistence/Initialization/VersionSeasonSix/GameMapsInitializer.cs` (add one `yield return`) **Interfaces:** - Produces: `internal class Arkania : BaseMapInitializer` with `internal const byte Number = 83;` and `internal const string Name = "Arkania";`. Consumed by `GameMapsInitializer` (fresh seed) and the update-plugin (Task 3). - [ ] **Step 1: Create the map class** Create `Arkania.cs` (mirrors the `Aida.cs` pattern, but with no monster/NPC spawns — those are added later via the web panel): ```csharp // // Licensed under the MIT License. See LICENSE file in the project root for full license information. // namespace MUnique.OpenMU.Persistence.Initialization.VersionSeasonSix.Maps; using MUnique.OpenMU.DataModel.Configuration; /// /// The initialization for the Arkania map (imported map, world 83). /// Seeded empty; monster/NPC spawns and gates are added via the admin panel. /// internal class Arkania : BaseMapInitializer { /// /// The Number of the Map. /// internal const byte Number = 83; /// /// The Name of the Map. /// internal const string Name = "Arkania"; /// /// Initializes a new instance of the class. /// /// The context. /// The game configuration. public Arkania(IContext context, GameConfiguration gameConfiguration) : base(context, gameConfiguration) { } /// protected override byte MapNumber => Number; /// protected override string MapName => Name; } ``` - [ ] **Step 2: Register the map in GameMapsInitializer** In `VersionSeasonSix/GameMapsInitializer.cs`, in `MapInitializerTypes`, add after the last existing `yield return typeof(...);` line: ```csharp yield return typeof(Arkania); ``` - [ ] **Step 3: Verify class + registration compile-consistency (textual)** Run: ```bash grep -n "class Arkania : BaseMapInitializer" "/d/OpenMU/MU Client_Mobile 1.04d - Season 6E3/AdamuSw/src/Persistence/Initialization/VersionSeasonSix/Maps/Arkania.cs" grep -n "typeof(Arkania)" "/d/OpenMU/MU Client_Mobile 1.04d - Season 6E3/AdamuSw/src/Persistence/Initialization/VersionSeasonSix/GameMapsInitializer.cs" ``` Expected: one match each. - [ ] **Step 4: Commit** ```bash cd "/d/OpenMU/MU Client_Mobile 1.04d - Season 6E3/AdamuSw" git add src/Persistence/Initialization/VersionSeasonSix/Maps/Arkania.cs src/Persistence/Initialization/VersionSeasonSix/GameMapsInitializer.cs git commit -m "feat(maps): add Arkania map initializer (number 83) and register it" ``` --- ### Task 3: Add spawn gate + `/move` warp, and the DB update-plugin **Files:** - Modify: `AdamuSw/src/Persistence/Initialization/Updates/UpdateVersion.cs` (add enum value) - Create: `AdamuSw/src/Persistence/Initialization/Updates/AddArkaniaMapUpdateSeason6.cs` **Interfaces:** - Consumes: `Arkania.Number` (83) and `Arkania.Name` ("Arkania") from Task 2; the `Terrain84.att` resource from Task 1. - Produces: update-plugin `AddArkaniaMapUpdateSeason6` with `Version => UpdateVersion.AddArkaniaMapSeason6`. - [ ] **Step 1: Compute a walkable spawn coordinate from the terrain** The spawn gate must sit on a walkable tile. Scan the pack `Terrain83.att` (3-byte header + 256×256 attribute bytes; a tile is walkable when its attribute byte has none of the blocking bits `0x04` NOMOVE / `0x08` NOGROUND set — i.e. value `0x00` or `0x01` safezone). Run: ```bash python - <<'PY' p="/c/Users/efpa/Downloads/Arkania Acheron Debenter Uruk Ferea/Arkania Acheron Debenter Uruk Ferea/Server Side/Data/Terrain/Terrain83.att" d=open(p,'rb').read()[3:] # drop 3-byte header # attribute[y*256 + x]; find a walkable tile nearest the center best=None for y in range(256): for x in range(256): a=d[y*256+x] if a & 0x04 or a & 0x08: # NOMOVE or NOGROUND -> blocked continue dist=abs(x-128)+abs(y-128) if best is None or dist /// Adds the imported Arkania map (number 83). /// AddArkaniaMapSeason6 = 96, ``` - [ ] **Step 3: Create the update-plugin** Create `Updates/AddArkaniaMapUpdateSeason6.cs` (mirrors `AddImperialWeapons050UpdateSeason6.cs` for the boilerplate and `AddLorenMarketJuliaWarpPlugIn.cs` for the inline gate/warp). Replace `SPAWN_X`/`SPAWN_Y` with the values from Step 1: ```csharp // // Licensed under the MIT License. See LICENSE file in the project root for full license information. // namespace MUnique.OpenMU.Persistence.Initialization.Updates; using System.Runtime.InteropServices; using MUnique.OpenMU.DataModel.Configuration; using MUnique.OpenMU.Persistence.Initialization.VersionSeasonSix.Maps; using MUnique.OpenMU.PlugIns; /// /// Adds the imported Arkania map (number 83): the map definition with its terrain, /// a spawn gate on a walkable tile, and a /move warp entry so players can reach it. /// Monsters and NPCs are added afterwards via the admin panel. /// [PlugIn] [Display(Name = PlugInName, Description = PlugInDescription)] [Guid("2C8B0A61-6B2E-4D7A-9E3C-7F1A4B2D9E01")] public class AddArkaniaMapUpdateSeason6 : UpdatePlugInBase { internal const string PlugInName = "Add Arkania map (83)"; internal const string PlugInDescription = "Adds the imported Arkania map (number 83) with terrain, a spawn gate, and a /move warp. Monsters/NPCs are added via the admin panel."; /// public override UpdateVersion Version => UpdateVersion.AddArkaniaMapSeason6; /// public override string DataInitializationKey => VersionSeasonSix.DataInitialization.Id; /// public override string Name => PlugInName; /// public override string Description => PlugInDescription; /// public override bool IsMandatory => true; /// public override DateTime CreatedAt => new(2026, 07, 19, 12, 0, 0, DateTimeKind.Utc); /// protected override ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration) { // Idempotency guard: skip if Arkania (83) already exists. if (gameConfiguration.Maps.Any(m => m.Number == Arkania.Number)) { return default; } var initializer = new Arkania(context, gameConfiguration); initializer.Initialize(); // creates map 83 + loads terrain from Terrain84.att initializer.SetSafezoneMap(); var arkania = gameConfiguration.Maps.First(m => m.Number == Arkania.Number); // Spawn gate on a walkable tile (coordinates from the terrain scan). var spawnGate = context.CreateNew(); arkania.ExitGates.Add(spawnGate); spawnGate.Map = arkania; spawnGate.X1 = SPAWN_X; spawnGate.Y1 = SPAWN_Y; spawnGate.X2 = SPAWN_X; spawnGate.Y2 = SPAWN_Y; spawnGate.IsSpawnGate = true; // /move warp entry. if (gameConfiguration.WarpList.All(w => w.Name != Arkania.Name)) { var warp = context.CreateNew(); warp.Index = 83; warp.Name = Arkania.Name; warp.Costs = 5000; warp.LevelRequirement = 10; warp.Gate = spawnGate; } return default; } } ``` - [ ] **Step 4: Verify enum + plugin references are consistent (textual)** Run: ```bash grep -n "AddArkaniaMapSeason6 = 96" "/d/OpenMU/MU Client_Mobile 1.04d - Season 6E3/AdamuSw/src/Persistence/Initialization/Updates/UpdateVersion.cs" grep -n "UpdateVersion.AddArkaniaMapSeason6" "/d/OpenMU/MU Client_Mobile 1.04d - Season 6E3/AdamuSw/src/Persistence/Initialization/Updates/AddArkaniaMapUpdateSeason6.cs" grep -cn "SPAWN_X\|SPAWN_Y" "/d/OpenMU/MU Client_Mobile 1.04d - Season 6E3/AdamuSw/src/Persistence/Initialization/Updates/AddArkaniaMapUpdateSeason6.cs" ``` Expected: first two match; the third prints `0` (all `SPAWN_X`/`SPAWN_Y` placeholders were replaced with numbers). If it prints non-zero, replace the remaining placeholders. - [ ] **Step 5: Commit** ```bash cd "/d/OpenMU/MU Client_Mobile 1.04d - Season 6E3/AdamuSw" git add src/Persistence/Initialization/Updates/UpdateVersion.cs src/Persistence/Initialization/Updates/AddArkaniaMapUpdateSeason6.cs git commit -m "feat(maps): add Arkania DB update-plugin with spawn gate and /move warp" ``` --- ### Task 4: Build the server image and verify map 83 is in the configuration **Files:** none (build + verification only). - [ ] **Step 1: Docker build** Run (from AdamuSw root): ```bash cd "/d/OpenMU/MU Client_Mobile 1.04d - Season 6E3/AdamuSw" docker build -f Startup/Dockerfile -t adamu-openmu:dev . 2>&1 | tail -20 ``` Expected: `Successfully tagged adamu-openmu:dev` (or the buildkit equivalent). If compilation fails, fix the reported C# error and rebuild. - [ ] **Step 2: Verify the map symbol is compiled into the initialization DLL** Run: ```bash docker run --rm --entrypoint sh adamu-openmu:dev -c "grep -a -c Arkania /app/MUnique.OpenMU.Persistence.Initialization.dll || true" ``` Expected: a non-zero count (the `Arkania`/`AddArkaniaMap` strings are present in the built DLL). If `0`, the class was not compiled in — recheck Task 2/3. - [ ] **Step 3: Commit (no code; checkpoint only if any fixes were made)** Only if Steps 1-2 required code fixes: ```bash cd "/d/OpenMU/MU Client_Mobile 1.04d - Season 6E3/AdamuSw" git commit -am "fix(maps): resolve Arkania build errors" ``` --- ### Task 5: Deploy the server and apply the update on the existing DB **Files:** none (deploy + admin action). - [ ] **Step 1: Bring the server up with the new image** Run: ```bash cd "/d/OpenMU/MU Client_Mobile 1.04d - Season 6E3/AdamuSw" docker compose -f deploy-adamu/docker-compose.local.yml up -d ``` Expected: containers start (game server + admin panel). Note: if the server points at the remote `65.109.224.204` DB instead of local, deploy the image to that host per the established deploy flow instead. - [ ] **Step 2: Apply the update via AdminPanel → Updates** Open the AdminPanel (`http://localhost:8081` locally, or the server's admin URL), go to the **Updates** page, and apply **"Add Arkania map (83)"**. (Fresh/zero DBs already have it from the seed and will show nothing to apply.) Expected: the update applies without error and disappears from the pending list. - [ ] **Step 3: Verify the map exists in the panel** In the AdminPanel, open `/edit-config-grid/GameMapDefinition` (Config → Game Maps). Expected: **Arkania** appears in the list with number 83. - [ ] **Step 4: Verify terrain renders in the visual editor** Open `/map-editor`, select **Arkania**. Expected: the terrain image renders (not blank). Blank terrain ⇒ the `Terrain84.att` resource didn't load — recheck Task 1 (csproj embedding + off-by-one name). --- ### Task 6: Copy the client assets to the PC client **Files:** - Copy into `D:\AdaMu\AdaMu\Data`: `World83\`, `Object83\`, and the `Local\Eng` additions. **Interfaces:** - Consumes: pack `Client Side/Data/{World83,Object83,Local/Eng}`. - Produces: renderable client-side assets for world index 83. - [ ] **Step 1: Copy World83 and Object83** Run: ```bash PACK="/c/Users/efpa/Downloads/Arkania Acheron Debenter Uruk Ferea/Arkania Acheron Debenter Uruk Ferea/Client Side/Data" DST="/d/AdaMu/AdaMu/Data" cp -rf "$PACK/World83" "$DST/World83" cp -rf "$PACK/Object83" "$DST/Object83" ls "$DST/World83/EncTerrain83.att" "$DST/Object83" >/dev/null && echo "World83+Object83 copied" ``` Expected: prints `World83+Object83 copied`. - [ ] **Step 2: Copy the map-name image and minimap for world 83** Run: ```bash PACK="/c/Users/efpa/Downloads/Arkania Acheron Debenter Uruk Ferea/Arkania Acheron Debenter Uruk Ferea/Client Side/Data/Local/Eng" DST="/d/AdaMu/AdaMu/Data/Local/Eng" [ -f "$PACK/ImgsMapName/Arkania.OZT" ] && cp -f "$PACK/ImgsMapName/Arkania.OZT" "$DST/ImgsMapName/" && echo "map-name image copied" ls "$PACK/Minimap/" | grep -i "World83" | while read f; do cp -f "$PACK/Minimap/$f" "$DST/Minimap/"; echo "minimap $f copied"; done ``` Expected: the Arkania name image copies; a `Minimap_World83_eng.bmd` copies if present (absence is non-fatal — minimap only). - [ ] **Step 3: Verify no case-mismatch on referenced object textures** The MUnique PC client on Windows is case-insensitive, so a spot check suffices. Run: ```bash ls "/d/AdaMu/AdaMu/Data/World83" | head; echo "---"; ls "/d/AdaMu/AdaMu/Data/Object83" | wc -l ``` Expected: World83 lists `EncTerrain83.*` + tiles; Object83 has a non-zero file count. _(No commit — `D:\AdaMu\AdaMu` is the deployed client folder, not a git repo.)_ --- ### Task 7: Verify in the PC client **Files:** none (runtime verification). May require a `vendor/MuMain` change + rebuild if the engine caps the world index. - [ ] **Step 1: Launch the client and reach Arkania** Start `D:\AdaMu\AdaMu\main.exe`, log in to a character (server `65.109.224.204`), and issue `/move Arkania` in chat. Expected: the character warps to Arkania; terrain and objects render; no `OpenTexture Failed` / crash; the character can walk. - [ ] **Step 2: If the map does not load (world-index unsupported by the engine)** If the client crashes or shows a blank/again-Lorencia world on entering 83, the MUnique engine may not handle world index 83. Check the terrain/world-loading path in `vendor/MuMain/src/source` for a hard world-index cap or a per-index table, add support for 83, and rebuild `Main.exe` (see the `pc-munique-client-build` memory: `vcvarsall x86` + vswhere on PATH + `cmake --build out/build/windows-x86-fresh --config Release`), then redeploy `main.exe` to `D:\AdaMu\AdaMu`. Re-run Step 1. - [ ] **Step 3: Final confirmation** Confirm all three surfaces: - Server/panel: Arkania (83) listed, terrain renders in `/map-editor`. - Client: `/move Arkania` enters a walkable, correctly-rendered map. - Then the user adds monster/NPC spawns via the panel and confirms they appear live. Pilot complete. Follow-up (separate plan): replicate Tasks 1-7 for maps 84-87 (Acheron/Debenter/Uruk/Ferea) — each terrain resource is `Terrain{N+1}.att` from the pack's `Terrain{N}.att` — plus mobile-client asset import. ## Self-Review notes - **Spec coverage:** terrain resource (Task 1), map class + registration (Task 2), update-plugin + warp/gate (Task 3), build/verify (Task 4), deploy/apply/panel-verify (Task 5), client assets (Task 6), client verify (Task 7). All spec sections covered. - **Off-by-one terrain naming** is captured as a Global Constraint and applied in Task 1 (resource `Terrain84.att` ← pack `Terrain83.att`). - **No monster spawns in the seed** — intentional (user adds via panel), matching the spec. - **Type consistency:** `Arkania.Number` (83) and `Arkania.Name` ("Arkania") defined in Task 2 are the exact symbols consumed in Task 3; `UpdateVersion.AddArkaniaMapSeason6` (=96) defined and consumed consistently.