CivFactionsSystem.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
  1. using Content.Server.Chat.Systems;
  2. using Content.Shared.Civ14.CivFactions;
  3. using Content.Shared.Popups; // Use Shared Popups
  4. using Robust.Server.Player;
  5. using Robust.Shared.Player; // Required for Filter, ICommonSession
  6. using Robust.Shared.Network; // Required for NetUserId, INetChannel
  7. using System.Linq;
  8. using Content.Server.Chat.Managers;
  9. using Content.Shared.Chat;
  10. using Robust.Shared.Map.Components;
  11. using Robust.Shared.GameObjects; // Required for EntityUid
  12. using Content.Server.GameTicking;
  13. namespace Content.Server.Civ14.CivFactions;
  14. public sealed class CivFactionsSystem : EntitySystem
  15. {
  16. [Dependency] private readonly IPlayerManager _playerManager = default!;
  17. [Dependency] private readonly ChatSystem _chatSystem = default!;
  18. [Dependency] private readonly IChatManager _chatManager = default!;
  19. [Dependency] private readonly SharedPopupSystem _popupSystem = default!;
  20. [Dependency] private readonly IEntityManager _entityManager = default!; // Use IEntityManager
  21. [Dependency] private readonly GameTicker _gameTicker = default!;
  22. private EntityUid? _factionsEntity;
  23. private CivFactionsComponent? _factionsComponent;
  24. /// <summary>
  25. /// Initialises the faction system, ensuring the global factions component exists and subscribing to relevant network events for faction management.
  26. /// </summary>
  27. public override void Initialize()
  28. {
  29. base.Initialize();
  30. // Attempt to find the global factions component on startup
  31. EnsureFactionsComponent();
  32. // Subscribe to network events
  33. SubscribeNetworkEvent<CreateFactionRequestEvent>(OnCreateFactionRequest);
  34. SubscribeNetworkEvent<LeaveFactionRequestEvent>(OnLeaveFactionRequest);
  35. SubscribeNetworkEvent<InviteFactionRequestEvent>(OnInviteFactionRequest);
  36. SubscribeNetworkEvent<AcceptFactionInviteEvent>(OnAcceptFactionInvite);
  37. }
  38. /// <summary>
  39. /// Performs cleanup operations when the faction system is shut down.
  40. /// </summary>
  41. public override void Shutdown()
  42. {
  43. base.Shutdown();
  44. }
  45. /// <summary>
  46. /// Ensures the global CivFactionsComponent exists and caches its reference.
  47. /// Creates one if necessary (e.g., attached to the first map found).
  48. /// <summary>
  49. /// Ensures that a global CivFactionsComponent exists and is cached, creating one on a map entity if necessary.
  50. /// </summary>
  51. /// <returns>True if the factions component is available and cached; false if it could not be ensured.</returns>
  52. private bool EnsureFactionsComponent()
  53. {
  54. if (!_gameTicker.IsGameRuleActive("FactionRule"))
  55. {
  56. Log.Info($"Factions are disabled on this map.");
  57. return false;
  58. }
  59. if (_factionsComponent != null && !_entityManager.Deleted(_factionsEntity))
  60. return true; // Already cached and valid
  61. var query = EntityQueryEnumerator<CivFactionsComponent>();
  62. if (query.MoveNext(out var owner, out var comp))
  63. {
  64. _factionsEntity = owner;
  65. _factionsComponent = comp;
  66. Log.Info($"Found existing CivFactionsComponent on entity {_entityManager.ToPrettyString(owner)}");
  67. return true;
  68. }
  69. else
  70. {
  71. var mapQuery = EntityQueryEnumerator<MapComponent>();
  72. if (mapQuery.MoveNext(out var mapUid, out _))
  73. {
  74. Log.Info($"No CivFactionsComponent found. Creating one on map entity {_entityManager.ToPrettyString(mapUid)}.");
  75. _factionsComponent = _entityManager.AddComponent<CivFactionsComponent>(mapUid);
  76. _factionsEntity = mapUid;
  77. return true;
  78. }
  79. else
  80. {
  81. Log.Error("Could not find CivFactionsComponent and no map entity found to attach a new one!");
  82. _factionsComponent = null;
  83. _factionsEntity = null;
  84. return false;
  85. }
  86. }
  87. }
  88. /// <summary>
  89. /// Handles a request to create a new faction, validating the faction name and player status, and adds the player as the initial member if successful.
  90. /// </summary>
  91. private void OnCreateFactionRequest(CreateFactionRequestEvent msg, EntitySessionEventArgs args)
  92. {
  93. if (!EnsureFactionsComponent())
  94. {
  95. return;
  96. }
  97. var sourceEntity = _factionsEntity ?? EntityUid.Invalid; // Use Invalid if component entity is somehow null
  98. if (_factionsComponent == null || _factionsEntity == null)
  99. {
  100. Log.Error($"Player {args.SenderSession.Name} tried to create faction, but CivFactionsComponent is missing!");
  101. // FIX: Correct arguments for ChatMessageToOne
  102. var errorMsg = "Cannot create faction: Server configuration error.";
  103. _chatManager.ChatMessageToOne(ChatChannel.Notifications, errorMsg, errorMsg, sourceEntity, false, args.SenderSession.Channel);
  104. return;
  105. }
  106. var playerSession = args.SenderSession;
  107. var playerId = playerSession.UserId.ToString();
  108. // Validation
  109. if (string.IsNullOrWhiteSpace(msg.FactionName) || msg.FactionName.Length > 32)
  110. {
  111. // FIX: Correct arguments for ChatMessageToOne
  112. var errorMsg = "Invalid faction name.";
  113. _chatManager.ChatMessageToOne(ChatChannel.Notifications, errorMsg, errorMsg, sourceEntity, false, playerSession.Channel);
  114. return;
  115. }
  116. if (IsPlayerInFaction(playerSession.UserId, out _))
  117. {
  118. // FIX: Correct arguments for ChatMessageToOne
  119. var errorMsg = "You are already in a faction.";
  120. _chatManager.ChatMessageToOne(ChatChannel.Notifications, errorMsg, errorMsg, sourceEntity, false, playerSession.Channel);
  121. return;
  122. }
  123. if (_factionsComponent.FactionList.Any(f => f.FactionName.Equals(msg.FactionName, StringComparison.OrdinalIgnoreCase)))
  124. {
  125. // FIX: Correct arguments for ChatMessageToOne
  126. var errorMsg = $"Faction name '{msg.FactionName}' is already taken.";
  127. _chatManager.ChatMessageToOne(ChatChannel.Notifications, errorMsg, errorMsg, sourceEntity, false, playerSession.Channel);
  128. return;
  129. }
  130. // Create the new faction component
  131. var newFaction = new FactionData // <-- Use FactionData
  132. {
  133. FactionName = msg.FactionName,
  134. FactionMembers = new List<string> { playerId }
  135. };
  136. _factionsComponent.FactionList.Add(newFaction);
  137. Dirty(_factionsEntity.Value, _factionsComponent);
  138. Log.Info($"Player {playerSession.Name} created faction '{msg.FactionName}'.");
  139. // Send confirmation message
  140. var confirmationMsg = $"Faction '{msg.FactionName}' created successfully.";
  141. _chatManager.ChatMessageToOne(ChatChannel.Notifications, confirmationMsg, confirmationMsg, sourceEntity, false, playerSession.Channel);
  142. // Notify the client their status changed
  143. var statusChangeEvent = new PlayerFactionStatusChangedEvent(true, newFaction.FactionName);
  144. RaiseNetworkEvent(statusChangeEvent, playerSession.Channel); // Target the specific player
  145. }
  146. /// <summary>
  147. /// Handles a player's request to leave their current faction, updating faction membership and notifying the player.
  148. /// </summary>
  149. private void OnLeaveFactionRequest(LeaveFactionRequestEvent msg, EntitySessionEventArgs args)
  150. {
  151. if (!EnsureFactionsComponent())
  152. {
  153. return;
  154. }
  155. var sourceEntity = _factionsEntity ?? EntityUid.Invalid;
  156. if (_factionsComponent == null || _factionsEntity == null) return;
  157. var playerSession = args.SenderSession;
  158. var playerId = playerSession.UserId.ToString();
  159. if (!TryGetPlayerFaction(playerSession.UserId, out var faction))
  160. {
  161. // FIX: Correct arguments for ChatMessageToOne
  162. var errorMsg = "You are not in a faction.";
  163. _chatManager.ChatMessageToOne(ChatChannel.Notifications, errorMsg, errorMsg, sourceEntity, false, playerSession.Channel);
  164. return;
  165. }
  166. faction!.FactionMembers.Remove(playerId);
  167. Log.Info($"Player {playerSession.Name} left faction '{faction.FactionName}'.");
  168. // FIX: Correct arguments for ChatMessageToOne
  169. var confirmationMsg = $"You have left faction '{faction.FactionName}'.";
  170. _chatManager.ChatMessageToOne(ChatChannel.Notifications, confirmationMsg, confirmationMsg, sourceEntity, false, playerSession.Channel);
  171. if (faction.FactionMembers.Count == 0)
  172. {
  173. _factionsComponent.FactionList.Remove(faction);
  174. Log.Info($"Faction '{faction.FactionName}' disbanded as it became empty.");
  175. }
  176. Dirty(_factionsEntity.Value, _factionsComponent);
  177. // Notify the client their status changed
  178. var statusChangeEvent = new PlayerFactionStatusChangedEvent(false, null);
  179. RaiseNetworkEvent(statusChangeEvent, playerSession.Channel); // Target the specific player
  180. }
  181. /// <summary>
  182. /// Handles a request for a player to invite another player to their faction, performing validation and sending appropriate notifications and network events.
  183. /// </summary>
  184. private void OnInviteFactionRequest(InviteFactionRequestEvent msg, EntitySessionEventArgs args)
  185. {
  186. if (!EnsureFactionsComponent())
  187. {
  188. return;
  189. }
  190. var sourceEntity = _factionsEntity ?? EntityUid.Invalid;
  191. if (_factionsComponent == null || _factionsEntity == null) return;
  192. var inviterSession = args.SenderSession;
  193. var inviterId = inviterSession.UserId;
  194. if (!TryGetPlayerFaction(inviterId, out var inviterFaction))
  195. {
  196. // FIX: Correct arguments for ChatMessageToOne
  197. var errorMsg = "You must be in a faction to invite others.";
  198. _chatManager.ChatMessageToOne(ChatChannel.Notifications, errorMsg, errorMsg, sourceEntity, false, inviterSession.Channel);
  199. return;
  200. }
  201. if (!_playerManager.TryGetSessionById(msg.TargetPlayerUserId, out var targetSession))
  202. {
  203. // FIX: Correct arguments for ChatMessageToOne
  204. var errorMsg = "Could not find the player you tried to invite.";
  205. _chatManager.ChatMessageToOne(ChatChannel.Notifications, errorMsg, errorMsg, sourceEntity, false, inviterSession.Channel);
  206. return;
  207. }
  208. if (IsPlayerInFaction(msg.TargetPlayerUserId, out _))
  209. {
  210. // FIX: Correct arguments for ChatMessageToOne (to inviter)
  211. var inviterErrorMsg = $"{targetSession.Name} is already in a faction.";
  212. _chatManager.ChatMessageToOne(ChatChannel.Notifications, inviterErrorMsg, inviterErrorMsg, sourceEntity, false, inviterSession.Channel);
  213. // FIX: Correct arguments for ChatMessageToOne (to target)
  214. var targetErrorMsg = $"{inviterSession.Name} tried to invite you to '{inviterFaction!.FactionName}', but you are already in a faction.";
  215. _chatManager.ChatMessageToOne(ChatChannel.Notifications, targetErrorMsg, targetErrorMsg, sourceEntity, false, targetSession.Channel);
  216. return;
  217. }
  218. var offerEvent = new FactionInviteOfferEvent(inviterSession.Name, inviterFaction!.FactionName, inviterId);
  219. RaiseNetworkEvent(offerEvent, Filter.SinglePlayer(targetSession));
  220. // FIX: Correct arguments for ChatMessageToOne (confirmation to inviter)
  221. var inviterConfirmMsg = $"Invitation sent to {targetSession.Name}.";
  222. _chatManager.ChatMessageToOne(ChatChannel.Notifications, inviterConfirmMsg, inviterConfirmMsg, sourceEntity, false, inviterSession.Channel);
  223. // FIX: Correct arguments for ChatMessageToOne (notification to target)
  224. var targetNotifyMsg = $"{inviterSession.Name} has invited you to join the faction '{inviterFaction.FactionName}'. Check your chat or notifications.";
  225. _chatManager.ChatMessageToOne(ChatChannel.Notifications, targetNotifyMsg, targetNotifyMsg, sourceEntity, false, targetSession.Channel);
  226. Log.Info($"Player {inviterSession.Name} invited {targetSession.Name} to faction '{inviterFaction.FactionName}'.");
  227. }
  228. /// <summary>
  229. /// Handles a player's acceptance of a faction invitation, adding them to the specified faction and notifying them of the status change.
  230. /// </summary>
  231. private void OnAcceptFactionInvite(AcceptFactionInviteEvent msg, EntitySessionEventArgs args)
  232. {
  233. if (!EnsureFactionsComponent())
  234. {
  235. return;
  236. }
  237. var sourceEntity = _factionsEntity ?? EntityUid.Invalid;
  238. if (_factionsComponent == null || _factionsEntity == null) return;
  239. var accepterSession = args.SenderSession;
  240. var accepterId = accepterSession.UserId;
  241. var accepterIdStr = accepterId.ToString();
  242. if (IsPlayerInFaction(accepterId, out var currentFaction))
  243. {
  244. // FIX: Correct arguments for ChatMessageToOne
  245. var errorMsg = $"You cannot accept the invite, you are already in faction '{currentFaction!.FactionName}'.";
  246. _chatManager.ChatMessageToOne(ChatChannel.Notifications, errorMsg, errorMsg, sourceEntity, false, accepterSession.Channel);
  247. return;
  248. }
  249. var targetFaction = _factionsComponent.FactionList.FirstOrDefault(f => f.FactionName.Equals(msg.FactionName, StringComparison.OrdinalIgnoreCase));
  250. if (targetFaction == null)
  251. {
  252. // FIX: Correct arguments for ChatMessageToOne
  253. var errorMsg = $"The faction '{msg.FactionName}' no longer exists.";
  254. _chatManager.ChatMessageToOne(ChatChannel.Notifications, errorMsg, errorMsg, sourceEntity, false, accepterSession.Channel);
  255. return;
  256. }
  257. targetFaction.FactionMembers.Add(accepterIdStr);
  258. Dirty(_factionsEntity.Value, _factionsComponent);
  259. // FIX: Correct arguments for ChatMessageToOne
  260. var confirmationMsg = $"You have joined faction '{targetFaction.FactionName}'.";
  261. _chatManager.ChatMessageToOne(ChatChannel.Notifications, confirmationMsg, confirmationMsg, sourceEntity, false, accepterSession.Channel);
  262. Log.Info($"Player {accepterSession.Name} accepted invite and joined faction '{targetFaction.FactionName}'.");
  263. // Notify the client their status changed
  264. var statusChangeEvent = new PlayerFactionStatusChangedEvent(true, targetFaction.FactionName);
  265. RaiseNetworkEvent(statusChangeEvent, accepterSession.Channel); // Target the specific player
  266. }
  267. /// <summary>
  268. /// Determines whether the specified player is a member of any faction.
  269. /// </summary>
  270. /// <param name="userId">The user ID of the player to check.</param>
  271. /// <param name="faction">
  272. /// When this method returns, contains the faction the player belongs to if found; otherwise, null.
  273. /// </param>
  274. /// <returns>True if the player is in a faction; otherwise, false.</returns>
  275. public bool IsPlayerInFaction(NetUserId userId, out FactionData? faction) // <-- Use FactionData
  276. {
  277. faction = null;
  278. if (_factionsComponent == null)
  279. return false;
  280. var playerIdStr = userId.ToString();
  281. foreach (var f in _factionsComponent.FactionList)
  282. {
  283. if (f.FactionMembers.Contains(playerIdStr))
  284. {
  285. faction = f;
  286. return true;
  287. }
  288. }
  289. return false;
  290. }
  291. /// <summary>
  292. /// Attempts to find the faction that the specified player belongs to.
  293. /// </summary>
  294. /// <param name="userId">The user ID of the player.</param>
  295. /// <param name="faction">When this method returns, contains the player's faction if found; otherwise, null.</param>
  296. /// <returns>True if the player is a member of a faction; otherwise, false.</returns>
  297. public bool TryGetPlayerFaction(NetUserId userId, out FactionData? faction) // <-- Use FactionData
  298. {
  299. return IsPlayerInFaction(userId, out faction);
  300. }
  301. }