using Content.Shared.Overlays; using Robust.Shared.Player; using Content.Shared.Civ14.CivTDMFactions; using Robust.Shared.Random; // Added for IRobustRandom using System.Linq; using Content.Shared.NPC.Components; // Added for LINQ namespace Content.Server.Overlays { /// /// Server-side system for managing faction and squad icon assignments. /// Inherits core logic from SharedFactionIconsSystem. /// public sealed class FactionIconsSystem : SharedFactionIconsSystem { [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly EntityManager _entityManager = default!; public override void Initialize() { base.Initialize(); } /// /// Attempts to assign a player entity to a squad within a specific CivFaction, /// balancing members and managing sergeant roles per CivFaction. /// This can be called by other server systems (e.g., role assignment, admin commands). /// /// The entity UID of the player. /// The ID of the CivFaction the player belongs to (e.g., Faction1Id from CivTDMFactionsComponent). /// Whether the player desires a sergeant role. /// The player's ShowFactionIconsComponent. /// True if successfully assigned, false otherwise. public bool AttemptAssignPlayerToSquad(EntityUid playerUid, string playerAssignedCivFactionId, bool wantsToBeSergeant, ShowFactionIconsComponent? sfiComponent = null) { if (!Resolve(playerUid, ref sfiComponent, logMissing: false)) { Log.Warning($"Player {ToPrettyString(playerUid)} does not have ShowFactionIconsComponent. Cannot assign to squad."); return false; } if (TryComp(playerUid, out var factMem)) { if (factMem.AssignSquad == false) { return false; } } // Get the CivTDMFactionsComponent CivTDMFactionsComponent? civTDMComp = null; var civQuery = _entityManager.EntityQueryEnumerator(); if (civQuery.MoveNext(out _, out civTDMComp)) { // Ensure Faction IDs are set if (civTDMComp.Faction1Id == null || civTDMComp.Faction2Id == null) { Log.Error("CivTDMFactionsComponent Faction1Id or Faction2Id is not set. Cannot assign squads."); return false; } } else { Log.Error("CivTDMFactionsComponent not found. Cannot assign squads."); return false; } string? targetSquadNameKey = null; // e.g., "Alpha", "Bravo" bool assignAsSergeantInCall = wantsToBeSergeant; string targetCivFactionIdForAssignment = playerAssignedCivFactionId; if (wantsToBeSergeant) { var squadsInTargetCivFaction = GetCivFactionSquads(civTDMComp, targetCivFactionIdForAssignment); targetSquadNameKey = FindBestSquadForRole(squadsInTargetCivFaction, true); if (targetSquadNameKey == null) // No suitable squad for sergeant in target CivFaction { assignAsSergeantInCall = false; // Try to assign as member in their original faction targetCivFactionIdForAssignment = playerAssignedCivFactionId; // Revert to player's own faction } } if (!assignAsSergeantInCall) // Assigning as member (either initially or fallback) { targetCivFactionIdForAssignment = playerAssignedCivFactionId; // Ensure assignment is to player's own faction var squadsInPlayerCivFaction = GetCivFactionSquads(civTDMComp, playerAssignedCivFactionId); targetSquadNameKey = FindBestSquadForRole(squadsInPlayerCivFaction, false); } if (targetSquadNameKey == null) { Log.Info($"Could not find a suitable squad for {ToPrettyString(playerUid)} in CivFaction {targetCivFactionIdForAssignment} as {(assignAsSergeantInCall ? "Sergeant" : "Member")}."); return false; } // Store old state for count updates var oldSquadIcon = sfiComponent.SquadIcon; var oldBelongsToCivFaction = sfiComponent.BelongsToCivFactionId; bool success = base.TryAssignToSquad(playerUid, targetSquadNameKey, assignAsSergeantInCall, sfiComponent); if (success) { sfiComponent.BelongsToCivFactionId = targetCivFactionIdForAssignment; // Update player's CivFaction if it changed Dirty(playerUid, sfiComponent); RecalculateAllCivFactionSquadCounts(civTDMComp); // Recalculate counts after assignment Log.Info($"Successfully assigned {ToPrettyString(playerUid)} to squad {targetSquadNameKey} in CivFaction {targetCivFactionIdForAssignment} as {(assignAsSergeantInCall ? "Sergeant" : "Member")}. New icon: {sfiComponent.SquadIcon}"); } else { Log.Info($"Failed to assign {ToPrettyString(playerUid)} to squad {targetSquadNameKey} via SharedFactionIconsSystem."); // Revert BelongsToCivFactionId if it was tentatively changed for sergeant assignment if (wantsToBeSergeant && targetCivFactionIdForAssignment != playerAssignedCivFactionId) { sfiComponent.BelongsToCivFactionId = playerAssignedCivFactionId; } } return success; } private Dictionary CountSergeantsPerCivFaction(CivTDMFactionsComponent civTDMComp) { var counts = new Dictionary(); if (civTDMComp.Faction1Id != null) counts[civTDMComp.Faction1Id] = 0; if (civTDMComp.Faction2Id != null) counts[civTDMComp.Faction2Id] = 0; var query = _entityManager.EntityQueryEnumerator(); while (query.MoveNext(out _, out var sfiComp)) { if (sfiComp.BelongsToCivFactionId != null && sfiComp.AssignSquad && // Must be assigned to a squad sfiComp.IsSergeantInSquad && // Must be a sergeant counts.ContainsKey(sfiComp.BelongsToCivFactionId)) { counts[sfiComp.BelongsToCivFactionId]++; } } return counts; } private Dictionary? GetCivFactionSquads(CivTDMFactionsComponent civTDMComp, string civFactionId) { if (civFactionId == civTDMComp.Faction1Id) return civTDMComp.Faction1Squads; if (civFactionId == civTDMComp.Faction2Id) return civTDMComp.Faction2Squads; return null; } private string? FindBestSquadForRole(Dictionary? squadsData, bool findForSergeant) { if (squadsData == null) return null; string? bestSquad = null; int minMembers = int.MaxValue; foreach (var (squadNameKey, squadData) in squadsData.OrderBy(x => _random.Next())) // Randomize tie-breaking { if (!Squads.TryGetValue(squadNameKey, out var squadConfig)) continue; // This squad type isn't defined in SharedFactionIconsSystem int currentTotal = squadData.SergeantCount + squadData.MemberCount; if (currentTotal >= squadConfig.MaxSize) continue; // Squad is full if (findForSergeant) { if (squadData.SergeantCount == 0) // Slot for sergeant is open { if (currentTotal < minMembers) // Prefer less populated squads for sergeants too { minMembers = currentTotal; bestSquad = squadNameKey; } } } else // Finding for member { if (currentTotal < minMembers) { minMembers = currentTotal; bestSquad = squadNameKey; } } } return bestSquad; } public void RecalculateAllCivFactionSquadCounts(CivTDMFactionsComponent civTDMComp) { // Reset counts foreach (var squadDataDict in new[] { civTDMComp.Faction1Squads, civTDMComp.Faction2Squads }) { foreach (var squadData in squadDataDict.Values) { squadData.MemberCount = 0; squadData.SergeantCount = 0; } } var memberIconIds = Squads.ToDictionary(kvp => kvp.Value.MemberIconId, kvp => kvp.Key); var sergeantIconIds = Squads.ToDictionary(kvp => kvp.Value.SergeantIconId, kvp => kvp.Key); var query = _entityManager.EntityQueryEnumerator(); while (query.MoveNext(out _, out var sfiComp)) { if (sfiComp.SquadIcon == null || sfiComp.BelongsToCivFactionId == null || sfiComp.AssignSquad == false) continue; var targetSquadsDict = GetCivFactionSquads(civTDMComp, sfiComp.BelongsToCivFactionId); if (targetSquadsDict == null) continue; if (memberIconIds.TryGetValue(sfiComp.SquadIcon, out var squadNameKeySgt) && targetSquadsDict.TryGetValue(squadNameKeySgt, out var squadData)) { if (sfiComp.JobIcon == "JobIconISgt") { squadData.SergeantCount++; } else { squadData.MemberCount++; } } } // Mark CivTDMFactionsComponent as dirty. Need to get the MapUid from the TransformComponent. if (_entityManager.TryGetComponent(civTDMComp.Owner, out var xform) && xform.MapUid.HasValue) { Dirty(xform.MapUid.Value, civTDMComp); } } } }