Compare commits
4 Commits
5d48b1512e
...
b44cc4f60d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b44cc4f60d | ||
|
|
2ced76b303 | ||
|
|
03521c3ef5 | ||
|
|
7ff1e12f2a |
262
docs/superpowers/plans/2026-07-14-phase0b-remote-npc-port.md
Normal file
262
docs/superpowers/plans/2026-07-14-phase0b-remote-npc-port.md
Normal file
@@ -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: <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.
|
||||
@@ -161,12 +161,12 @@ docker push <hub>/adamu-openmu:<tag>
|
||||
|
||||
> Faz 0 ve her fazda güncellenecek. Amaç: upstream merge'lerde çakışma riskini denetlenebilir tutmak.
|
||||
|
||||
| Dosya | Faz | Neden | Not |
|
||||
| Dosya | Faz | Neden | Durum |
|
||||
|---|---|---|---|
|
||||
| `GameLogic/PlayerActions/TalkNpcAction.cs` | 0 | remote-NPC (mevcut) | `// ADAMU-CUSTOM` ile sar |
|
||||
| `GameLogic/GameMap.cs` | 0 | remote-NPC (mevcut) | port + işaretle |
|
||||
| `GameServer/MessageHandler/TalkNpcHandlerPlugInBase.cs` | 0 | remote-NPC (mevcut) | port + işaretle |
|
||||
| DataModel + Initialization (CS entity'leri) | P1 | CS persistence | EF migration gerekir |
|
||||
| `GameLogic/PlayerActions/TalkNpcAction.cs` | 0b | remote-NPC: `TalkToNpcByNumberAsync` (additive metod) | ✅ Uygulandı, `// ADAMU-CUSTOM` işaretli |
|
||||
| `GameLogic/GameMap.cs` | 0b | remote-NPC: `GetNpcByNumber` helper (additive metod) | ✅ Uygulandı, `// ADAMU-CUSTOM` işaretli; test: `GameMapTest.GetNpcByNumberFindsSpawnedNpcAsync` |
|
||||
| `GameServer/MessageHandler/TalkNpcHandlerPlugInBase.cs` | 0b | remote-NPC: `0x8000` marker dalı (tek gerçek core-logic dokunuşu) | ✅ Uygulandı, `// ADAMU-CUSTOM` işaretli |
|
||||
| DataModel + Initialization (CS entity'leri) | P1 | CS persistence | ⏳ Planlanacak (EF migration gerekir) |
|
||||
| *(fazlar ilerledikçe eklenecek)* | | | |
|
||||
|
||||
## 10. Test stratejisi
|
||||
|
||||
@@ -103,6 +103,23 @@ public class GameMap
|
||||
return result;
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
/// <summary>
|
||||
/// Gets the attackables in range of the specified coordinates.
|
||||
/// </summary>
|
||||
|
||||
@@ -57,6 +57,39 @@ public class TalkNpcAction
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this action advances the player state to <see cref="PlayerState.NpcDialogOpened" />.
|
||||
/// </summary>
|
||||
|
||||
@@ -29,6 +29,17 @@ internal abstract class TalkNpcHandlerPlugInBase : IPacketHandlerPlugIn
|
||||
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);
|
||||
|
||||
@@ -165,6 +165,26 @@ public class GameMapTest
|
||||
Assert.That(player2.Object.Observers.Count, Is.EqualTo(0));
|
||||
}
|
||||
|
||||
/// <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);
|
||||
}
|
||||
|
||||
private Mock<ITestPlayer> GetPlayer()
|
||||
{
|
||||
var player = new Mock<ITestPlayer>();
|
||||
|
||||
Reference in New Issue
Block a user