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; /// The nearby_objects and dungeon_map frames as the original built them. 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().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(); 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 cells) : IDungeonCellSource { public int Reads { get; private set; } public IReadOnlyList? ReadCells(uint landblock) { Reads++; return cells; } } }