Radar: nearby_objects every second and dungeon_map per dungeon landblock
start_radar/stop_radar from the backend drive it, as before. nearby_objects keeps the original's shape: map coordinates on the surface, landblock-local raw positions with zero map coordinates in a dungeon, ids as signed ints, the same eight object classes, no wielded items or self. dungeon_map is built the original's way from the landblock's cells (x a multiple of 10, whole metres, 6-metre floors, rotation = the orientation's stored W) and sent once per landblock per session; the cells come through a small reader that the plugin API's dungeon-cell data will fill (until then the map is not sent). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
0efef6170c
commit
ec22348ebc
6 changed files with 425 additions and 0 deletions
|
|
@ -42,6 +42,8 @@ public sealed class FakeAutomation : IAutomationSurface
|
|||
|
||||
public FakeNetwork Network { get; } = new();
|
||||
|
||||
public FakeDungeonMap DungeonMap { get; } = new();
|
||||
|
||||
ICharacterInfo IAutomationSurface.Character => Character;
|
||||
ISpellCatalog IAutomationSurface.Spells => Spells;
|
||||
IMagicCommands IAutomationSurface.Magic => Magic;
|
||||
|
|
@ -58,6 +60,7 @@ public sealed class FakeAutomation : IAutomationSurface
|
|||
IVendorAutomation IAutomationSurface.Vendor => Vendor;
|
||||
ILoginAutomation IAutomationSurface.Login => Login;
|
||||
INetworkAutomation IAutomationSurface.Network => Network;
|
||||
IDungeonMapAutomation IAutomationSurface.DungeonMap => DungeonMap;
|
||||
}
|
||||
|
||||
/// <summary>A settable <see cref="ITradeAutomation"/>: a test drives <see cref="Raise*"/> and inspects <see cref="Calls"/>.</summary>
|
||||
|
|
|
|||
|
|
@ -43,3 +43,12 @@ public sealed class FakeNetwork : INetworkAutomation
|
|||
return AcceptImports;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>A dungeon-map surface whose sealed landblocks a test names.</summary>
|
||||
public sealed class FakeDungeonMap : IDungeonMapAutomation
|
||||
{
|
||||
public HashSet<uint> SealedLandblocks { get; } = [];
|
||||
|
||||
public bool IsSealedDungeon(uint cellId) =>
|
||||
(cellId & 0xFFFFu) >= 0x100u && SealedLandblocks.Contains(cellId & 0xFFFF0000u);
|
||||
}
|
||||
|
|
|
|||
119
tests/OpenAC.MosswartMassacre.Tests/Radar/RadarTests.cs
Normal file
119
tests/OpenAC.MosswartMassacre.Tests/Radar/RadarTests.cs
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
using System.Globalization;
|
||||
using AcDream.Plugin.Abstractions;
|
||||
using OpenAC.MosswartMassacre.Backend;
|
||||
using OpenAC.MosswartMassacre.Radar;
|
||||
using OpenAC.MosswartMassacre.Tests.Streams;
|
||||
|
||||
namespace OpenAC.MosswartMassacre.Tests.Radar;
|
||||
|
||||
/// <summary>The nearby_objects and dungeon_map frames as the original built them.</summary>
|
||||
public sealed class RadarTests
|
||||
{
|
||||
private const string Stamp = "2026-09-25T08:30:15.1230000Z";
|
||||
|
||||
private static string D(double value) => value.ToString("R", CultureInfo.InvariantCulture);
|
||||
|
||||
private static PluginWorldObject Landscape(uint id, string name, PluginObjectClass cls, PluginNavigationPosition position, uint wielder = 0) =>
|
||||
new(id, 1u, name, cls, 0u, 0u, wielder) { IsLandscape = true, HasPosition = true, Position = position };
|
||||
|
||||
[Fact]
|
||||
public void Nearby_objects_on_the_surface()
|
||||
{
|
||||
// NearbyObjectsTracker.cs:124-231: objects with ew/ns to 7 places and
|
||||
// z to 2; the player's own position both as map coordinates and as
|
||||
// landblock-local metres; ids as signed ints; only the eight classes;
|
||||
// no wielded items, no self.
|
||||
using var harness = new PluginHarness("Mossy");
|
||||
harness.Host.Automation.Navigation.Snapshot = new PluginNavigationSnapshot(
|
||||
true, false, 1u, StreamGoldenTests.Position(), false, false);
|
||||
harness.Host.Automation.Objects.Objects.AddRange(
|
||||
[
|
||||
Landscape(0x80001234, "Drudge Skulker", PluginObjectClass.Monster, StreamGoldenTests.Position(x: 108f, y: 120f, z: 44.25f)),
|
||||
Landscape(0x80001235, "Sword", PluginObjectClass.MeleeWeapon, StreamGoldenTests.Position()),
|
||||
Landscape(0x80001236, "Held", PluginObjectClass.Monster, StreamGoldenTests.Position(), wielder: 5),
|
||||
Landscape(0x50000001, "Mossy", PluginObjectClass.Player, StreamGoldenTests.Position()),
|
||||
]);
|
||||
harness.Login();
|
||||
|
||||
string json = Wire.Serialize(harness.Feature<RadarStream>().BuildNearbyObjects());
|
||||
|
||||
double monsterEw = StreamGoldenTests.Position(x: 108f).EastWest;
|
||||
double monsterNs = StreamGoldenTests.Position(y: 120f).NorthSouth;
|
||||
Assert.Equal(
|
||||
"{\"type\":\"nearby_objects\",\"character_name\":\"Mossy\",\"timestamp\":\"" + Stamp + "\","
|
||||
+ "\"player_ew\":" + D(Math.Round((double)33.65f, 7)) + ",\"player_ns\":" + D(Math.Round((double)42.55f, 7))
|
||||
+ ",\"player_z\":42.5,\"player_heading\":90.0,\"is_dungeon\":false,\"landblock\":\"0xA9B40000\","
|
||||
+ "\"player_x\":96.0,\"player_y\":120.0,\"player_raw_z\":42.5,"
|
||||
+ "\"objects\":[{\"id\":-2147478988,\"name\":\"Drudge Skulker\",\"object_class\":\"Monster\","
|
||||
+ "\"ew\":" + D(Math.Round(monsterEw, 7)) + ",\"ns\":" + D(Math.Round(monsterNs, 7)) + ",\"z\":44.25}]}",
|
||||
json);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void In_a_dungeon_objects_carry_raw_positions_and_the_map_goes_out_once()
|
||||
{
|
||||
const uint cell = 0x01D90105u;
|
||||
var here = StreamGoldenTests.Position(x: 30f, y: 40f, z: -12f, cell: cell);
|
||||
var cells = new FakeCells(
|
||||
[
|
||||
new DungeonCellRecord(0x0123, 10.2f, 20f, 0.4f, 1f),
|
||||
new DungeonCellRecord(0x0456, 15f, 20f, 0f, 0.7071f),
|
||||
new DungeonCellRecord(0x0789, 30f, -10f, -6.4f, 0f),
|
||||
]);
|
||||
using var harness = new PluginHarness("Mossy");
|
||||
harness.Host.Automation.Navigation.Snapshot = new PluginNavigationSnapshot(true, false, 1u, here, false, false);
|
||||
harness.Host.Automation.DungeonMap.SealedLandblocks.Add(0x01D90000u);
|
||||
harness.Host.Automation.Objects.Objects.Add(
|
||||
Landscape(0x80000001, "Lich", PluginObjectClass.Monster, StreamGoldenTests.Position(x: 31.5f, y: 41.25f, z: -12f, cell: cell)));
|
||||
harness.Login();
|
||||
var radar = new RadarStream(harness.Context, cells) ;
|
||||
radar.Start();
|
||||
|
||||
radar.OnTick();
|
||||
radar.OnTick();
|
||||
|
||||
Assert.Equal(
|
||||
"{\"type\":\"dungeon_map\",\"character_name\":\"Mossy\",\"landblock\":\"0x01D90000\","
|
||||
+ "\"bounds\":{\"min_x\":10,\"max_x\":30,\"min_y\":-10,\"max_y\":20},"
|
||||
+ "\"z_levels\":[{\"z\":-6,\"cells\":[{\"env_id\":1929,\"x\":30,\"y\":-10,\"z\":-6,\"rotation\":0.0}]},"
|
||||
+ "{\"z\":0,\"cells\":[{\"env_id\":291,\"x\":10,\"y\":20,\"z\":0,\"rotation\":1.0}]}]}",
|
||||
Assert.Single(harness.Frames, f => f.Contains("dungeon_map", StringComparison.Ordinal)));
|
||||
string objects = harness.Frames.First(f => f.Contains("nearby_objects", StringComparison.Ordinal));
|
||||
Assert.Contains("\"is_dungeon\":true", objects, StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"{\"id\":-2147483647,\"name\":\"Lich\",\"object_class\":\"Monster\",\"ew\":0.0,\"ns\":0.0,\"z\":0.0,"
|
||||
+ "\"raw_x\":31.5,\"raw_y\":41.25,\"raw_z\":-12.0}",
|
||||
objects,
|
||||
StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void The_backend_turns_the_radar_on_and_off()
|
||||
{
|
||||
using var harness = new PluginHarness("Mossy");
|
||||
harness.Login();
|
||||
var radar = harness.Feature<RadarStream>();
|
||||
|
||||
harness.Receive("{\"player_name\":\"Mossy\",\"command\":\"start_radar\"}");
|
||||
harness.Tick(0.01);
|
||||
Assert.True(radar.IsActive);
|
||||
|
||||
harness.Run(2.05);
|
||||
Assert.Equal(2, harness.FramesOfType("nearby_objects").Count());
|
||||
|
||||
harness.Receive("{\"player_name\":\"Mossy\",\"command\":\"stop_radar\"}");
|
||||
harness.Tick(0.01);
|
||||
Assert.False(radar.IsActive);
|
||||
}
|
||||
|
||||
private sealed class FakeCells(IReadOnlyList<DungeonCellRecord> cells) : IDungeonCellSource
|
||||
{
|
||||
public int Reads { get; private set; }
|
||||
|
||||
public IReadOnlyList<DungeonCellRecord>? ReadCells(uint landblock)
|
||||
{
|
||||
Reads++;
|
||||
return cells;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue