using System.Linq; using System.Diagnostics.CodeAnalysis; using Content.Server.DeviceLinking.Components; using Content.Server.MachineLinking.Components; using Content.Server.Power.Components; using Content.Server.Tools; using Content.Shared.DeviceLinking.Events; using Content.Shared.Interaction; using Content.Shared.MachineLinking; using Content.Shared.Popups; using Robust.Server.GameObjects; using Content.Shared.Verbs; using Robust.Shared.Prototypes; using SignalReceivedEvent = Content.Server.DeviceLinking.Events.SignalReceivedEvent; namespace Content.Server.MachineLinking.System { public sealed class SignalLinkerSystem : EntitySystem { [Dependency] private readonly UserInterfaceSystem _userInterfaceSystem = default!; [Dependency] private readonly SharedPopupSystem _popupSystem = default!; [Dependency] private readonly IPrototypeManager _protoMan = default!; [Dependency] private readonly ToolSystem _tools = default!; public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnTransmitterRemoved); SubscribeLocalEvent(OnTransmitterInteractUsing); SubscribeLocalEvent>(OnGetTransmitterVerbs); SubscribeLocalEvent(OnReceiverStartup); SubscribeLocalEvent(OnReceiverRemoved); SubscribeLocalEvent(OnReceiverInteractUsing); SubscribeLocalEvent>(OnGetReceiverVerbs); SubscribeLocalEvent(OnSignalPortSelected); SubscribeLocalEvent(OnLinkerClearSelected); SubscribeLocalEvent(OnLinkerLinkDefaultSelected); SubscribeLocalEvent(OnLinkerUIClosed); } /// /// Convenience function to add several ports to an entity. /// public void EnsureReceiverPorts(EntityUid uid, params string[] ports) { var comp = EnsureComp(uid); foreach (var port in ports) { comp.Inputs.TryAdd(port, new List()); } } public void EnsureTransmitterPorts(EntityUid uid, params string[] ports) { var comp = EnsureComp(uid); foreach (var port in ports) { comp.Outputs.TryAdd(port, new List()); } } /// /// Add an alt-click verb to allow users to link the default ports, without needing to open the UI. /// private void OnGetReceiverVerbs(EntityUid uid, SignalReceiverComponent component, GetVerbsEvent args) { if (!args.CanAccess || !args.CanInteract) return; if (!TryComp(args.Using, out SignalLinkerComponent? linker) || !IsLinkerInteractable(args.Using.Value, linker)) { return; } var verb = new AlternativeVerb() { Text = Loc.GetString("signal-linking-verb-text-link-default"), IconEntity = args.Using }; args.Verbs.Add(verb); if (linker.SavedTransmitter != null) { verb.Act = () => { var msg = TryLinkDefaults(uid, linker.SavedTransmitter.Value, args.User, component) ? Loc.GetString("signal-linking-verb-success", ("machine", linker.SavedTransmitter.Value)) : Loc.GetString("signal-linking-verb-fail", ("machine", linker.SavedTransmitter.Value)); _popupSystem.PopupEntity(msg, uid, args.User); }; return; } verb.Disabled = true; verb.Message = Loc.GetString("signal-linking-verb-disabled-no-transmitter"); } private void OnGetTransmitterVerbs(EntityUid uid, SignalTransmitterComponent component, GetVerbsEvent args) { if (!args.CanAccess || !args.CanInteract) return; if (!TryComp(args.Using, out SignalLinkerComponent? linker) || !IsLinkerInteractable(args.Using.Value, linker)) return; AlternativeVerb verb = new() { Text = Loc.GetString("signal-linking-verb-text-link-default"), IconEntity = args.Using }; args.Verbs.Add(verb); if (linker.SavedReceiver != null) { verb.Act = () => { var msg = TryLinkDefaults(linker.SavedReceiver.Value, uid, args.User, null, component) ? Loc.GetString("signal-linking-verb-success", ("machine", linker.SavedReceiver.Value)) : Loc.GetString("signal-linking-verb-fail", ("machine", linker.SavedReceiver.Value)); _popupSystem.PopupEntity(msg, uid, args.User); }; return; } verb.Disabled = true; verb.Message = Loc.GetString("signal-linking-verb-disabled-no-receiver"); } public void InvokePort(EntityUid uid, string port, SignalTransmitterComponent? component = null) { InvokePort(uid, port, SignalState.Momentary, component); } public void InvokePort(EntityUid uid, string port, SignalState state, SignalTransmitterComponent? component = null) { if (!Resolve(uid, ref component)) return; if (state != SignalState.Momentary && state == component.LastState) { // no change in output signal return; } if (!component.Outputs.TryGetValue(port, out var receivers)) return; component.LastState = state; foreach (var receiver in receivers) { var eventArgs = new SignalReceivedEvent(receiver.Port, uid); RaiseLocalEvent(receiver.Uid, ref eventArgs); } } private void OnReceiverStartup(EntityUid uid, SignalReceiverComponent receiver, ComponentStartup args) { RemCompDeferred(uid); } private void OnTransmitterRemoved(EntityUid uid, SignalTransmitterComponent transmitter, ComponentRemove args) { Dictionary uidCache = new(); foreach (var tport in transmitter.Outputs) foreach (var rport in tport.Value) { if (!uidCache.TryGetValue(rport.Uid, out var receiver)) uidCache.Add(rport.Uid, receiver = CompOrNull(rport.Uid)); if (receiver != null && receiver.Inputs.TryGetValue(rport.Port, out var rpv)) rpv.Remove(new(uid, tport.Key)); } } private void OnReceiverRemoved(EntityUid uid, SignalReceiverComponent component, ComponentRemove args) { Dictionary uidCache = new(); foreach (var rport in component.Inputs) foreach (var tport in rport.Value) { if (!uidCache.TryGetValue(tport.Uid, out var transmitter)) uidCache.Add(tport.Uid, transmitter = CompOrNull(tport.Uid)); if (transmitter != null && transmitter.Outputs.TryGetValue(tport.Port, out var receivers)) receivers.Remove(new(uid, rport.Key)); } } private void OnTransmitterInteractUsing(EntityUid uid, SignalTransmitterComponent transmitter, InteractUsingEvent args) { if (args.Handled) return; if (!TryComp(args.Used, out SignalLinkerComponent? linker) || !IsLinkerInteractable(args.Used, linker) || !TryComp(args.User, out ActorComponent? actor)) return; if (!linker.LinkTX()) return; linker.SavedTransmitter = uid; if (!TryComp(linker.SavedReceiver, out SignalReceiverComponent? receiver)) { _popupSystem.PopupCursor(Loc.GetString("signal-linker-component-saved", ("machine", uid)), args.User, PopupType.Medium); args.Handled = true; return; } if (TryGetOrOpenUI(args.Used, out var bui, actor)) { TryUpdateUI(args.Used, uid, linker.SavedReceiver!.Value, bui, transmitter, receiver); args.Handled = true; } } private void OnReceiverInteractUsing(EntityUid uid, SignalReceiverComponent receiver, InteractUsingEvent args) { if (args.Handled) return; if (!TryComp(args.Used, out SignalLinkerComponent? linker) || !IsLinkerInteractable(args.Used, linker) || !TryComp(args.User, out ActorComponent? actor)) return; if (!linker.LinkRX()) return; linker.SavedReceiver = uid; if (!TryComp(linker.SavedTransmitter, out SignalTransmitterComponent? transmitter)) { _popupSystem.PopupCursor(Loc.GetString("signal-linker-component-saved", ("machine", uid)), args.User, PopupType.Medium); args.Handled = true; return; } if (TryGetOrOpenUI(args.Used, out var bui, actor)) { TryUpdateUI(args.Used, linker.SavedTransmitter!.Value, uid, bui, transmitter, receiver); args.Handled = true; } } private bool TryGetOrOpenUI(EntityUid linkerUid, [NotNullWhen(true)] out BoundUserInterface? bui, ActorComponent actor) { if (_userInterfaceSystem.TryGetUi(linkerUid, SignalLinkerUiKey.Key, out bui)) { _userInterfaceSystem.OpenUi(bui, actor.PlayerSession); return true; } return false; } private bool TryUpdateUI(EntityUid linkerUid, EntityUid transmitterUid, EntityUid receiverUid, BoundUserInterface? bui = null, SignalTransmitterComponent? transmitter = null, SignalReceiverComponent? receiver = null) { if (!Resolve(transmitterUid, ref transmitter) || !Resolve(receiverUid, ref receiver)) return false; if (bui == null && !_userInterfaceSystem.TryGetUi(linkerUid, SignalLinkerUiKey.Key, out bui)) return false; var outKeys = transmitter.Outputs.Keys.ToList(); var inKeys = receiver.Inputs.Keys.ToList(); List<(int, int)> links = new(); for (var i = 0; i < outKeys.Count; i++) { foreach (var re in transmitter.Outputs[outKeys[i]]) { if (re.Uid == receiverUid) links.Add((i, inKeys.IndexOf(re.Port))); } } UserInterfaceSystem.SetUiState(bui, new SignalPortsState( $"{Name(transmitterUid)} ({transmitterUid})", outKeys, $"{Name(receiverUid)} ({receiverUid})", inKeys, links )); return true; } private bool TryLink(EntityUid transmitterUid, EntityUid receiverUid, SignalPortSelected args, EntityUid? user, bool quiet = false, bool checkRange = true, SignalTransmitterComponent? transmitter = null, SignalReceiverComponent? receiver = null) { if (!Resolve(transmitterUid, ref transmitter) || !Resolve(receiverUid, ref receiver)) return false; if (!transmitter.Outputs.TryGetValue(args.TransmitterPort, out var linkedReceivers) || !receiver.Inputs.TryGetValue(args.ReceiverPort, out var linkedTransmitters)) return false; quiet |= !user.HasValue; // Does the link already exist? Under the assumption that nothing has broken, lets only check the // transmitter ports. foreach (var identifier in linkedTransmitters) { if (identifier.Uid == transmitterUid && identifier.Port == args.TransmitterPort) return true; } if (checkRange && !IsInRange(transmitterUid, receiverUid, transmitter, receiver)) { if (!quiet) _popupSystem.PopupCursor(Loc.GetString("signal-linker-component-out-of-range"), user!.Value); return false; } // allow other systems to refuse the connection var linkAttempt = new LinkAttemptEvent(user, transmitterUid, args.TransmitterPort, receiverUid, args.ReceiverPort); RaiseLocalEvent(transmitterUid, linkAttempt, true); if (linkAttempt.Cancelled) { if (!quiet) _popupSystem.PopupCursor(Loc.GetString("signal-linker-component-connection-refused", ("machine", transmitterUid)), user!.Value); return false; } RaiseLocalEvent(receiverUid, linkAttempt, true); if (linkAttempt.Cancelled) { if (!quiet) _popupSystem.PopupCursor(Loc.GetString("signal-linker-component-connection-refused", ("machine", receiverUid)), user!.Value); return false; } linkedReceivers.Add(new(receiverUid, args.ReceiverPort)); linkedTransmitters.Add(new(transmitterUid, args.TransmitterPort)); if (!quiet) { _popupSystem.PopupCursor(Loc.GetString("signal-linker-component-linked-port", ("machine1", transmitterUid), ("port1", PortName(args.TransmitterPort)), ("machine2", receiverUid), ("port2", PortName(args.ReceiverPort))), user!.Value, PopupType.Medium); } var newLink = new NewLinkEvent(user, transmitterUid, args.TransmitterPort, receiverUid, args.ReceiverPort); RaiseLocalEvent(receiverUid, newLink); RaiseLocalEvent(transmitterUid, newLink); return true; } private void OnSignalPortSelected(EntityUid uid, SignalLinkerComponent linker, SignalPortSelected args) { if (!TryComp(linker.SavedTransmitter, out SignalTransmitterComponent? transmitter) || !TryComp(linker.SavedReceiver, out SignalReceiverComponent? receiver) || !transmitter.Outputs.TryGetValue(args.TransmitterPort, out var receivers) || !receiver.Inputs.TryGetValue(args.ReceiverPort, out var transmitters)) return; if (args.Session.AttachedEntity is not { Valid: true } attached) return; var receiverUid = linker.SavedReceiver.Value; var transmitterUid = linker.SavedTransmitter.Value; if (receivers.Contains(new(receiverUid, args.ReceiverPort)) || transmitters.Contains(new(transmitterUid, args.TransmitterPort))) { // link already exists, remove it if (receivers.Remove(new(receiverUid, args.ReceiverPort)) && transmitters.Remove(new(transmitterUid, args.TransmitterPort))) { RaiseLocalEvent(receiverUid, new PortDisconnectedEvent(args.ReceiverPort), true); RaiseLocalEvent(transmitterUid, new PortDisconnectedEvent(args.TransmitterPort), true); _popupSystem.PopupCursor(Loc.GetString("signal-linker-component-unlinked-port", ("machine1", transmitterUid), ("port1", PortName(args.TransmitterPort)), ("machine2", receiverUid), ("port2", PortName(args.ReceiverPort))), attached, PopupType.Medium); } else { // something weird happened // TODO log error } } else { TryLink(transmitterUid, receiverUid, args, attached, transmitter: transmitter, receiver: receiver); } TryUpdateUI(uid, transmitterUid, receiverUid, transmitter: transmitter, receiver: receiver); } /// /// Convenience function to retrieve the name of a port prototype. /// /// /// public string PortName(string port) where TPort : MachinePortPrototype, IPrototype { if (!_protoMan.TryIndex(port, out var proto)) return port; return Loc.GetString(proto.Name); } private void OnLinkerClearSelected(EntityUid uid, SignalLinkerComponent linker, LinkerClearSelected args) { if (!TryComp(linker.SavedTransmitter, out SignalTransmitterComponent? transmitter) || !TryComp(linker.SavedReceiver, out SignalReceiverComponent? receiver)) return; var transmitterUid = linker.SavedTransmitter.Value; var receiverUid = linker.SavedReceiver.Value; foreach (var (port, receivers) in transmitter.Outputs) { if (receivers.RemoveAll(id => id.Uid == receiverUid) > 0) RaiseLocalEvent(transmitterUid, new PortDisconnectedEvent(port), true); } foreach (var (port, transmitters) in receiver.Inputs) { if (transmitters.RemoveAll(id => id.Uid == transmitterUid) > 0) RaiseLocalEvent(receiverUid, new PortDisconnectedEvent(port), true); } TryUpdateUI(uid, transmitterUid, receiverUid, transmitter: transmitter, receiver: receiver); } private void OnLinkerLinkDefaultSelected(EntityUid uid, SignalLinkerComponent linker, LinkerLinkDefaultSelected args) { if (!TryComp(linker.SavedTransmitter, out SignalTransmitterComponent? transmitter) || !TryComp(linker.SavedReceiver, out SignalReceiverComponent? receiver)) return; if (args.Session.AttachedEntity is not { Valid: true } user) return; var transmitterUid = linker.SavedTransmitter!.Value; var receiverUid = linker.SavedReceiver!.Value; TryLinkDefaults(receiverUid, transmitterUid, user, receiver, transmitter); TryUpdateUI(uid, transmitterUid, receiverUid, transmitter: transmitter, receiver: receiver); } /// /// Attempt to link all default ports connections. Returns true if all links succeeded. Otherwise returns /// false. /// public bool TryLinkDefaults(EntityUid receiverUid, EntityUid transmitterUid, EntityUid? user, SignalReceiverComponent? receiver = null, SignalTransmitterComponent? transmitter = null) { if (!Resolve(receiverUid, ref receiver, false) || !Resolve(transmitterUid, ref transmitter, false)) return false; if (!IsInRange(transmitterUid, receiverUid, transmitter, receiver)) return false; var allLinksSucceeded = true; // First, disconnect existing links. foreach (var (port, receivers) in transmitter.Outputs) { if (receivers.RemoveAll(id => id.Uid == receiverUid) > 0) RaiseLocalEvent(transmitterUid, new PortDisconnectedEvent(port), true); } foreach (var (port, transmitters) in receiver.Inputs) { if (transmitters.RemoveAll(id => id.Uid == transmitterUid) > 0) RaiseLocalEvent(receiverUid, new PortDisconnectedEvent(port), true); } // Then make any valid default connections. foreach (var outPort in transmitter.Outputs.Keys) { var prototype = _protoMan.Index(outPort); if (prototype.DefaultLinks == null) continue; foreach (var inPort in prototype.DefaultLinks) { if (receiver.Inputs.ContainsKey(inPort)) allLinksSucceeded &= TryLink(transmitterUid, receiverUid, new(outPort, inPort), user, quiet: true, checkRange: false, transmitter: transmitter, receiver: receiver); } } return allLinksSucceeded; } private void OnLinkerUIClosed(EntityUid uid, SignalLinkerComponent component, BoundUIClosedEvent args) { component.SavedTransmitter = null; component.SavedReceiver = null; } private bool IsInRange(EntityUid transmitterUid, EntityUid receiverUid, SignalTransmitterComponent transmitterComponent, SignalReceiverComponent _) { if (TryComp(transmitterUid, out ApcPowerReceiverComponent? transmitterPower) && TryComp(receiverUid, out ApcPowerReceiverComponent? receiverPower) && transmitterPower.Provider?.Net == receiverPower.Provider?.Net) return true; // TODO: As elsewhere don't use mappos inrange. return Comp(transmitterUid).MapPosition.InRange( Comp(receiverUid).MapPosition, transmitterComponent.TransmissionRange); } private bool IsLinkerInteractable(EntityUid uid, SignalLinkerComponent linkerComponent) { var quality = linkerComponent.RequiredQuality; return quality == null || _tools.HasQuality(uid, quality); } } }