1
0

ShowFactionIconsSystem.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. using Content.Shared.Overlays;
  2. using Robust.Shared.Player;
  3. using Content.Shared.Civ14.CivTDMFactions;
  4. using Robust.Shared.Random; // Added for IRobustRandom
  5. using System.Linq;
  6. using Content.Shared.NPC.Components; // Added for LINQ
  7. namespace Content.Server.Overlays
  8. {
  9. /// <summary>
  10. /// Server-side system for managing faction and squad icon assignments.
  11. /// Inherits core logic from SharedFactionIconsSystem.
  12. /// </summary>
  13. public sealed class FactionIconsSystem : SharedFactionIconsSystem
  14. {
  15. [Dependency] private readonly IRobustRandom _random = default!;
  16. [Dependency] private readonly EntityManager _entityManager = default!;
  17. public override void Initialize()
  18. {
  19. base.Initialize();
  20. }
  21. /// <summary>
  22. /// Attempts to assign a player entity to a squad within a specific CivFaction,
  23. /// balancing members and managing sergeant roles per CivFaction.
  24. /// This can be called by other server systems (e.g., role assignment, admin commands).
  25. /// </summary>
  26. /// <param name="playerUid">The entity UID of the player.</param>
  27. /// <param name="playerAssignedCivFactionId">The ID of the CivFaction the player belongs to (e.g., Faction1Id from CivTDMFactionsComponent).</param>
  28. /// <param name="wantsToBeSergeant">Whether the player desires a sergeant role.</param>
  29. /// <param name="sfiComponent">The player's ShowFactionIconsComponent.</param>
  30. /// <returns>True if successfully assigned, false otherwise.</returns>
  31. public bool AttemptAssignPlayerToSquad(EntityUid playerUid, string playerAssignedCivFactionId, bool wantsToBeSergeant, ShowFactionIconsComponent? sfiComponent = null)
  32. {
  33. if (!Resolve(playerUid, ref sfiComponent, logMissing: false))
  34. {
  35. Log.Warning($"Player {ToPrettyString(playerUid)} does not have ShowFactionIconsComponent. Cannot assign to squad.");
  36. return false;
  37. }
  38. if (TryComp<ShowFactionIconsComponent>(playerUid, out var factMem))
  39. {
  40. if (factMem.AssignSquad == false)
  41. {
  42. return false;
  43. }
  44. }
  45. // Get the CivTDMFactionsComponent
  46. CivTDMFactionsComponent? civTDMComp = null;
  47. var civQuery = _entityManager.EntityQueryEnumerator<CivTDMFactionsComponent>();
  48. if (civQuery.MoveNext(out _, out civTDMComp))
  49. {
  50. // Ensure Faction IDs are set
  51. if (civTDMComp.Faction1Id == null || civTDMComp.Faction2Id == null)
  52. {
  53. Log.Error("CivTDMFactionsComponent Faction1Id or Faction2Id is not set. Cannot assign squads.");
  54. return false;
  55. }
  56. }
  57. else
  58. {
  59. Log.Error("CivTDMFactionsComponent not found. Cannot assign squads.");
  60. return false;
  61. }
  62. string? targetSquadNameKey = null; // e.g., "Alpha", "Bravo"
  63. bool assignAsSergeantInCall = wantsToBeSergeant;
  64. string targetCivFactionIdForAssignment = playerAssignedCivFactionId;
  65. if (wantsToBeSergeant)
  66. {
  67. var squadsInTargetCivFaction = GetCivFactionSquads(civTDMComp, targetCivFactionIdForAssignment);
  68. targetSquadNameKey = FindBestSquadForRole(squadsInTargetCivFaction, true);
  69. if (targetSquadNameKey == null) // No suitable squad for sergeant in target CivFaction
  70. {
  71. assignAsSergeantInCall = false; // Try to assign as member in their original faction
  72. targetCivFactionIdForAssignment = playerAssignedCivFactionId; // Revert to player's own faction
  73. }
  74. }
  75. if (!assignAsSergeantInCall) // Assigning as member (either initially or fallback)
  76. {
  77. targetCivFactionIdForAssignment = playerAssignedCivFactionId; // Ensure assignment is to player's own faction
  78. var squadsInPlayerCivFaction = GetCivFactionSquads(civTDMComp, playerAssignedCivFactionId);
  79. targetSquadNameKey = FindBestSquadForRole(squadsInPlayerCivFaction, false);
  80. }
  81. if (targetSquadNameKey == null)
  82. {
  83. Log.Info($"Could not find a suitable squad for {ToPrettyString(playerUid)} in CivFaction {targetCivFactionIdForAssignment} as {(assignAsSergeantInCall ? "Sergeant" : "Member")}.");
  84. return false;
  85. }
  86. // Store old state for count updates
  87. var oldSquadIcon = sfiComponent.SquadIcon;
  88. var oldBelongsToCivFaction = sfiComponent.BelongsToCivFactionId;
  89. bool success = base.TryAssignToSquad(playerUid, targetSquadNameKey, assignAsSergeantInCall, sfiComponent);
  90. if (success)
  91. {
  92. sfiComponent.BelongsToCivFactionId = targetCivFactionIdForAssignment; // Update player's CivFaction if it changed
  93. Dirty(playerUid, sfiComponent);
  94. RecalculateAllCivFactionSquadCounts(civTDMComp); // Recalculate counts after assignment
  95. Log.Info($"Successfully assigned {ToPrettyString(playerUid)} to squad {targetSquadNameKey} in CivFaction {targetCivFactionIdForAssignment} as {(assignAsSergeantInCall ? "Sergeant" : "Member")}. New icon: {sfiComponent.SquadIcon}");
  96. }
  97. else
  98. {
  99. Log.Info($"Failed to assign {ToPrettyString(playerUid)} to squad {targetSquadNameKey} via SharedFactionIconsSystem.");
  100. // Revert BelongsToCivFactionId if it was tentatively changed for sergeant assignment
  101. if (wantsToBeSergeant && targetCivFactionIdForAssignment != playerAssignedCivFactionId)
  102. {
  103. sfiComponent.BelongsToCivFactionId = playerAssignedCivFactionId;
  104. }
  105. }
  106. return success;
  107. }
  108. private Dictionary<string, int> CountSergeantsPerCivFaction(CivTDMFactionsComponent civTDMComp)
  109. {
  110. var counts = new Dictionary<string, int>();
  111. if (civTDMComp.Faction1Id != null) counts[civTDMComp.Faction1Id] = 0;
  112. if (civTDMComp.Faction2Id != null) counts[civTDMComp.Faction2Id] = 0;
  113. var query = _entityManager.EntityQueryEnumerator<ShowFactionIconsComponent>();
  114. while (query.MoveNext(out _, out var sfiComp))
  115. {
  116. if (sfiComp.BelongsToCivFactionId != null &&
  117. sfiComp.AssignSquad && // Must be assigned to a squad
  118. sfiComp.IsSergeantInSquad && // Must be a sergeant
  119. counts.ContainsKey(sfiComp.BelongsToCivFactionId))
  120. {
  121. counts[sfiComp.BelongsToCivFactionId]++;
  122. }
  123. }
  124. return counts;
  125. }
  126. private Dictionary<string, SquadData>? GetCivFactionSquads(CivTDMFactionsComponent civTDMComp, string civFactionId)
  127. {
  128. if (civFactionId == civTDMComp.Faction1Id)
  129. return civTDMComp.Faction1Squads;
  130. if (civFactionId == civTDMComp.Faction2Id)
  131. return civTDMComp.Faction2Squads;
  132. return null;
  133. }
  134. private string? FindBestSquadForRole(Dictionary<string, SquadData>? squadsData, bool findForSergeant)
  135. {
  136. if (squadsData == null)
  137. return null;
  138. string? bestSquad = null;
  139. int minMembers = int.MaxValue;
  140. foreach (var (squadNameKey, squadData) in squadsData.OrderBy(x => _random.Next())) // Randomize tie-breaking
  141. {
  142. if (!Squads.TryGetValue(squadNameKey, out var squadConfig))
  143. continue; // This squad type isn't defined in SharedFactionIconsSystem
  144. int currentTotal = squadData.SergeantCount + squadData.MemberCount;
  145. if (currentTotal >= squadConfig.MaxSize)
  146. continue; // Squad is full
  147. if (findForSergeant)
  148. {
  149. if (squadData.SergeantCount == 0) // Slot for sergeant is open
  150. {
  151. if (currentTotal < minMembers) // Prefer less populated squads for sergeants too
  152. {
  153. minMembers = currentTotal;
  154. bestSquad = squadNameKey;
  155. }
  156. }
  157. }
  158. else // Finding for member
  159. {
  160. if (currentTotal < minMembers)
  161. {
  162. minMembers = currentTotal;
  163. bestSquad = squadNameKey;
  164. }
  165. }
  166. }
  167. return bestSquad;
  168. }
  169. public void RecalculateAllCivFactionSquadCounts(CivTDMFactionsComponent civTDMComp)
  170. {
  171. // Reset counts
  172. foreach (var squadDataDict in new[] { civTDMComp.Faction1Squads, civTDMComp.Faction2Squads })
  173. {
  174. foreach (var squadData in squadDataDict.Values)
  175. {
  176. squadData.MemberCount = 0;
  177. squadData.SergeantCount = 0;
  178. }
  179. }
  180. var memberIconIds = Squads.ToDictionary(kvp => kvp.Value.MemberIconId, kvp => kvp.Key);
  181. var sergeantIconIds = Squads.ToDictionary(kvp => kvp.Value.SergeantIconId, kvp => kvp.Key);
  182. var query = _entityManager.EntityQueryEnumerator<ShowFactionIconsComponent>();
  183. while (query.MoveNext(out _, out var sfiComp))
  184. {
  185. if (sfiComp.SquadIcon == null || sfiComp.BelongsToCivFactionId == null || sfiComp.AssignSquad == false)
  186. continue;
  187. var targetSquadsDict = GetCivFactionSquads(civTDMComp, sfiComp.BelongsToCivFactionId);
  188. if (targetSquadsDict == null)
  189. continue;
  190. if (memberIconIds.TryGetValue(sfiComp.SquadIcon, out var squadNameKeySgt) && targetSquadsDict.TryGetValue(squadNameKeySgt, out var squadData))
  191. {
  192. if (sfiComp.JobIcon == "JobIconISgt")
  193. {
  194. squadData.SergeantCount++;
  195. }
  196. else
  197. {
  198. squadData.MemberCount++;
  199. }
  200. }
  201. }
  202. // Mark CivTDMFactionsComponent as dirty. Need to get the MapUid from the TransformComponent.
  203. if (_entityManager.TryGetComponent<TransformComponent>(civTDMComp.Owner, out var xform) && xform.MapUid.HasValue)
  204. {
  205. Dirty(xform.MapUid.Value, civTDMComp);
  206. }
  207. }
  208. }
  209. }