AgentGroup.cs 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  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 AvgStrength => members.Count > 0 ? (int)members.Average(a => a.Stats.Strength) : 0;
  33. public int AvgSpeed => members.Count > 0 ? (int)members.Average(a => a.Stats.Speed) : 0;
  34. public int AvgMagic => members.Count > 0 ? (int)members.Average(a => a.Stats.Magic) : 0;
  35. public int AvgDexterity => members.Count > 0 ? (int)members.Average(a => a.Stats.Dexterity) : 0;
  36. public int AvgIntelligence => members.Count > 0 ? (int)members.Average(a => a.Stats.Intelligence) : 0;
  37. private readonly List<AIAgent> members = new();
  38. private static int nextGroupId = 1;
  39. public AgentGroup(AIAgent founder)
  40. {
  41. GroupId = nextGroupId++;
  42. AddMember(founder);
  43. }
  44. // ------------------------------------------------------------------ //
  45. // Membership //
  46. // ------------------------------------------------------------------ //
  47. /// <summary>
  48. /// Attempt to add a new agent. Returns true if accepted.
  49. /// Chance of acceptance drops with group size so large groups are
  50. /// harder to join.
  51. /// </summary>
  52. public bool TryAddMember(AIAgent candidate)
  53. {
  54. if (IsFull) return false;
  55. if (members.Contains(candidate)) return false;
  56. // Base acceptance roll – bigger group = lower chance to accept new member.
  57. // Size 1 → 100%, size 8 → ~11%
  58. float acceptanceChance = 1f / members.Count;
  59. // High INT candidate is better at selling themselves to the group;
  60. // high INT leader is more selective (slightly lower base chance but
  61. // we already reward INT via groupUpAffinity in the caller).
  62. acceptanceChance *= Mathf.Lerp(0.8f, 1.2f, candidate.GroupUpAffinity);
  63. if (Random.value > acceptanceChance) return false;
  64. AddMember(candidate);
  65. return true;
  66. }
  67. private void AddMember(AIAgent agent)
  68. {
  69. members.Add(agent);
  70. agent.JoinGroup(this);
  71. RecalcStats();
  72. UpdateVisual();
  73. Debug.Log($"[Group {GroupId}] {agent.AgentName} joined. Size now {members.Count}");
  74. }
  75. public void RemoveMember(AIAgent agent)
  76. {
  77. if (!members.Contains(agent)) return;
  78. bool wasLeader = agent == Leader;
  79. members.Remove(agent);
  80. agent.LeaveGroup();
  81. RecalcStats();
  82. if (members.Count == 0)
  83. {
  84. // Group dissolved
  85. DestroyVisual();
  86. return;
  87. }
  88. if (wasLeader)
  89. {
  90. // Promote next member; move visual to new leader
  91. Debug.Log($"[Group {GroupId}] Leader left, {Leader.AgentName} is the new leader.");
  92. }
  93. UpdateVisual();
  94. Debug.Log($"[Group {GroupId}] {agent.AgentName} left. Size now {members.Count}");
  95. }
  96. // ------------------------------------------------------------------ //
  97. // Stats //
  98. // ------------------------------------------------------------------ //
  99. private void RecalcStats()
  100. {
  101. if (members.Count == 0) return;
  102. CombinedStrength = members.Max(a => a.Stats.Strength);
  103. CombinedSpeed = members.Max(a => a.Stats.Speed);
  104. CombinedMagic = members.Max(a => a.Stats.Magic);
  105. CombinedDexterity = members.Max(a => a.Stats.Dexterity);
  106. CombinedIntelligence = members.Max(a => a.Stats.Intelligence);
  107. }
  108. // ------------------------------------------------------------------ //
  109. // Visual //
  110. // ------------------------------------------------------------------ //
  111. private AgentGroupVisual visual;
  112. private void UpdateVisual()
  113. {
  114. if (Leader == null) return;
  115. if (visual == null)
  116. {
  117. visual = Leader.gameObject.AddComponent<AgentGroupVisual>();
  118. visual.Init(this);
  119. }
  120. else
  121. {
  122. visual.Refresh();
  123. }
  124. }
  125. private void DestroyVisual()
  126. {
  127. if (visual != null)
  128. {
  129. Object.Destroy(visual);
  130. visual = null;
  131. }
  132. }
  133. }