From 7ff1e12f2ad9ce1309aabb29cbf0114392673f16 Mon Sep 17 00:00:00 2001 From: Acentech Dev Date: Tue, 14 Jul 2026 21:32:13 +0300 Subject: [PATCH] docs: Phase 0b plan (remote-NPC port with ADAMU-CUSTOM markers + regression test) Co-Authored-By: Claude Opus 4.8 (1M context) --- .../2026-07-14-phase0b-remote-npc-port.md | 262 ++++++++++++++++++ 1 file changed, 262 insertions(+) create mode 100644 docs/superpowers/plans/2026-07-14-phase0b-remote-npc-port.md diff --git a/docs/superpowers/plans/2026-07-14-phase0b-remote-npc-port.md b/docs/superpowers/plans/2026-07-14-phase0b-remote-npc-port.md new file mode 100644 index 0000000..87f6a7c --- /dev/null +++ b/docs/superpowers/plans/2026-07-14-phase0b-remote-npc-port.md @@ -0,0 +1,262 @@ +# Faz 0b — Remote-NPC Portu — Uygulama 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:** Eski AdaMu fork'undaki "her yerden NPC aç" (mobile NPC-list) özelliğini güncel upstream (AdamuSw) üstüne temiz, `// ADAMU-CUSTOM` işaretli ve regresyon-testli olarak taşımak; kendi image'i yeniden derleyip özelliğin çalıştığını doğrulamak. + +**Architecture:** Özellik 3 dosyaya dokunur: 2 additive metod + 1 handler dalı. Client, `TalkToNpcRequest` paketinde NPC id'sinin yüksek bitini (`0x8000`) marker olarak set eder; set olduğunda alt 15 bit bir **NPC definition number**'dır ve o NPC'nin penceresi (shop/chaos/vault) konumdan bağımsız açılır. Object id'leri her zaman `<= 0x7FFF` (GameMap `IdGenerator` üst sınırı `0x7FFF`), o yüzden yüksek bit güvenli marker. + +**Tech Stack:** .NET 10, C#, NUnit + Moq (tests/MUnique.OpenMU.Tests), Docker. + +## Global Constraints + +- Depo: `d:/OpenMU/MU Client_Mobile 1.04d - Season 6E3/AdamuSw` (bash'te tırnak içinde). +- Her core dokunuşu `// ADAMU-CUSTOM: ` ... `// ADAMU-CUSTOM end` ile sarılır (merge güvenliği). +- Lokal derleme daima `-p:ci=true` (source-gen prebuild'i atlar). +- Commit yazarı: `Acentech Dev `. +- Kaynak referans (kanıtlanmış eski kod): `d:/OpenMU/src` (AdaMu fork). + +--- + +### Task 1: 3 kaynak değişikliğini uygula (ADAMU-CUSTOM işaretli) + +**Files:** +- Modify: `src/GameLogic/GameMap.cs` (GetObject metodundan sonra, ~satır 104) +- Modify: `src/GameLogic/PlayerActions/TalkNpcAction.cs` (TalkToNpcAsync metodundan sonra, ~satır 58) +- Modify: `src/GameServer/MessageHandler/TalkNpcHandlerPlugInBase.cs` (HandlePacketAsync içi, ~satır 31) + +**Interfaces:** +- Produces: `GameMap.GetNpcByNumber(short) : NonPlayerCharacter?`, `TalkNpcAction.TalkToNpcByNumberAsync(Player, short) : ValueTask`, ve handler'da `0x8000` yönlendirmesi. Task 2 (test) `GetNpcByNumber`'a güvenir. + +- [ ] **Step 1: GameMap.cs — `GetNpcByNumber` ekle** + +`GetObject` metodunun kapanış `}`'inden hemen sonra ekle: + +```csharp + // ADAMU-CUSTOM: remote NPC ("NPC list" mobile feature) — spawn olmuş NPC'yi definition number ile bul. + /// + /// Gets a spawned non-player-character on this map by its definition number. + /// Used to open an NPC's window remotely (mobile "NPC list" feature), independent + /// of the player's position. + /// + /// The . + /// The first matching non-player-character, or null. + public NonPlayerCharacter? GetNpcByNumber(short number) + { + return this._objectsInMap.Values + .OfType() + .FirstOrDefault(npc => npc.Definition.Number == number); + } + + // ADAMU-CUSTOM end +``` + +`using` kontrolü: dosyanın başında `using System.Linq;` ve `using MUnique.OpenMU.GameLogic.NPC;` olmalı. Yoksa ekle (GameMap zaten NPC tipleriyle çalışır, muhtemelen vardır). + +- [ ] **Step 2: TalkNpcAction.cs — `TalkToNpcByNumberAsync` ekle** + +`TalkToNpcAsync` metodunun kapanış `}`'inden hemen sonra (AdvancePlayerState'ten önce) ekle: + +```csharp + // ADAMU-CUSTOM: remote NPC — NPC'ye definition number ile her yerden konuş (mobile "NPC list"). + /// + /// Talks to an NPC identified by its definition , regardless of the + /// player's current position or map (no proximity required). Used by the mobile "NPC list" to + /// open a merchant store / chaos machine / vault from anywhere. Loads the NPC's home map if it + /// isn't loaded yet, then reuses the normal flow. + /// + /// The player. + /// The NPC definition number. + public async ValueTask TalkToNpcByNumberAsync(Player player, short npcNumber) + { + var gameContext = player.GameContext; + + // NPC'nin spawn olduğu haritayı bul, (gerekiyorsa) yükle ve instance'ı al. + foreach (var mapDef in gameContext.Configuration.Maps) + { + if (mapDef.MonsterSpawns is null + || !mapDef.MonsterSpawns.Any(s => s.MonsterDefinition?.Number == npcNumber)) + { + continue; + } + + var map = await gameContext.GetMapAsync((ushort)mapDef.Number).ConfigureAwait(false); + if (map?.GetNpcByNumber(npcNumber) is { } npc) + { + await this.TalkToNpcAsync(player, npc).ConfigureAwait(false); + return; + } + } + } + + // ADAMU-CUSTOM end +``` + +`using System.Linq;` gerekli (Any). Dosyada yoksa ekle. + +- [ ] **Step 3: TalkNpcHandlerPlugInBase.cs — `0x8000` dalını ekle** + +`HandlePacketAsync` metodunu şu şekilde değiştir (mevcut gövde `TalkToNpcRequest message = packet;` + `if (player.CurrentMap?...)`): + +```csharp + public async ValueTask HandlePacketAsync(Player player, Memory packet) + { + TalkToNpcRequest message = packet; + + // ADAMU-CUSTOM: remote NPC ("NPC list" mobile feature). Object id'leri her zaman <= 0x7FFF + // (GameMap IdGenerator), o yüzden yüksek bit serbest marker. Set edilmişse alt 15 bit bir + // NPC *definition number*'dır — o NPC'nin penceresini konum/harita bağımsız aç. + if ((message.NpcId & 0x8000) != 0) + { + await this.TalkNpcAction.TalkToNpcByNumberAsync(player, (short)(message.NpcId & 0x7FFF)).ConfigureAwait(false); + return; + } + // ADAMU-CUSTOM end + + if (player.CurrentMap?.GetObject(message.NpcId) is NonPlayerCharacter npc) + { + await this.TalkNpcAction.TalkToNpcAsync(player, npc).ConfigureAwait(false); + } + } +``` + +- [ ] **Step 4: Derlenmeyi doğrula** + +```bash +cd "/d/OpenMU/MU Client_Mobile 1.04d - Season 6E3/AdamuSw/src" && \ +dotnet build Startup/MUnique.OpenMU.Startup.csproj -c Release -p:ci=true --nologo -clp:ErrorsOnly 2>&1 | tail -12 +``` +Expected: `Oluşturma başarılı oldu` / `Build succeeded` (0 hata). + +- [ ] **Step 5: Commit** + +```bash +cd "/d/OpenMU/MU Client_Mobile 1.04d - Season 6E3/AdamuSw" && \ +git add src/GameLogic/GameMap.cs src/GameLogic/PlayerActions/TalkNpcAction.cs src/GameServer/MessageHandler/TalkNpcHandlerPlugInBase.cs && \ +git -c user.name="Acentech Dev" -c user.email="acentech_dev@affinitybox.com" \ + commit -m "feat(ADAMU-CUSTOM): port remote-NPC (open NPC window from anywhere via 0x8000 marker)" +``` + +--- + +### Task 2: `GetNpcByNumber` regresyon testi + +**Files:** +- Modify: `tests/MUnique.OpenMU.Tests/GameMapTest.cs` + +**Interfaces:** +- Consumes: `GameMap.GetNpcByNumber` (Task 1) + +- [ ] **Step 1: Testi ekle** + +`GameMapTest` sınıfının içine (mevcut testlerin yanına) ekle: + +```csharp + /// + /// ADAMU-CUSTOM: remote-NPC — GetNpcByNumber, definition number ile spawn olmuş NPC'yi bulur; + /// bilinmeyen number için null döner. (upstream merge bu özelliği bozarsa bu test kırılır.) + /// + [Test] + public async ValueTask GetNpcByNumberFindsSpawnedNpcAsync() + { + var map = new GameMap(new GameMapDefinition(), TimeSpan.FromSeconds(60), ChunkSize); + var definition = new MUnique.OpenMU.DataModel.Configuration.MonsterDefinition { Number = 540 }; + var spawnArea = new MUnique.OpenMU.DataModel.Configuration.MonsterSpawnArea { MonsterDefinition = definition }; + var npc = new MUnique.OpenMU.GameLogic.NPC.NonPlayerCharacter(spawnArea, definition, map) + { + Position = new Point(100, 100), + }; + await map.AddAsync(npc).ConfigureAwait(false); + + Assert.That(map.GetNpcByNumber(540), Is.SameAs(npc)); + Assert.That(map.GetNpcByNumber(999), Is.Null); + } +``` + +- [ ] **Step 2: Testi çalıştır** + +```bash +cd "/d/OpenMU/MU Client_Mobile 1.04d - Season 6E3/AdamuSw/src" && \ +dotnet test ../tests/MUnique.OpenMU.Tests/MUnique.OpenMU.Tests.csproj -c Release -p:ci=true --filter "GetNpcByNumberFindsSpawnedNpc" --nologo 2>&1 | tail -15 +``` +Expected: `Passed!` (1 passed). Eğer `AddAsync` NPC için ek kurulum (ör. attribute/rotasyon) istiyorsa, hatayı okuyup NPC init'ini asgari düzeyde tamamla ve tekrar çalıştır. + +- [ ] **Step 3: Commit** + +```bash +cd "/d/OpenMU/MU Client_Mobile 1.04d - Season 6E3/AdamuSw" && \ +git add tests/MUnique.OpenMU.Tests/GameMapTest.cs && \ +git -c user.name="Acentech Dev" -c user.email="acentech_dev@affinitybox.com" \ + commit -m "test(ADAMU-CUSTOM): regression test for GameMap.GetNpcByNumber (remote-NPC)" +``` + +--- + +### Task 3: Touch registry'yi güncelle (spec) + +**Files:** +- Modify: `docs/superpowers/specs/2026-07-14-castle-siege-design.md` (§9 touch registry) + +- [ ] **Step 1: Touch registry satırlarını "uygulandı" olarak işaretle** + +§9 tablosundaki 3 remote-NPC satırını gerçek durumla güncelle: her biri `// ADAMU-CUSTOM` ile işaretli, Faz 0b'de uygulandı, regresyon testi `GameMapTest.GetNpcByNumberFindsSpawnedNpcAsync`. `GameMap.cs` satırının nedenini "remote-NPC: GetNpcByNumber helper" olarak netleştir. + +- [ ] **Step 2: Commit** + +```bash +cd "/d/OpenMU/MU Client_Mobile 1.04d - Season 6E3/AdamuSw" && \ +git add docs/superpowers/specs/2026-07-14-castle-siege-design.md && \ +git -c user.name="Acentech Dev" -c user.email="acentech_dev@affinitybox.com" \ + commit -m "docs: mark remote-NPC touch registry as applied (Faz 0b)" +``` + +--- + +### Task 4: Image'i yeniden derle, doğrula, yayınla + +**Files:** yok (derleme + deploy + push) + +- [ ] **Step 1: Image'i yeniden derle** + +```bash +cd "/d/OpenMU/MU Client_Mobile 1.04d - Season 6E3/AdamuSw/src" && \ +docker build -f Startup/Dockerfile -t adamu-openmu:dev -t atfatmc/adamu-openmu:latest . +``` +Expected: build başarılı, `naming to ... adamu-openmu:dev`. + +- [ ] **Step 2: Lokalde ayağa kaldır ve boot doğrula** + +```bash +cd "/d/OpenMU/MU Client_Mobile 1.04d - Season 6E3/AdamuSw/deploy-adamu" && \ +docker compose -f docker-compose.local.yml up -d && \ +sleep 60 && \ +curl -s -o /dev/null -w "%{http_code}\n" http://localhost:8080/ +``` +Expected: `200` (veya `302`). Loglarda exception yok. + +- [ ] **Step 3: Mobil uçtan uca (manuel)** + +Emülatörde mobil client'ı bu server'a bağla, "NPC list"ten bir dükkân/chaos/vault'u **herhangi bir konumdan** aç → pencere açılmalı. Bu, `0x8000` akışının client↔server uçtan uca çalıştığını kanıtlar (unit test tek başına handler dalını garanti etmez). + +- [ ] **Step 4: Stack'i durdur ve Gitea'ya push et** + +```bash +cd "/d/OpenMU/MU Client_Mobile 1.04d - Season 6E3/AdamuSw/deploy-adamu" && \ +docker compose -f docker-compose.local.yml down && \ +cd "/d/OpenMU/MU Client_Mobile 1.04d - Season 6E3/AdamuSw" && \ +git push origin main +``` + +- [ ] **Step 5: (Manuel onay sonrası) DockerHub'a yayınla** + +Mobil doğrulama geçtiyse: +```bash +docker push atfatmc/adamu-openmu:latest +``` +Artık bu image remote-NPC içeriyor → production'a geçiş güvenli. + +--- + +## Sonraki adım + +Faz 0b bitince: kendi image'imiz artık remote-NPC'yi içeriyor ve production'a geçilebilir. Sonraki büyük iş: **Castle Siege P1** (veri modeli + faz state machine + zamanlama + admin start/stop) — kendi spec/plan döngüsüyle.