AgentGroup.cs 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. using UnityEngine;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. /// <summary>
  5. /// Represents a group of agents moving together through the maze.
  6. /// The first member is always the leader whose GameObject drives movement.
  7. /// Other members follow by parenting / position copy each frame.
  8. ///
  9. /// Max size: 9. Joining chance decreases as group grows.
  10. /// Group shows a single enlarged sphere with a member-count label (AgentGroupVisual).
  11. /// </summary>
  12. public class AgentGroup
  13. {
  14. public const int MAX_SIZE = 9;
  15. /// <summary>Unique id for this group (for logging / future UI)</summary>
  16. public int GroupId { get; private set; }
  17. /// <summary>All members; index 0 is always the leader.</summary>
  18. public IReadOnlyList<AIAgent> Members => members;
  19. /// <summary>The agent that physically moves – others follow.</summary>
  20. public AIAgent Leader => members.Count > 0 ? members[0] : null;
  21. public int Size => members.Count;
  22. public bool IsFull => members.Count >= MAX_SIZE;
  23. // ------------------------------------------------------------------ //
  24. // Combined stats (max of each stat across members – group benefits //
  25. // from having specialists, but average is tracked too for display) //
  26. // ------------------------------------------------------------------ //
  27. public int CombinedStrength { get; private set; }
  28. public int CombinedSpeed { get; private set; }
  29. public int CombinedMagic { get; private set; }
  30. public int CombinedDexterity { get; private set; }
  31. public int CombinedIntelligence { get; private set; }
  32. public int CombinedConstitution { get; private set; }
  33. /// <summary>
  34. /// Combined group current health: sum of all members' current HP.
  35. /// </summary>
  36. public int CombinedHealth => members.Count > 0 ? members.Sum(a => a.Stats.CurrentHealth) : 0;
  37. /// <summary>
  38. /// Combined group max health: sum of all members' max HP.
  39. /// </summary>
  40. public int CombinedMaxHealth => members.Count > 0 ? members.Sum(a => a.Stats.MaxHealth) : 0;
  41. public int AvgStrength => members.Count > 0 ? (int)members.Average(a => a.Stats.Strength) : 0;
  42. public int AvgSpeed => members.Count > 0 ? (int)members.Average(a => a.Stats.Speed) : 0;
  43. public int AvgMagic => members.Count > 0 ? (int)members.Average(a => a.Stats.Magic) : 0;
  44. public int AvgDexterity => members.Count > 0 ? (int)members.Average(a => a.Stats.Dexterity) : 0;
  45. public int AvgIntelligence => members.Count > 0 ? (int)members.Average(a => a.Stats.Intelligence) : 0;
  46. public int AvgConstitution => members.Count > 0 ? (int)members.Average(a => a.Stats.Constitution) : 0;
  47. private readonly List<AIAgent> members = new();
  48. private static int nextGroupId = 1;
  49. public AgentGroup(AIAgent founder)
  50. {
  51. GroupId = nextGroupId++;
  52. AddMember(founder);
  53. }
  54. // ------------------------------------------------------------------ //
  55. // Membership //
  56. // ------------------------------------------------------------------ //
  57. /// <summary>
  58. /// Attempt to add a new agent. Returns true if accepted.
  59. /// Chance of acceptance drops with group size so large groups are
  60. /// harder to join.
  61. /// </summary>
  62. public bool TryAddMember(AIAgent candidate)
  63. {
  64. if (IsFull) return false;
  65. if (members.Contains(candidate)) return false;
  66. // Base acceptance roll – bigger group = lower chance to accept new member.
  67. // Size 1 → 100%, size 8 → ~11%
  68. float acceptanceChance = 1f / members.Count;
  69. // High INT candidate is better at selling themselves to the group;
  70. // high INT leader is more selective (slightly lower base chance but
  71. // we already reward INT via groupUpAffinity in the caller).
  72. acceptanceChance *= Mathf.Lerp(0.8f, 1.2f, candidate.GroupUpAffinity);
  73. if (Random.value > acceptanceChance) return false;
  74. AddMember(candidate);
  75. return true;
  76. }
  77. private void AddMember(AIAgent agent)
  78. {
  79. members.Add(agent);
  80. agent.JoinGroup(this);
  81. RecalcStats();
  82. UpdateVisual();
  83. Debug.Log($"[Group {GroupId}] {agent.AgentName} joined. Size now {members.Count}");
  84. }
  85. public void RemoveMember(AIAgent agent)
  86. {
  87. if (!members.Contains(agent)) return;
  88. bool wasLeader = agent == Leader;
  89. members.Remove(agent);
  90. agent.LeaveGroup();
  91. RecalcStats();
  92. if (members.Count == 0)
  93. {
  94. // Group dissolved
  95. DestroyVisual();
  96. return;
  97. }
  98. if (wasLeader)
  99. {
  100. // Promote next member; move visual to new leader
  101. Debug.Log($"[Group {GroupId}] Leader left, {Leader.AgentName} is the new leader.");
  102. }
  103. UpdateVisual();
  104. Debug.Log($"[Group {GroupId}] {agent.AgentName} left. Size now {members.Count}");
  105. }
  106. // ------------------------------------------------------------------ //
  107. // Stats //
  108. // ------------------------------------------------------------------ //
  109. private void RecalcStats()
  110. {
  111. if (members.Count == 0) return;
  112. CombinedStrength = members.Max(a => a.Stats.Strength);
  113. CombinedSpeed = members.Max(a => a.Stats.Speed);
  114. CombinedMagic = members.Max(a => a.Stats.Magic);
  115. CombinedDexterity = members.Max(a => a.Stats.Dexterity);
  116. CombinedIntelligence = members.Max(a => a.Stats.Intelligence);
  117. CombinedConstitution = members.Max(a => a.Stats.Constitution);
  118. }
  119. // ------------------------------------------------------------------ //
  120. // Visual //
  121. // ------------------------------------------------------------------ //
  122. private AgentGroupVisual visual;
  123. private void UpdateVisual()
  124. {
  125. if (Leader == null) return;
  126. if (visual == null)
  127. {
  128. visual = Leader.gameObject.AddComponent<AgentGroupVisual>();
  129. visual.Init(this);
  130. }
  131. else
  132. {
  133. visual.Refresh();
  134. }
  135. }
  136. private void DestroyVisual()
  137. {
  138. if (visual != null)
  139. {
  140. Object.Destroy(visual);
  141. visual = null;
  142. }
  143. }
  144. }