Files
AdamuSw/docs/superpowers/plans/2026-07-14-phase0b-remote-npc-port.md
2026-07-14 21:32:13 +03:00

263 lines
11 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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: <neden>` ... `// 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 <acentech_dev@affinitybox.com>`.
- 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.
/// <summary>
/// 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.
/// </summary>
/// <param name="number">The <see cref="MUnique.OpenMU.DataModel.Configuration.MonsterDefinition.Number"/>.</param>
/// <returns>The first matching non-player-character, or <c>null</c>.</returns>
public NonPlayerCharacter? GetNpcByNumber(short number)
{
return this._objectsInMap.Values
.OfType<NonPlayerCharacter>()
.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").
/// <summary>
/// Talks to an NPC identified by its definition <paramref name="npcNumber"/>, 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 <see cref="TalkToNpcAsync"/> flow.
/// </summary>
/// <param name="player">The player.</param>
/// <param name="npcNumber">The NPC definition number.</param>
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<byte> 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
/// <summary>
/// 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.)
/// </summary>
[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.