using UnityEngine; using System.Collections.Generic; using System.Linq; /// /// Represents a group of agents moving together through the maze. /// The first member is always the leader whose GameObject drives movement. /// Other members follow by parenting / position copy each frame. /// /// Max size: 9. Joining chance decreases as group grows. /// Group shows a single enlarged sphere with a member-count label (AgentGroupVisual). /// public class AgentGroup { public const int MAX_SIZE = 9; /// Unique id for this group (for logging / future UI) public int GroupId { get; private set; } /// All members; index 0 is always the leader. public IReadOnlyList Members => members; /// The agent that physically moves – others follow. public AIAgent Leader => members.Count > 0 ? members[0] : null; public int Size => members.Count; public bool IsFull => members.Count >= MAX_SIZE; // ------------------------------------------------------------------ // // Combined stats (max of each stat across members – group benefits // // from having specialists, but average is tracked too for display) // // ------------------------------------------------------------------ // public int CombinedStrength { get; private set; } public int CombinedSpeed { get; private set; } public int CombinedMagic { get; private set; } public int CombinedDexterity { get; private set; } public int CombinedIntelligence { get; private set; } public int CombinedConstitution { get; private set; } /// /// Combined group current health: sum of all members' current HP. /// public int CombinedHealth => members.Count > 0 ? members.Sum(a => a.Stats.CurrentHealth) : 0; /// /// Combined group max health: sum of all members' max HP. /// public int CombinedMaxHealth => members.Count > 0 ? members.Sum(a => a.Stats.MaxHealth) : 0; public int AvgStrength => members.Count > 0 ? (int)members.Average(a => a.Stats.Strength) : 0; public int AvgSpeed => members.Count > 0 ? (int)members.Average(a => a.Stats.Speed) : 0; public int AvgMagic => members.Count > 0 ? (int)members.Average(a => a.Stats.Magic) : 0; public int AvgDexterity => members.Count > 0 ? (int)members.Average(a => a.Stats.Dexterity) : 0; public int AvgIntelligence => members.Count > 0 ? (int)members.Average(a => a.Stats.Intelligence) : 0; public int AvgConstitution => members.Count > 0 ? (int)members.Average(a => a.Stats.Constitution) : 0; private readonly List members = new(); private static int nextGroupId = 1; public AgentGroup(AIAgent founder) { GroupId = nextGroupId++; AddMember(founder); } // ------------------------------------------------------------------ // // Membership // // ------------------------------------------------------------------ // /// /// Attempt to add a new agent. Returns true if accepted. /// Chance of acceptance drops with group size so large groups are /// harder to join. /// public bool TryAddMember(AIAgent candidate) { if (IsFull) return false; if (members.Contains(candidate)) return false; // Base acceptance roll – bigger group = lower chance to accept new member. // Size 1 → 100%, size 8 → ~11% float acceptanceChance = 1f / members.Count; // High INT candidate is better at selling themselves to the group; // high INT leader is more selective (slightly lower base chance but // we already reward INT via groupUpAffinity in the caller). acceptanceChance *= Mathf.Lerp(0.8f, 1.2f, candidate.GroupUpAffinity); if (Random.value > acceptanceChance) return false; AddMember(candidate); return true; } private void AddMember(AIAgent agent) { members.Add(agent); agent.JoinGroup(this); RecalcStats(); UpdateVisual(); Debug.Log($"[Group {GroupId}] {agent.AgentName} joined. Size now {members.Count}"); } public void RemoveMember(AIAgent agent) { if (!members.Contains(agent)) return; bool wasLeader = agent == Leader; members.Remove(agent); agent.LeaveGroup(); RecalcStats(); if (members.Count == 0) { // Group dissolved DestroyVisual(); return; } if (wasLeader) { // Promote next member; move visual to new leader Debug.Log($"[Group {GroupId}] Leader left, {Leader.AgentName} is the new leader."); } UpdateVisual(); Debug.Log($"[Group {GroupId}] {agent.AgentName} left. Size now {members.Count}"); } // ------------------------------------------------------------------ // // Stats // // ------------------------------------------------------------------ // private void RecalcStats() { if (members.Count == 0) return; CombinedStrength = members.Max(a => a.Stats.Strength); CombinedSpeed = members.Max(a => a.Stats.Speed); CombinedMagic = members.Max(a => a.Stats.Magic); CombinedDexterity = members.Max(a => a.Stats.Dexterity); CombinedIntelligence = members.Max(a => a.Stats.Intelligence); CombinedConstitution = members.Max(a => a.Stats.Constitution); } // ------------------------------------------------------------------ // // Visual // // ------------------------------------------------------------------ // private AgentGroupVisual visual; private void UpdateVisual() { if (Leader == null) return; if (visual == null) { visual = Leader.gameObject.AddComponent(); visual.Init(this); } else { visual.Refresh(); } } private void DestroyVisual() { if (visual != null) { Object.Destroy(visual); visual = null; } } }