using Content.Server.Chat.Systems; using Content.Shared.Civ14.CivFactions; using Content.Shared.Popups; // Use Shared Popups using Robust.Server.Player; using Robust.Shared.Player; // Required for Filter, ICommonSession using Robust.Shared.Network; // Required for NetUserId, INetChannel using System.Linq; using Content.Server.Chat.Managers; using Content.Shared.Chat; using Robust.Shared.Map.Components; using Robust.Shared.GameObjects; // Required for EntityUid using Content.Server.GameTicking; namespace Content.Server.Civ14.CivFactions; public sealed class CivFactionsSystem : EntitySystem { [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly ChatSystem _chatSystem = default!; [Dependency] private readonly IChatManager _chatManager = default!; [Dependency] private readonly SharedPopupSystem _popupSystem = default!; [Dependency] private readonly IEntityManager _entityManager = default!; // Use IEntityManager [Dependency] private readonly GameTicker _gameTicker = default!; private EntityUid? _factionsEntity; private CivFactionsComponent? _factionsComponent; /// /// Initialises the faction system, ensuring the global factions component exists and subscribing to relevant network events for faction management. /// public override void Initialize() { base.Initialize(); // Attempt to find the global factions component on startup EnsureFactionsComponent(); // Subscribe to network events SubscribeNetworkEvent(OnCreateFactionRequest); SubscribeNetworkEvent(OnLeaveFactionRequest); SubscribeNetworkEvent(OnInviteFactionRequest); SubscribeNetworkEvent(OnAcceptFactionInvite); } /// /// Performs cleanup operations when the faction system is shut down. /// public override void Shutdown() { base.Shutdown(); } /// /// Ensures the global CivFactionsComponent exists and caches its reference. /// Creates one if necessary (e.g., attached to the first map found). /// /// Ensures that a global CivFactionsComponent exists and is cached, creating one on a map entity if necessary. /// /// True if the factions component is available and cached; false if it could not be ensured. private bool EnsureFactionsComponent() { if (!_gameTicker.IsGameRuleActive("FactionRule")) { Log.Info($"Factions are disabled on this map."); return false; } if (_factionsComponent != null && !_entityManager.Deleted(_factionsEntity)) return true; // Already cached and valid var query = EntityQueryEnumerator(); if (query.MoveNext(out var owner, out var comp)) { _factionsEntity = owner; _factionsComponent = comp; Log.Info($"Found existing CivFactionsComponent on entity {_entityManager.ToPrettyString(owner)}"); return true; } else { var mapQuery = EntityQueryEnumerator(); if (mapQuery.MoveNext(out var mapUid, out _)) { Log.Info($"No CivFactionsComponent found. Creating one on map entity {_entityManager.ToPrettyString(mapUid)}."); _factionsComponent = _entityManager.AddComponent(mapUid); _factionsEntity = mapUid; return true; } else { Log.Error("Could not find CivFactionsComponent and no map entity found to attach a new one!"); _factionsComponent = null; _factionsEntity = null; return false; } } } /// /// 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. /// private void OnCreateFactionRequest(CreateFactionRequestEvent msg, EntitySessionEventArgs args) { if (!EnsureFactionsComponent()) { return; } var sourceEntity = _factionsEntity ?? EntityUid.Invalid; // Use Invalid if component entity is somehow null if (_factionsComponent == null || _factionsEntity == null) { Log.Error($"Player {args.SenderSession.Name} tried to create faction, but CivFactionsComponent is missing!"); // FIX: Correct arguments for ChatMessageToOne var errorMsg = "Cannot create faction: Server configuration error."; _chatManager.ChatMessageToOne(ChatChannel.Notifications, errorMsg, errorMsg, sourceEntity, false, args.SenderSession.Channel); return; } var playerSession = args.SenderSession; var playerId = playerSession.UserId.ToString(); // Validation if (string.IsNullOrWhiteSpace(msg.FactionName) || msg.FactionName.Length > 32) { // FIX: Correct arguments for ChatMessageToOne var errorMsg = "Invalid faction name."; _chatManager.ChatMessageToOne(ChatChannel.Notifications, errorMsg, errorMsg, sourceEntity, false, playerSession.Channel); return; } if (IsPlayerInFaction(playerSession.UserId, out _)) { // FIX: Correct arguments for ChatMessageToOne var errorMsg = "You are already in a faction."; _chatManager.ChatMessageToOne(ChatChannel.Notifications, errorMsg, errorMsg, sourceEntity, false, playerSession.Channel); return; } if (_factionsComponent.FactionList.Any(f => f.FactionName.Equals(msg.FactionName, StringComparison.OrdinalIgnoreCase))) { // FIX: Correct arguments for ChatMessageToOne var errorMsg = $"Faction name '{msg.FactionName}' is already taken."; _chatManager.ChatMessageToOne(ChatChannel.Notifications, errorMsg, errorMsg, sourceEntity, false, playerSession.Channel); return; } // Create the new faction component var newFaction = new FactionData // <-- Use FactionData { FactionName = msg.FactionName, FactionMembers = new List { playerId } }; _factionsComponent.FactionList.Add(newFaction); Dirty(_factionsEntity.Value, _factionsComponent); Log.Info($"Player {playerSession.Name} created faction '{msg.FactionName}'."); // Send confirmation message var confirmationMsg = $"Faction '{msg.FactionName}' created successfully."; _chatManager.ChatMessageToOne(ChatChannel.Notifications, confirmationMsg, confirmationMsg, sourceEntity, false, playerSession.Channel); // Notify the client their status changed var statusChangeEvent = new PlayerFactionStatusChangedEvent(true, newFaction.FactionName); RaiseNetworkEvent(statusChangeEvent, playerSession.Channel); // Target the specific player } /// /// Handles a player's request to leave their current faction, updating faction membership and notifying the player. /// private void OnLeaveFactionRequest(LeaveFactionRequestEvent msg, EntitySessionEventArgs args) { if (!EnsureFactionsComponent()) { return; } var sourceEntity = _factionsEntity ?? EntityUid.Invalid; if (_factionsComponent == null || _factionsEntity == null) return; var playerSession = args.SenderSession; var playerId = playerSession.UserId.ToString(); if (!TryGetPlayerFaction(playerSession.UserId, out var faction)) { // FIX: Correct arguments for ChatMessageToOne var errorMsg = "You are not in a faction."; _chatManager.ChatMessageToOne(ChatChannel.Notifications, errorMsg, errorMsg, sourceEntity, false, playerSession.Channel); return; } faction!.FactionMembers.Remove(playerId); Log.Info($"Player {playerSession.Name} left faction '{faction.FactionName}'."); // FIX: Correct arguments for ChatMessageToOne var confirmationMsg = $"You have left faction '{faction.FactionName}'."; _chatManager.ChatMessageToOne(ChatChannel.Notifications, confirmationMsg, confirmationMsg, sourceEntity, false, playerSession.Channel); if (faction.FactionMembers.Count == 0) { _factionsComponent.FactionList.Remove(faction); Log.Info($"Faction '{faction.FactionName}' disbanded as it became empty."); } Dirty(_factionsEntity.Value, _factionsComponent); // Notify the client their status changed var statusChangeEvent = new PlayerFactionStatusChangedEvent(false, null); RaiseNetworkEvent(statusChangeEvent, playerSession.Channel); // Target the specific player } /// /// Handles a request for a player to invite another player to their faction, performing validation and sending appropriate notifications and network events. /// private void OnInviteFactionRequest(InviteFactionRequestEvent msg, EntitySessionEventArgs args) { if (!EnsureFactionsComponent()) { return; } var sourceEntity = _factionsEntity ?? EntityUid.Invalid; if (_factionsComponent == null || _factionsEntity == null) return; var inviterSession = args.SenderSession; var inviterId = inviterSession.UserId; if (!TryGetPlayerFaction(inviterId, out var inviterFaction)) { // FIX: Correct arguments for ChatMessageToOne var errorMsg = "You must be in a faction to invite others."; _chatManager.ChatMessageToOne(ChatChannel.Notifications, errorMsg, errorMsg, sourceEntity, false, inviterSession.Channel); return; } if (!_playerManager.TryGetSessionById(msg.TargetPlayerUserId, out var targetSession)) { // FIX: Correct arguments for ChatMessageToOne var errorMsg = "Could not find the player you tried to invite."; _chatManager.ChatMessageToOne(ChatChannel.Notifications, errorMsg, errorMsg, sourceEntity, false, inviterSession.Channel); return; } if (IsPlayerInFaction(msg.TargetPlayerUserId, out _)) { // FIX: Correct arguments for ChatMessageToOne (to inviter) var inviterErrorMsg = $"{targetSession.Name} is already in a faction."; _chatManager.ChatMessageToOne(ChatChannel.Notifications, inviterErrorMsg, inviterErrorMsg, sourceEntity, false, inviterSession.Channel); // FIX: Correct arguments for ChatMessageToOne (to target) var targetErrorMsg = $"{inviterSession.Name} tried to invite you to '{inviterFaction!.FactionName}', but you are already in a faction."; _chatManager.ChatMessageToOne(ChatChannel.Notifications, targetErrorMsg, targetErrorMsg, sourceEntity, false, targetSession.Channel); return; } var offerEvent = new FactionInviteOfferEvent(inviterSession.Name, inviterFaction!.FactionName, inviterId); RaiseNetworkEvent(offerEvent, Filter.SinglePlayer(targetSession)); // FIX: Correct arguments for ChatMessageToOne (confirmation to inviter) var inviterConfirmMsg = $"Invitation sent to {targetSession.Name}."; _chatManager.ChatMessageToOne(ChatChannel.Notifications, inviterConfirmMsg, inviterConfirmMsg, sourceEntity, false, inviterSession.Channel); // FIX: Correct arguments for ChatMessageToOne (notification to target) var targetNotifyMsg = $"{inviterSession.Name} has invited you to join the faction '{inviterFaction.FactionName}'. Check your chat or notifications."; _chatManager.ChatMessageToOne(ChatChannel.Notifications, targetNotifyMsg, targetNotifyMsg, sourceEntity, false, targetSession.Channel); Log.Info($"Player {inviterSession.Name} invited {targetSession.Name} to faction '{inviterFaction.FactionName}'."); } /// /// Handles a player's acceptance of a faction invitation, adding them to the specified faction and notifying them of the status change. /// private void OnAcceptFactionInvite(AcceptFactionInviteEvent msg, EntitySessionEventArgs args) { if (!EnsureFactionsComponent()) { return; } var sourceEntity = _factionsEntity ?? EntityUid.Invalid; if (_factionsComponent == null || _factionsEntity == null) return; var accepterSession = args.SenderSession; var accepterId = accepterSession.UserId; var accepterIdStr = accepterId.ToString(); if (IsPlayerInFaction(accepterId, out var currentFaction)) { // FIX: Correct arguments for ChatMessageToOne var errorMsg = $"You cannot accept the invite, you are already in faction '{currentFaction!.FactionName}'."; _chatManager.ChatMessageToOne(ChatChannel.Notifications, errorMsg, errorMsg, sourceEntity, false, accepterSession.Channel); return; } var targetFaction = _factionsComponent.FactionList.FirstOrDefault(f => f.FactionName.Equals(msg.FactionName, StringComparison.OrdinalIgnoreCase)); if (targetFaction == null) { // FIX: Correct arguments for ChatMessageToOne var errorMsg = $"The faction '{msg.FactionName}' no longer exists."; _chatManager.ChatMessageToOne(ChatChannel.Notifications, errorMsg, errorMsg, sourceEntity, false, accepterSession.Channel); return; } targetFaction.FactionMembers.Add(accepterIdStr); Dirty(_factionsEntity.Value, _factionsComponent); // FIX: Correct arguments for ChatMessageToOne var confirmationMsg = $"You have joined faction '{targetFaction.FactionName}'."; _chatManager.ChatMessageToOne(ChatChannel.Notifications, confirmationMsg, confirmationMsg, sourceEntity, false, accepterSession.Channel); Log.Info($"Player {accepterSession.Name} accepted invite and joined faction '{targetFaction.FactionName}'."); // Notify the client their status changed var statusChangeEvent = new PlayerFactionStatusChangedEvent(true, targetFaction.FactionName); RaiseNetworkEvent(statusChangeEvent, accepterSession.Channel); // Target the specific player } /// /// Determines whether the specified player is a member of any faction. /// /// The user ID of the player to check. /// /// When this method returns, contains the faction the player belongs to if found; otherwise, null. /// /// True if the player is in a faction; otherwise, false. public bool IsPlayerInFaction(NetUserId userId, out FactionData? faction) // <-- Use FactionData { faction = null; if (_factionsComponent == null) return false; var playerIdStr = userId.ToString(); foreach (var f in _factionsComponent.FactionList) { if (f.FactionMembers.Contains(playerIdStr)) { faction = f; return true; } } return false; } /// /// Attempts to find the faction that the specified player belongs to. /// /// The user ID of the player. /// When this method returns, contains the player's faction if found; otherwise, null. /// True if the player is a member of a faction; otherwise, false. public bool TryGetPlayerFaction(NetUserId userId, out FactionData? faction) // <-- Use FactionData { return IsPlayerInFaction(userId, out faction); } }