using Content.Server.Power.Components; using Content.Server.Power.EntitySystems; using Content.Server.Shuttles.Components; using Content.Server.Shuttles.Events; using Content.Server.UserInterface; 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 Robust.Server.GameObjects; using Robust.Shared.GameStates; using Robust.Shared.Utility; namespace Content.Server.Shuttles.Systems { public sealed class ShuttleConsoleSystem : SharedShuttleConsoleSystem { [Dependency] private readonly ActionBlockerSystem _blocker = default!; [Dependency] private readonly AlertsSystem _alertsSystem = default!; [Dependency] private readonly TagSystem _tags = default!; [Dependency] private readonly UserInterfaceSystem _ui = default!; public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnConsoleShutdown); SubscribeLocalEvent(OnConsolePowerChange); SubscribeLocalEvent(OnConsoleAnchorChange); SubscribeLocalEvent(OnConsoleUIOpenAttempt); SubscribeLocalEvent(OnModeRequest); SubscribeLocalEvent(OnConsoleUIClose); SubscribeLocalEvent(OnDock); SubscribeLocalEvent(OnUndock); SubscribeLocalEvent(HandlePilotMove); SubscribeLocalEvent(OnGetState); } private void OnDock(DockEvent ev) { RefreshShuttleConsoles(); } private void OnUndock(UndockEvent ev) { RefreshShuttleConsoles(); } /// /// Refreshes all of the data for shuttle consoles. /// public void RefreshShuttleConsoles() { // TODO: Should really call this per shuttle in some instances. var docks = GetAllDocks(); foreach (var comp in EntityQuery(true)) { UpdateState(comp, docks); } } /// /// 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; // In case they D/C should still clean them up. foreach (var comp in EntityQuery(true)) { comp.Requesters.Remove(user); } 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) { UpdateState(component); } private void OnConsolePowerChange(EntityUid uid, ShuttleConsoleComponent component, PowerChangedEvent args) { UpdateState(component); } 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 = EntityManager.EnsureComponent(user); var console = pilotComponent.Console; if (console != null) { RemovePilot(pilotComponent); if (console == component) { return false; } } AddPilot(user, component); return true; } private void OnGetState(EntityUid uid, PilotComponent component, ref ComponentGetState args) { args.State = new PilotComponentState(component.Console?.Owner); } /// /// Client is requesting a change in the shuttle's driving mode. /// private void OnModeRequest(EntityUid uid, ShuttleConsoleComponent component, ShuttleModeRequestMessage args) { if (args.Session.AttachedEntity is not { } player || !TryComp(player, out var pilot) || !TryComp(player, out var xform) || pilot.Console is not ShuttleConsoleComponent console) return; if (!console.SubscribedPilots.Contains(pilot) || !TryComp(xform.GridEntityId, out var shuttle)) return; SetShuttleMode(args.Mode, console, shuttle); } /// /// Sets the shuttle's movement mode. Does minimal revalidation. /// private void SetShuttleMode(ShuttleMode mode, ShuttleConsoleComponent consoleComponent, ShuttleComponent shuttleComponent, TransformComponent? consoleXform = null) { // Re-validate if (!this.IsPowered(consoleComponent.Owner, EntityManager) || !Resolve(consoleComponent.Owner, ref consoleXform) || !consoleXform.Anchored || consoleXform.GridID != Transform(shuttleComponent.Owner).GridID) { UpdateState(consoleComponent); return; } shuttleComponent.Mode = mode; switch (mode) { case ShuttleMode.Strafing: break; case ShuttleMode.Cruise: break; default: throw new ArgumentOutOfRangeException(); } UpdateState(consoleComponent); } /// /// Returns the position and angle of all dockingcomponents. /// private List GetAllDocks() { // TODO: NEED TO MAKE SURE THIS UPDATES ON ANCHORING CHANGES! var result = new List(); foreach (var (comp, xform) in EntityQuery(true)) { if (xform.ParentUid != xform.GridUid) continue; var state = new DockingInterfaceState() { Coordinates = xform.Coordinates, Angle = xform.LocalRotation, Entity = comp.Owner, Connected = comp.Docked, }; result.Add(state); } return result; } private void UpdateState(ShuttleConsoleComponent component, List? docks = null) { TryComp(component.Owner, out var radar); var range = radar?.MaxRange ?? 0f; TryComp(Transform(component.Owner).GridUid, out var shuttle); var mode = shuttle?.Mode ?? ShuttleMode.Cruise; docks ??= GetAllDocks(); _ui.GetUiOrNull(component.Owner, ShuttleConsoleUiKey.Key) ?.SetState(new ShuttleConsoleBoundInterfaceState( mode, range, component.Owner, docks)); } public override void Update(float frameTime) { base.Update(frameTime); var toRemove = new RemQueue(); foreach (var comp in EntityManager.EntityQuery()) { if (comp.Console == null) continue; if (!_blocker.CanInteract(comp.Owner, comp.Console.Owner)) { toRemove.Add(comp); } } foreach (var comp in toRemove) { RemovePilot(comp); } } /// /// If pilot is moved then we'll stop them from piloting. /// private void HandlePilotMove(EntityUid uid, PilotComponent component, ref MoveEvent args) { if (component.Console == null || component.Position == null) { DebugTools.Assert(component.Position == null && component.Console == null); EntityManager.RemoveComponent(uid); return; } if (args.NewPosition.TryDistance(EntityManager, component.Position.Value, out var distance) && distance < PilotComponent.BreakDistance) return; RemovePilot(component); } protected override void HandlePilotShutdown(EntityUid uid, PilotComponent component, ComponentShutdown args) { base.HandlePilotShutdown(uid, component, args); RemovePilot(component); } private void OnConsoleShutdown(EntityUid uid, ShuttleConsoleComponent component, ComponentShutdown args) { ClearPilots(component); } public void AddPilot(EntityUid entity, ShuttleConsoleComponent component) { if (!EntityManager.TryGetComponent(entity, out PilotComponent? pilotComponent) || component.SubscribedPilots.Contains(pilotComponent)) { return; } if (TryComp(entity, out var eye)) { eye.Zoom = component.Zoom; } component.SubscribedPilots.Add(pilotComponent); _alertsSystem.ShowAlert(entity, AlertType.PilotingShuttle); pilotComponent.Console = component; ActionBlockerSystem.UpdateCanMove(entity); pilotComponent.Position = EntityManager.GetComponent(entity).Coordinates; Dirty(pilotComponent); } public void RemovePilot(PilotComponent pilotComponent) { var console = pilotComponent.Console; if (console is not ShuttleConsoleComponent helmsman) return; pilotComponent.Console = null; pilotComponent.Position = null; if (TryComp(pilotComponent.Owner, out var eye)) { eye.Zoom = new(1.0f, 1.0f); } if (!helmsman.SubscribedPilots.Remove(pilotComponent)) return; _alertsSystem.ClearAlert(pilotComponent.Owner, AlertType.PilotingShuttle); pilotComponent.Owner.PopupMessage(Loc.GetString("shuttle-pilot-end")); if (pilotComponent.LifeStage < ComponentLifeStage.Stopping) EntityManager.RemoveComponent(pilotComponent.Owner); } public void RemovePilot(EntityUid entity) { if (!EntityManager.TryGetComponent(entity, out PilotComponent? pilotComponent)) return; RemovePilot(pilotComponent); } public void ClearPilots(ShuttleConsoleComponent component) { while (component.SubscribedPilots.TryGetValue(0, out var pilot)) { RemovePilot(pilot); } } } }