FightTracker.cs 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. using UnityEngine;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. /// <summary>
  5. /// Tracks active fights between runners and monsters
  6. /// Provides fight data for UI display and camera zoom operations
  7. /// </summary>
  8. public class FightTracker : MonoBehaviour
  9. {
  10. [SerializeField] private float fightDisplayDuration = 3f; // How long to display fights after they end
  11. // Internal fight tracking structure
  12. private class ActiveFight
  13. {
  14. public AIAgent runner;
  15. public Monster monster;
  16. public MazeRoom room;
  17. public float startTime;
  18. public float endTime = float.MaxValue; // While fighting, this is MaxValue
  19. public bool isActive => endTime == float.MaxValue;
  20. }
  21. private List<ActiveFight> activeFights = new();
  22. private Dictionary<(AIAgent, Monster), ActiveFight> fightLookup = new();
  23. private static FightTracker instance;
  24. public static FightTracker Instance
  25. {
  26. get
  27. {
  28. if (instance == null)
  29. {
  30. var go = new GameObject("FightTracker");
  31. instance = go.AddComponent<FightTracker>();
  32. }
  33. return instance;
  34. }
  35. }
  36. /// <summary>
  37. /// Called when a runner starts fighting a monster
  38. /// </summary>
  39. public void StartFight(AIAgent runner, Monster monster)
  40. {
  41. if (runner == null || monster == null) return;
  42. if (runner.IsDead || monster.IsDead) return;
  43. var key = (runner, monster);
  44. if (fightLookup.ContainsKey(key)) return; // Already tracking this fight
  45. var fight = new ActiveFight
  46. {
  47. runner = runner,
  48. monster = monster,
  49. room = monster.HomeRoom,
  50. startTime = Time.time
  51. };
  52. activeFights.Add(fight);
  53. fightLookup[key] = fight;
  54. }
  55. /// <summary>
  56. /// Called when a fight ends (runner or monster dies, or they separate)
  57. /// </summary>
  58. public void EndFight(AIAgent runner, Monster monster)
  59. {
  60. if (runner == null || monster == null) return;
  61. var key = (runner, monster);
  62. if (fightLookup.TryGetValue(key, out var fight))
  63. {
  64. fight.endTime = Time.time;
  65. fightLookup.Remove(key);
  66. }
  67. }
  68. /// <summary>
  69. /// Gets all fights that should currently be displayed (active + recent)
  70. /// </summary>
  71. public List<FightData> GetDisplayFights()
  72. {
  73. var displayFights = new List<FightData>();
  74. // Clean up old completed fights and null entries
  75. activeFights.RemoveAll(f =>
  76. {
  77. // Remove if either fighter was destroyed
  78. if (f.runner == null || f.monster == null)
  79. return true;
  80. float timeSinceEnd = Time.time - f.endTime;
  81. return !f.isActive && timeSinceEnd > fightDisplayDuration;
  82. });
  83. // Convert to display format and sort
  84. foreach (var fight in activeFights)
  85. {
  86. // Skip if either fighter is null (shouldn't happen due to RemoveAll, but safe)
  87. if (fight.runner == null || fight.monster == null)
  88. continue;
  89. displayFights.Add(new FightData
  90. {
  91. runnerName = fight.runner.AgentName,
  92. runnerId = fight.runner.AgentId,
  93. monsterType = $"Monster_{fight.monster.GetHashCode()}",
  94. room = fight.room,
  95. isActive = fight.isActive,
  96. timeSinceEnd = fight.isActive ? 0f : Time.time - fight.endTime,
  97. runner = fight.runner,
  98. monster = fight.monster
  99. });
  100. }
  101. // Sort by number of participants in same room (descending), then by active status
  102. displayFights = displayFights
  103. .OrderByDescending(f => CountFightsInRoom(f.room))
  104. .ThenByDescending(f => f.isActive)
  105. .ToList();
  106. return displayFights;
  107. }
  108. /// <summary>
  109. /// Count how many fights are happening in a specific room
  110. /// </summary>
  111. private int CountFightsInRoom(MazeRoom room)
  112. {
  113. return activeFights.Count(f => f.room.Id == room.Id);
  114. }
  115. /// <summary>
  116. /// Gets total number of fighters in a room (runners + monsters)
  117. /// </summary>
  118. public int GetFighterCountInRoom(MazeRoom room)
  119. {
  120. var fightersInRoom = activeFights.Where(f => f.room.Id == room.Id).ToList();
  121. var uniqueRunners = new HashSet<AIAgent>(fightersInRoom.Select(f => f.runner));
  122. var uniqueMonsters = new HashSet<Monster>(fightersInRoom.Select(f => f.monster));
  123. return uniqueRunners.Count + uniqueMonsters.Count;
  124. }
  125. void Awake()
  126. {
  127. if (instance == null)
  128. {
  129. instance = this;
  130. DontDestroyOnLoad(gameObject);
  131. }
  132. else if (instance != this)
  133. {
  134. Destroy(gameObject);
  135. }
  136. }
  137. void Update()
  138. {
  139. // Periodically clean up dead fighters
  140. activeFights.RemoveAll(f => f.runner == null || f.runner.IsDead || f.monster == null || f.monster.IsDead);
  141. }
  142. }
  143. /// <summary>
  144. /// Display-friendly fight data
  145. /// </summary>
  146. public struct FightData
  147. {
  148. public string runnerName;
  149. public int runnerId;
  150. public string monsterType;
  151. public MazeRoom room;
  152. public bool isActive;
  153. public float timeSinceEnd;
  154. public AIAgent runner;
  155. public Monster monster;
  156. public string GetDisplayText()
  157. {
  158. return $"{runnerName} vs {monsterType}";
  159. }
  160. public string GetRoomInfo()
  161. {
  162. return $"Room {room.Id}";
  163. }
  164. }