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

11 KiB
Raw Permalink Blame History

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:

    // 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:

    // 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?...)):

    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
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
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:

    /// <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
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
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
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
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
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
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:

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.