using Content.Server.Power.Components; using Content.Server.Power.EntitySystems; using Content.Server.Shuttles.Components; using Content.Server.Shuttles.Events; using Content.Server.Station.Systems; using Content.Shared.ActionBlocker; using Content.Shared.Alert; using Content.Shared.Popups; using Content.Shared.Shuttles.BUIStates; using Content.Shared.Shuttles.Components; using Content.Shared.Shuttles.Events; using Content.Shared.Shuttles.Systems; using Content.Shared.Tag; using Content.Shared.Movement.Systems; using Content.Shared.Shuttles.UI.MapObjects; using Robust.Server.GameObjects; using Robust.Shared.Collections; using Robust.Shared.GameStates; using Robust.Shared.Map; using Robust.Shared.Utility; using Content.Shared.UserInterface; namespace Content.Server.Shuttles.Systems; public sealed partial class ShuttleConsoleSystem : SharedShuttleConsoleSystem { [Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly ActionBlockerSystem _blocker = default!; [Dependency] private readonly AlertsSystem _alertsSystem = default!; [Dependency] private readonly EntityLookupSystem _lookup = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; [Dependency] private readonly SharedTransformSystem _transform = default!; [Dependency] private readonly ShuttleSystem _shuttle = default!; [Dependency] private readonly StationSystem _station = default!; [Dependency] private readonly TagSystem _tags = default!; [Dependency] private readonly UserInterfaceSystem _ui = default!; [Dependency] private readonly SharedContentEyeSystem _eyeSystem = default!; private EntityQuery _metaQuery; private EntityQuery _xformQuery; private readonly HashSet> _consoles = new(); public override void Initialize() { base.Initialize(); _metaQuery = GetEntityQuery(); _xformQuery = GetEntityQuery(); SubscribeLocalEvent(OnConsoleShutdown); SubscribeLocalEvent(OnConsolePowerChange); SubscribeLocalEvent(OnConsoleAnchorChange); SubscribeLocalEvent(OnConsoleUIOpenAttempt); Subs.BuiEvents(ShuttleConsoleUiKey.Key, subs => { subs.Event(OnBeaconFTLMessage); subs.Event(OnPositionFTLMessage); subs.Event(OnConsoleUIClose); }); SubscribeLocalEvent(OnCargoGetConsole); SubscribeLocalEvent(OnDronePilotConsoleOpen); Subs.BuiEvents(ShuttleConsoleUiKey.Key, subs => { subs.Event(OnDronePilotConsoleClose); }); SubscribeLocalEvent(OnDock); SubscribeLocalEvent(OnUndock); SubscribeLocalEvent(OnGetState); SubscribeLocalEvent(OnFtlDestStartup); SubscribeLocalEvent(OnFtlDestShutdown); InitializeFTL(); } private void OnFtlDestStartup(EntityUid uid, FTLDestinationComponent component, ComponentStartup args) { RefreshShuttleConsoles(); } private void OnFtlDestShutdown(EntityUid uid, FTLDestinationComponent component, ComponentShutdown args) { RefreshShuttleConsoles(); } private void OnDock(DockEvent ev) { RefreshShuttleConsoles(); } private void OnUndock(UndockEvent ev) { RefreshShuttleConsoles(); } /// /// Refreshes all the shuttle console data for a particular grid. /// public void RefreshShuttleConsoles(EntityUid gridUid) { var exclusions = new List(); GetExclusions(ref exclusions); _consoles.Clear(); _lookup.GetChildEntities(gridUid, _consoles); DockingInterfaceState? dockState = null; foreach (var entity in _consoles) { UpdateState(entity, ref dockState); } } /// /// Refreshes all of the data for shuttle consoles. /// public void RefreshShuttleConsoles() { var exclusions = new List(); GetExclusions(ref exclusions); var query = AllEntityQuery(); DockingInterfaceState? dockState = null; while (query.MoveNext(out var uid, out _)) { UpdateState(uid,ref dockState); } } /// /// Stop piloting if the window is closed. /// private void OnConsoleUIClose(EntityUid uid, ShuttleConsoleComponent component, BoundUIClosedEvent args) { if ((ShuttleConsoleUiKey) args.UiKey != ShuttleConsoleUiKey.Key || args.Session.AttachedEntity is not { } user) { return; } RemovePilot(user); } private void OnConsoleUIOpenAttempt(EntityUid uid, ShuttleConsoleComponent component, ActivatableUIOpenAttemptEvent args) { if (!TryPilot(args.User, uid)) args.Cancel(); } private void OnConsoleAnchorChange(EntityUid uid, ShuttleConsoleComponent component, ref AnchorStateChangedEvent args) { DockingInterfaceState? dockState = null; UpdateState(uid, ref dockState); } private void OnConsolePowerChange(EntityUid uid, ShuttleConsoleComponent component, ref PowerChangedEvent args) { DockingInterfaceState? dockState = null; UpdateState(uid, ref dockState); } private bool TryPilot(EntityUid user, EntityUid uid) { if (!_tags.HasTag(user, "CanPilot") || !TryComp(uid, out var component) || !this.IsPowered(uid, EntityManager) || !Transform(uid).Anchored || !_blocker.CanInteract(user, uid)) { return false; } var pilotComponent = EnsureComp(user); var console = pilotComponent.Console; if (console != null) { RemovePilot(user, pilotComponent); // This feels backwards; is this intended to be a toggle? if (console == uid) return false; } AddPilot(uid, user, component); return true; } private void OnGetState(EntityUid uid, PilotComponent component, ref ComponentGetState args) { args.State = new PilotComponentState(GetNetEntity(component.Console)); } /// /// Returns the position and angle of all dockingcomponents. /// public Dictionary> GetAllDocks() { // TODO: NEED TO MAKE SURE THIS UPDATES ON ANCHORING CHANGES! var result = new Dictionary>(); var query = AllEntityQuery(); while (query.MoveNext(out var uid, out var comp, out var xform, out var metadata)) { if (xform.ParentUid != xform.GridUid) continue; var gridDocks = result.GetOrNew(GetNetEntity(xform.GridUid.Value)); var state = new DockingPortState() { Name = metadata.EntityName, Coordinates = GetNetCoordinates(xform.Coordinates), Angle = xform.LocalRotation, Entity = GetNetEntity(uid), GridDockedWith = _xformQuery.TryGetComponent(comp.DockedWith, out var otherDockXform) ? GetNetEntity(otherDockXform.GridUid) : null, }; gridDocks.Add(state); } return result; } private void UpdateState(EntityUid consoleUid, ref DockingInterfaceState? dockState) { EntityUid? entity = consoleUid; var getShuttleEv = new ConsoleShuttleEvent { Console = entity, }; RaiseLocalEvent(entity.Value, ref getShuttleEv); entity = getShuttleEv.Console; TryComp(entity, out var consoleXform); var shuttleGridUid = consoleXform?.GridUid; NavInterfaceState navState; ShuttleMapInterfaceState mapState; dockState ??= GetDockState(); if (shuttleGridUid != null && entity != null) { navState = GetNavState(entity.Value, dockState.Docks); mapState = GetMapState(shuttleGridUid.Value); } else { navState = new NavInterfaceState(0f, null, null, new Dictionary>()); mapState = new ShuttleMapInterfaceState(FTLState.Invalid, 0f, new List(), new List()); } if (_ui.TryGetUi(consoleUid, ShuttleConsoleUiKey.Key, out var bui)) { _ui.SetUiState(bui, new ShuttleBoundUserInterfaceState(navState, mapState, dockState)); } } public override void Update(float frameTime) { base.Update(frameTime); var toRemove = new ValueList<(EntityUid, PilotComponent)>(); var query = EntityQueryEnumerator(); while (query.MoveNext(out var uid, out var comp)) { if (comp.Console == null) continue; if (!_blocker.CanInteract(uid, comp.Console)) { toRemove.Add((uid, comp)); } } foreach (var (uid, comp) in toRemove) { RemovePilot(uid, comp); } } protected override void HandlePilotShutdown(EntityUid uid, PilotComponent component, ComponentShutdown args) { base.HandlePilotShutdown(uid, component, args); RemovePilot(uid, component); } private void OnConsoleShutdown(EntityUid uid, ShuttleConsoleComponent component, ComponentShutdown args) { ClearPilots(component); } public void AddPilot(EntityUid uid, EntityUid entity, ShuttleConsoleComponent component) { if (!EntityManager.TryGetComponent(entity, out PilotComponent? pilotComponent) || component.SubscribedPilots.Contains(entity)) { return; } _eyeSystem.SetZoom(entity, component.Zoom, ignoreLimits: true); component.SubscribedPilots.Add(entity); _alertsSystem.ShowAlert(entity, AlertType.PilotingShuttle); pilotComponent.Console = uid; ActionBlockerSystem.UpdateCanMove(entity); pilotComponent.Position = EntityManager.GetComponent(entity).Coordinates; Dirty(entity, pilotComponent); } public void RemovePilot(EntityUid pilotUid, PilotComponent pilotComponent) { var console = pilotComponent.Console; if (!TryComp(console, out var helm)) return; pilotComponent.Console = null; pilotComponent.Position = null; _eyeSystem.ResetZoom(pilotUid); if (!helm.SubscribedPilots.Remove(pilotUid)) return; _alertsSystem.ClearAlert(pilotUid, AlertType.PilotingShuttle); _popup.PopupEntity(Loc.GetString("shuttle-pilot-end"), pilotUid, pilotUid); if (pilotComponent.LifeStage < ComponentLifeStage.Stopping) EntityManager.RemoveComponent(pilotUid); } public void RemovePilot(EntityUid entity) { if (!EntityManager.TryGetComponent(entity, out PilotComponent? pilotComponent)) return; RemovePilot(entity, pilotComponent); } public void ClearPilots(ShuttleConsoleComponent component) { var query = GetEntityQuery(); while (component.SubscribedPilots.TryGetValue(0, out var pilot)) { if (query.TryGetComponent(pilot, out var pilotComponent)) RemovePilot(pilot, pilotComponent); } } /// /// Specific for a particular shuttle. /// public NavInterfaceState GetNavState(Entity entity, Dictionary> docks) { if (!Resolve(entity, ref entity.Comp1, ref entity.Comp2)) return new NavInterfaceState(SharedRadarConsoleSystem.DefaultMaxRange, null, null, docks); return GetNavState( entity, docks, entity.Comp2.Coordinates, entity.Comp2.LocalRotation); } public NavInterfaceState GetNavState( Entity entity, Dictionary> docks, EntityCoordinates coordinates, Angle angle) { if (!Resolve(entity, ref entity.Comp1, ref entity.Comp2)) return new NavInterfaceState(SharedRadarConsoleSystem.DefaultMaxRange, GetNetCoordinates(coordinates), angle, docks); return new NavInterfaceState( entity.Comp1.MaxRange, GetNetCoordinates(coordinates), angle, docks); } /// /// Global for all shuttles. /// /// public DockingInterfaceState GetDockState() { var docks = GetAllDocks(); return new DockingInterfaceState(docks); } /// /// Specific to a particular shuttle. /// public ShuttleMapInterfaceState GetMapState(Entity shuttle) { FTLState ftlState = FTLState.Available; float stateDuration = 0f; if (Resolve(shuttle, ref shuttle.Comp, false) && shuttle.Comp.LifeStage < ComponentLifeStage.Stopped) { ftlState = shuttle.Comp.State; stateDuration = _shuttle.GetStateDuration(shuttle.Comp); } List? beacons = null; List? exclusions = null; GetBeacons(ref beacons); GetExclusions(ref exclusions); return new ShuttleMapInterfaceState( ftlState, stateDuration, beacons ?? new List(), exclusions ?? new List()); } }