using System.Numerics; using AcDream.App.UI.Layout; using AcDream.Core.Items; using AcDream.Core.Net; using AcDream.Core.Net.Messages; using AcDream.Core.Ui; using AcDream.Core.World; namespace AcDream.App.Tests.UI.Layout; public sealed class RadarSnapshotProviderTests { [Fact] public void BuildSnapshot_JoinsWorldAndWeenieTables_UsingCoreRetailMath() { const uint player = 1u; const uint monster = 2u; var objects = new ClientObjectTable(); objects.Ingest(Weenie(player, "Player", ItemType.Creature)); objects.Ingest(Weenie(monster, "Drudge", ItemType.Creature) with { RadarBehavior = (byte)RadarBehavior.ShowAlways, }); var entities = new Dictionary { [player] = Entity(player, Vector3.Zero, Quaternion.Identity), [monster] = Entity(monster, new Vector3(0f, 15f, 6f), Quaternion.Identity), }; var spawns = new Dictionary { [player] = Spawn(player) with { ObjectDescriptionFlags = 0x00000008u }, // BF_PLAYER [monster] = Spawn(monster) with { ObjectDescriptionFlags = 0x00000010u }, // BF_ATTACKABLE }; uint? selected = monster; var provider = new RadarSnapshotProvider( objects, entities, spawns, playerGuid: () => player, playerYawRadians: () => MathF.PI / 2f, // retail heading 0 degrees playerCellId: () => 0xA9B40001u, selectedGuid: () => selected, coordinatesOnRadar: () => true, uiLocked: () => false); var snapshot = provider.BuildSnapshot(); Assert.Equal(0f, snapshot.PlayerHeadingDegrees, 3); Assert.Equal(RadarCoordinates.TryFromCell(0xA9B40001u, out var coords) ? coords.CombinedText : null, snapshot.CoordinatesText); var blip = Assert.Single(snapshot.Blips); Assert.Equal(monster, blip.ObjectId); Assert.Equal("Drudge", blip.Name); Assert.Equal(60f, blip.PixelX); Assert.Equal(50f, blip.PixelY); // 15 m * 50 px / 75 m Assert.Equal(RadarBlipShape.Plus, blip.Shape); Assert.True(blip.Selected); Assert.Equal(RadarBlipColors.Gold.Red * 0.65f, blip.Color.X, 5); Assert.Equal(RadarBlipColors.Gold.Green * 0.65f, blip.Color.Y, 5); } [Fact] public void BuildSnapshot_RejectsShowNever_AndHidesIndoorCoordinates() { const uint player = 10u; const uint hidden = 11u; var objects = new ClientObjectTable(); objects.Ingest(Weenie(player, "Player", ItemType.Creature)); objects.Ingest(Weenie(hidden, "Hidden", ItemType.Portal) with { RadarBehavior = (byte)RadarBehavior.ShowNever, }); var entities = new Dictionary { [player] = Entity(player, Vector3.Zero, Quaternion.Identity), [hidden] = Entity(hidden, new Vector3(2f, 2f, 0f), Quaternion.Identity), }; var spawns = new Dictionary { [player] = Spawn(player), [hidden] = Spawn(hidden), }; var provider = new RadarSnapshotProvider( objects, entities, spawns, playerGuid: () => player, playerYawRadians: () => 0f, playerCellId: () => 0xA9B40100u, // EnvCell: no landscape coordinate selectedGuid: () => null, coordinatesOnRadar: () => true, uiLocked: () => true); var snapshot = provider.BuildSnapshot(); Assert.Empty(snapshot.Blips); Assert.Null(snapshot.CoordinatesText); Assert.True(snapshot.UiLocked); } [Fact] public void BuildSnapshot_UsesCanonicalPlayerWhileHiddenTargetsStayExcluded() { const uint player = 20u; const uint hiddenTarget = 21u; var objects = new ClientObjectTable(); objects.Ingest(Weenie(player, "Player", ItemType.Creature)); objects.Ingest(Weenie(hiddenTarget, "Hidden target", ItemType.Creature) with { RadarBehavior = (byte)RadarBehavior.ShowAlways, }); var canonical = new Dictionary { [player] = Entity(player, Vector3.Zero, Quaternion.Identity), [hiddenTarget] = Entity(hiddenTarget, new Vector3(5f, 0f, 0f), Quaternion.Identity), }; var interactionVisible = new Dictionary(); var spawns = new Dictionary { [player] = Spawn(player), [hiddenTarget] = Spawn(hiddenTarget), }; var provider = new RadarSnapshotProvider( objects, interactionVisible, spawns, playerGuid: () => player, playerYawRadians: () => 0f, playerCellId: () => 0xA9B40001u, selectedGuid: () => hiddenTarget, coordinatesOnRadar: () => true, uiLocked: () => false, playerEntities: canonical); var snapshot = provider.BuildSnapshot(); Assert.NotNull(snapshot.CoordinatesText); Assert.Empty(snapshot.Blips); } private static WorldEntity Entity(uint guid, Vector3 position, Quaternion rotation) => new() { Id = guid, ServerGuid = guid, SourceGfxObjOrSetupId = 0u, Position = position, Rotation = rotation, MeshRefs = Array.Empty(), }; private static WorldSession.EntitySpawn Spawn(uint guid) => new( Guid: guid, Position: null, SetupTableId: null, AnimPartChanges: Array.Empty(), TextureChanges: Array.Empty(), SubPalettes: Array.Empty(), BasePaletteId: null, ObjScale: null, Name: null, ItemType: null, MotionState: null, MotionTableId: null); private static WeenieData Weenie(uint guid, string name, ItemType type) => new( Guid: guid, Name: name, Type: type, WeenieClassId: 1u, IconId: 0u, IconOverlayId: 0u, IconUnderlayId: 0u, Effects: 0u, Value: null, StackSize: null, StackSizeMax: null, Burden: null, ContainerId: null, WielderId: null, ValidLocations: null, CurrentWieldedLocation: null, Priority: null, ItemsCapacity: null, ContainersCapacity: null, Structure: null, MaxStructure: null, Workmanship: null); }