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);
}
}
}
}