using System.Linq; using Content.Server._White.Radio.Components; using Content.Server._White.Radio.EntitySystems; using Content.Server.CartridgeLoader; using Content.Shared.CartridgeLoader; using Content.Shared.PDA; using Content.Server.GameTicking; using Robust.Shared.Timing; using Content.Server.DeviceNetwork.Systems; using Content.Shared.DeviceNetwork; using Content.Server.Station.Systems; using Content.Shared._White.CartridgeLoader.Cartridges; using Content.Shared.Inventory; using Content.Shared.Mind.Components; namespace Content.Server._White.CartridgeLoader.Cartridges; public sealed class MessagesCartridgeSystem : EntitySystem { [Dependency] private readonly CartridgeLoaderSystem _cartridgeLoaderSystem = default!; [Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly GameTicker _gameTicker = default!; [Dependency] private readonly MessagesServerSystem _messagesServerSystem = default!; [Dependency] private readonly DeviceNetworkSystem _deviceNetworkSystem = default!; [Dependency] private readonly SingletonDeviceNetServerSystem _singletonServerSystem = default!; [Dependency] private readonly StationSystem _stationSystem = default!; [Dependency] private readonly InventorySystem _inventorySystem = default!; public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnUiMessage); SubscribeLocalEvent(OnUiReady); SubscribeLocalEvent(OnPacketReceived); SubscribeLocalEvent(OnCartActivation); SubscribeLocalEvent(OnCartDeactivation); SubscribeLocalEvent(OnCartInsertion); SubscribeLocalEvent(OnRemove); SubscribeLocalEvent(OnPlayerSpawned); } private void OnPlayerSpawned(PlayerSpawnCompleteEvent ev) { if (!_inventorySystem.TryGetSlotEntity(ev.Mob, "id", out var pdaUid) || !HasComp(ev.Mob)) return; MessagesCartridgeComponent? comp = null; var programs = _cartridgeLoaderSystem.GetInstalled(pdaUid.Value); var program = programs.ToList().Find(program => TryComp(program, out comp)); if (comp == null) return; if (!TryComp(program, out CartridgeComponent? cartComponent)) return; var stationId = _stationSystem.GetOwningStation(pdaUid); if (!stationId.HasValue || !_singletonServerSystem.TryGetActiveServerAddress(stationId.Value, out var address)) return; SendName(pdaUid.Value, comp, cartComponent, address); } private void OnRemove(EntityUid uid, MessagesCartridgeComponent component, ComponentRemove args) { if (component.LastServer == null || !TryComp(component.LastServer, out var messagesServerComponent) || component.UserUid == null) return; messagesServerComponent.Dictionary.Remove(component.UserUid.Value); } /// /// This gets called when the ui fragment needs to be updated for the first time after activating /// private void OnUiReady(EntityUid uid, MessagesCartridgeComponent component, CartridgeUiReadyEvent args) { var stationId = _stationSystem.GetOwningStation(uid); if (stationId.HasValue && _singletonServerSystem.TryGetActiveServerAddress(stationId.Value, out var address) && TryComp(uid, out CartridgeComponent? cartComponent)) SendName(uid, component, cartComponent, address); UpdateUiState(uid, component); } /// /// The ui messages received here get wrapped by a CartridgeMessageEvent and are relayed from the /// /// /// The cartridge specific ui message event needs to inherit from the CartridgeMessageEvent /// private void OnUiMessage(EntityUid uid, MessagesCartridgeComponent component, CartridgeMessageEvent args) { if (args is not MessagesUiMessageEvent messageEvent) return; if (messageEvent.Action == MessagesUiAction.Send && HasComp(uid) && component.UserUid is { } userId && component.ChatUid != null && messageEvent.StringInput != null) { var stationId = _stationSystem.GetOwningStation(uid); if (!stationId.HasValue) return; var content = messageEvent.StringInput; if (content.Length > 1000) content = content.Substring(0, 997) + "..."; MessagesMessageData messageData = new() { SenderId = userId, ReceiverId = component.ChatUid.Value, Content = content, Time = _gameTiming.CurTime.Subtract(_gameTicker.RoundStartTimeSpan) }; var packet = new NetworkPayload() { [MessagesNetworkKeys.Message] = messageData }; _singletonServerSystem.TryGetActiveServerAddress(stationId.Value, out var address); _deviceNetworkSystem.QueuePacket(uid, address, packet); } else { if (messageEvent.Action == MessagesUiAction.ChangeChat) component.ChatUid = messageEvent.TargetChatUid; } UpdateUiState(uid, component); } /// /// On cart insertion, register as background process. /// private void OnCartInsertion(EntityUid uid, MessagesCartridgeComponent component, CartridgeAddedEvent args) { _cartridgeLoaderSystem.RegisterBackgroundProgram(args.Loader, uid); component.UserUid = args.Loader.Id; } /// /// On cartridge activation, connect to messages network. /// private void OnCartActivation(EntityUid uid, MessagesCartridgeComponent component, CartridgeActivatedEvent args) { _deviceNetworkSystem.ConnectDevice(uid); var stationId = _stationSystem.GetOwningStation(uid); if (stationId.HasValue && _singletonServerSystem.TryGetActiveServerAddress(stationId.Value, out var address) && TryComp(uid, out CartridgeComponent? cartComponent)) SendName(uid, component, cartComponent, address); } /// /// On cartridge deactivation, disconnect from messages network. /// private void OnCartDeactivation(EntityUid uid, MessagesCartridgeComponent component, CartridgeDeactivatedEvent args) { _deviceNetworkSystem.DisconnectDevice(uid, null); } /// /// React and respond to packets from the server /// private void OnPacketReceived(EntityUid uid, MessagesCartridgeComponent component, DeviceNetworkPacketEvent args) { if (!TryComp(uid, out CartridgeComponent? cartComponent)) return; component.LastServer = args.Sender; if (args.Data.TryGetValue(MessagesNetworkKeys.Message, out var message) && cartComponent.LoaderUid != null) { if (message.ReceiverId == cartComponent.LoaderUid.Value.Id) { TryGetName(message.SenderId, component, out var name); var subtitleString = Loc.GetString("messages-pda-notification-header", ("name", name)); _cartridgeLoaderSystem.SendNotification( cartComponent.LoaderUid.Value, subtitleString, message.Content); } } UpdateUiState(uid, component); } /// /// Updates the user's name in the storage component. /// public void SendName(EntityUid uid, MessagesCartridgeComponent component, CartridgeComponent cartComponent, string? address) { TryGetMessagesUser(component, cartComponent, out var messagesUser); ; var packet = new NetworkPayload() { [MessagesNetworkKeys.UserId] = component.UserUid, [MessagesNetworkKeys.NewUser] = messagesUser }; _deviceNetworkSystem.QueuePacket(uid, address, packet); } /// /// Retrieves the name of the given user from the last contacted server /// private void TryGetName(int key, MessagesCartridgeComponent component, out string name) { if (component.LastServer != null && _messagesServerSystem.TryGetUserFromDict(component.LastServer, key, out var messagesUser)) { name = messagesUser.Name; return; } name = Loc.GetString("messages-pda-connection-error"); } /// /// Returns the user's name, job title and job department /// private bool TryGetMessagesUser(MessagesCartridgeComponent component, CartridgeComponent cartridgeComponent, out MessagesUserData messagesUserData) { messagesUserData = new MessagesUserData(); if (component.LastServer != null && TryComp(component.LastServer, out var messagesServerComponent) && component.UserUid != null) messagesUserData = messagesServerComponent.Dictionary[component.UserUid.Value]; var pda = cartridgeComponent.LoaderUid; if (pda == null) return false; var pdaComponent = CompOrNull(pda); if (pdaComponent?.OwnerName == null) return false; messagesUserData.SetMessagesUser(pdaComponent.OwnerName, pdaComponent.OwnerJob, pdaComponent.OwnerDepartment); return true; } private void UpdateUiState(EntityUid uid, MessagesCartridgeComponent? component = null) { if (!Resolve(uid, ref component)) return; if (!TryComp(uid, out CartridgeComponent? cartComponent)) return; if (cartComponent.LoaderUid == null) return; var loaderUid = cartComponent.LoaderUid.Value; MessagesUiState state; var currentUserId = component.UserUid; if (currentUserId == null || component.LastServer == null) { state = new MessagesUiState(MessagesUiStateMode.Error); _cartridgeLoaderSystem.UpdateCartridgeUiState(loaderUid, state); return; } if (component.ChatUid == null) //if no chat is loaded, list users { List<(MessagesUserData, int?)> userList = []; var dictionary = _messagesServerSystem.GetNameDict(component.LastServer); foreach (var nameEntry in dictionary.Keys) { if (nameEntry == currentUserId) continue; userList.Add((dictionary[nameEntry], nameEntry)); } userList.Sort((a, b) => TimeSpan.Compare(b.Item1.Messages.LastOrDefault().Time, a.Item1.Messages.LastOrDefault().Time)); state = new MessagesUiState(MessagesUiStateMode.UserList, userList); } else { List messageList = []; //Else, list messages from the current chat foreach (var message in _messagesServerSystem.GetMessages(component.LastServer, component.ChatUid.Value, currentUserId.Value)) { if (message.SenderId == component.ChatUid && message.ReceiverId == currentUserId || message.ReceiverId == component.ChatUid && message.SenderId == currentUserId) messageList.Add(message); } messageList.Sort((a, b) => TimeSpan.Compare(a.Time, b.Time)); List<(string, int?)> formattedMessageList = []; foreach (var message in messageList) { TryGetName(message.SenderId, component, out var name); var stationTime = message.Time.Subtract(_gameTicker.RoundStartTimeSpan); var content = $"{stationTime:\\[hh\\:mm\\:ss\\]} {name}: {message.Content}"; formattedMessageList.Add((content, null)); } TryGetName(component.ChatUid.Value, component, out var user); state = new MessagesUiState(MessagesUiStateMode.Chat, null, formattedMessageList, user); } _cartridgeLoaderSystem.UpdateCartridgeUiState(loaderUid, state); } }