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