using System.Numerics; using AcDream.Core.Items; using AcDream.Core.Net; using AcDream.Core.Physics.Motion; using AcDream.Core.Ui; using AcDream.Core.World; namespace AcDream.App.UI.Layout; /// /// Joins acdream's existing two-table world model into the immutable projected frame /// consumed by . This is the App-layer equivalent of retail's /// gmRadarUI::DrawObjects object-maintenance walk: Core still owns every /// AC-specific classification and math decision, while the retained widget remains a /// backend-only renderer. /// public sealed class RadarSnapshotProvider { private static readonly Vector2 ProductionCenter = new(60f, 60f); private readonly ClientObjectTable _objects; private readonly IReadOnlyDictionary _worldEntities; private readonly IReadOnlyDictionary _playerEntities; private readonly IReadOnlyDictionary _spawns; private readonly Func _playerGuid; private readonly Func _playerYawRadians; private readonly Func _playerCellId; private readonly Func _selectedGuid; private readonly Func _coordinatesOnRadar; private readonly Func _uiLocked; private readonly Func? _relationshipFor; public RadarSnapshotProvider( ClientObjectTable objects, IReadOnlyDictionary worldEntities, IReadOnlyDictionary spawns, Func playerGuid, Func playerYawRadians, Func playerCellId, Func selectedGuid, Func coordinatesOnRadar, Func uiLocked, Func? relationshipFor = null, IReadOnlyDictionary? playerEntities = null) { _objects = objects ?? throw new ArgumentNullException(nameof(objects)); _worldEntities = worldEntities ?? throw new ArgumentNullException(nameof(worldEntities)); _playerEntities = playerEntities ?? worldEntities; _spawns = spawns ?? throw new ArgumentNullException(nameof(spawns)); _playerGuid = playerGuid ?? throw new ArgumentNullException(nameof(playerGuid)); _playerYawRadians = playerYawRadians ?? throw new ArgumentNullException(nameof(playerYawRadians)); _playerCellId = playerCellId ?? throw new ArgumentNullException(nameof(playerCellId)); _selectedGuid = selectedGuid ?? throw new ArgumentNullException(nameof(selectedGuid)); _coordinatesOnRadar = coordinatesOnRadar ?? throw new ArgumentNullException(nameof(coordinatesOnRadar)); _uiLocked = uiLocked ?? throw new ArgumentNullException(nameof(uiLocked)); _relationshipFor = relationshipFor; } public UiRadarSnapshot BuildSnapshot() { bool uiLocked = _uiLocked(); uint playerGuid = _playerGuid(); if (playerGuid == 0u || !_playerEntities.TryGetValue(playerGuid, out var playerEntity)) return UiRadarSnapshot.Empty with { UiLocked = uiLocked }; float heading = MoveToMath.HeadingFromYaw(_playerYawRadians()); uint playerCellId = _playerCellId(); bool isOutside = RadarCoordinates.TryFromCell(playerCellId, out var playerCoordinates); string? coordinates = _coordinatesOnRadar() && isOutside ? playerCoordinates.CombinedText : null; uint playerPwd = _spawns.TryGetValue(playerGuid, out var playerSpawn) ? playerSpawn.ObjectDescriptionFlags ?? 0u : 0u; var playerTraits = RadarObjectTraits.FromPublicWeenieDescription( (uint)(_objects.Get(playerGuid)?.Type ?? ItemType.None), playerPwd); float range = RetailRadar.GetRangeMeters(isOutside); var blips = new List(_worldEntities.Count); foreach (var pair in _worldEntities) { uint guid = pair.Key; if (guid == playerGuid) continue; var entity = pair.Value; var clientObject = _objects.Get(guid); _spawns.TryGetValue(guid, out var spawn); byte? rawBehavior = clientObject?.RadarBehavior ?? spawn.RadarBehavior; if (rawBehavior is null || !RetailRadar.IsShowable((RadarBehavior)rawBehavior.Value, hasPhysicsObject: true)) { continue; } uint itemType = (uint)(clientObject?.Type ?? ItemType.None); if (itemType == 0u) itemType = spawn.ItemType ?? 0u; uint pwd = spawn.ObjectDescriptionFlags ?? 0u; byte colorOverride = clientObject?.RadarBlipColor ?? spawn.RadarBlipColor ?? 0; var traits = RadarObjectTraits.FromPublicWeenieDescription( itemType, pwd, colorOverride); var relationship = _relationshipFor?.Invoke(guid) ?? default; relationship = relationship with { PlayerIsPlayerKiller = playerTraits.IsPlayerKiller, PlayerIsPkLite = playerTraits.IsPkLite, }; var shape = RetailRadar.GetBlipShape(traits, relationship); if (shape == RadarBlipShape.Undef) continue; Vector3 playerSpace = MoveToMath.GlobalToLocalVec( playerEntity.Rotation, entity.Position - playerEntity.Position); if (!RetailRadar.TryProject( playerSpace, ProductionCenter, RadarController.RadarPixelRadius, range, out var projection)) { continue; } var color = RadarBlipColors.For(traits, relationship) .DimRgb(projection.RgbMultiplier); string? name = clientObject?.Name; if (string.IsNullOrEmpty(name)) name = spawn.Name ?? $"0x{guid:X8}"; blips.Add(new UiRadarBlip( ObjectId: guid, Name: name, PixelX: projection.Pixel.X, PixelY: projection.Pixel.Y, Color: new Vector4(color.Red, color.Green, color.Blue, color.Alpha), Shape: shape, Selected: _selectedGuid() == guid)); } return new UiRadarSnapshot( PlayerHeadingDegrees: heading, Blips: blips, CoordinatesText: coordinates, BlankBlips: false, UiLocked: uiLocked); } }