using System.Collections.Generic; using Luneraria.Core; using UnityEngine; namespace Luneraria.View { /// /// Shows the driver's match (U2C): unit views per combat, building looks from the authoritative /// HP, tower bolts from the towers' attack events. Runs after the driver in the same frame and /// only reads the core. /// [DefaultExecutionOrder(100)] public sealed class LuneMatchView : MonoBehaviour { public LuneMatchDriver Driver; public LuneUnitLibrary Library; public Camera ViewCamera; readonly Dictionary _units = new Dictionary(); readonly Dictionary _buildings = new Dictionary(); readonly Dictionary _towerSerials = new Dictionary(); readonly List _bolts = new List(); LuneCombat _combat; Transform _unitRoot; bool _synced; sealed class Bolt { public LineRenderer Line; public float Age; public LuneUnitView Target; public Vector3 From; } public IEnumerable Units { get { return _units.Values; } } /// Main-thread cost of the last frame's view update (milliseconds). public double LastSyncMs { get; private set; } readonly System.Diagnostics.Stopwatch _clock = new System.Diagnostics.Stopwatch(); public LuneBuildingView Building(string id) { _buildings.TryGetValue(id, out LuneBuildingView view); return view; } void Start() { foreach (LuneBuildingView building in FindObjectsByType(FindObjectsSortMode.None)) _buildings[building.BuildingId] = building; _unitRoot = new GameObject("Units").transform; if (ViewCamera == null) ViewCamera = Camera.main; } void LateUpdate() { if (Driver == null || Driver.Runner == null) return; _clock.Restart(); LuneMatch match = Driver.Runner.Match; float dt = Time.deltaTime; if (match.Combat != _combat) { foreach (LuneUnitView view in _units.Values) Destroy(view.gameObject); _units.Clear(); _towerSerials.Clear(); _combat = match.Combat; } bool showUnits = _combat != null && Driver.Runner.Current != LuneMatchRunner.Stage.Planning; float alpha = Driver.Runner.Current == LuneMatchRunner.Stage.Combat ? (float)Driver.Runner.Clock.Alpha : 1f; if (showUnits) { foreach (LuneUnit unit in _combat.Units) { if (!unit.IsMobile()) continue; if (!_units.TryGetValue(unit.Id, out LuneUnitView view)) { view = new GameObject().AddComponent(); view.transform.SetParent(_unitRoot, false); view.Bind(unit, Library); _units[unit.Id] = view; } view.Sync(alpha, dt, ViewCamera != null ? ViewCamera.transform : null); } } SyncBuildings(match); SyncBolts(dt); _synced = true; LastSyncMs = _clock.Elapsed.TotalMilliseconds; } void SyncBuildings(LuneMatch match) { bool fighting = _combat != null && match.Phase == Phase.Combat; foreach (BuildingDef definition in match.Map.Buildings) { if (!_buildings.TryGetValue(definition.Id, out LuneBuildingView view)) continue; double hp = match.BuildingHp[definition.Id]; LuneUnit unit = null; if (_combat != null && _combat.Buildings.TryGetValue(definition.Id, out unit) && fighting) hp = unit.Hp; view.SetHealth((float)hp, (float)definition.MaxHp, _synced); if (unit != null && unit.Kind == "tower" && _combat != null) TowerAttack(view, unit); } } void TowerAttack(LuneBuildingView view, LuneUnit tower) { _towerSerials.TryGetValue(tower.BuildingId, out int last); _towerSerials[tower.BuildingId] = tower.AttackSerial; if (tower.AttackSerial == last || last == 0 && tower.AttackSerial == 0) return; if (tower.Target == null || !_units.TryGetValue(tower.Target.Id, out LuneUnitView target)) return; var line = new GameObject("Bolt").AddComponent(); line.transform.SetParent(_unitRoot, false); line.sharedMaterial = Library.Bolt[Mathf.Clamp(view.Team, 0, 2)]; line.widthMultiplier = view.Tier == "base" ? .22f : .16f; line.positionCount = 2; line.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off; _bolts.Add(new Bolt { Line = line, Target = target, From = view.Muzzle }); } void SyncBolts(float dt) { for (int i = _bolts.Count - 1; i >= 0; i--) { Bolt bolt = _bolts[i]; bolt.Age += dt; if (bolt.Age > .25f || bolt.Target == null) { Destroy(bolt.Line.gameObject); _bolts.RemoveAt(i); continue; } bolt.Line.SetPosition(0, bolt.From); bolt.Line.SetPosition(1, bolt.Target.AimPoint); } } } }