diff --git a/Content.Client/Content.Client.csproj b/Content.Client/Content.Client.csproj index 5d4759867cb1..3dc700f8ae2e 100644 --- a/Content.Client/Content.Client.csproj +++ b/Content.Client/Content.Client.csproj @@ -27,6 +27,7 @@ + diff --git a/Content.Client/Shuttles/BUI/ShuttleConsoleBoundUserInterface.cs b/Content.Client/Shuttles/BUI/ShuttleConsoleBoundUserInterface.cs index 35bfbbedfa01..937f470da010 100644 --- a/Content.Client/Shuttles/BUI/ShuttleConsoleBoundUserInterface.cs +++ b/Content.Client/Shuttles/BUI/ShuttleConsoleBoundUserInterface.cs @@ -1,27 +1,34 @@ -using Content.Client._Starlight.UserInterface; // Starlight +// ReSharper disable CheckNamespace using Content.Client.Shuttles.UI; +using Content.Client._Starlight.UserInterface; using Content.Shared.Shuttles.BUIStates; using Content.Shared.Shuttles.Events; using JetBrains.Annotations; -using Robust.Client.UserInterface; using Robust.Shared.Map; +using Content.Shared.Medical.CrewMonitoring; +using Content.Shared.Silicons.StationAi; +using Robust.Shared.Player; namespace Content.Client.Shuttles.BUI; [UsedImplicitly] public sealed class ShuttleConsoleBoundUserInterface : BoundUserInterface { + [Dependency] private ISharedPlayerManager _playerManager = default!; + [ViewVariables] private ShuttleConsoleWindow? _window; public ShuttleConsoleBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey) { + IoCManager.InjectDependencies(this); } protected override void Open() { base.Open(); _window = this.CreatePopOutableWindow(EntMan); // Starlight: popout support + _window.RadarClicked += OnRadarClicked; _window.RequestFTL += OnFTLRequest; _window.RequestBeaconFTL += OnFTLBeaconRequest; @@ -70,10 +77,21 @@ protected override void Dispose(bool disposing) if (disposing) { + if (_window != null) + _window.RadarClicked -= OnRadarClicked; _window?.DisposePopOut(); // Starlight: close the popout if exists } } + private void OnRadarClicked(EntityCoordinates coordinates) + { + var local = _playerManager.LocalEntity; + if (local is null || !EntMan.HasComponent(local.Value)) + return; + + SendMessage(new CrewMonitoringWarpRequestMessage(EntMan.GetNetCoordinates(coordinates))); + } + protected override void UpdateState(BoundUserInterfaceState state) { base.UpdateState(state); diff --git a/Content.Client/Shuttles/UI/NavScreen.xaml.cs b/Content.Client/Shuttles/UI/NavScreen.xaml.cs index fd6c09b52878..95bd050b467a 100644 --- a/Content.Client/Shuttles/UI/NavScreen.xaml.cs +++ b/Content.Client/Shuttles/UI/NavScreen.xaml.cs @@ -1,3 +1,4 @@ +// ReSharper disable CheckNamespace using System.Numerics; using Content.Shared._Starlight.Shuttles.BUIStates; using Content.Shared.Shuttles.BUIStates; @@ -19,6 +20,8 @@ public sealed partial class NavScreen : BoxContainer private EntityUid? _consoleEntity; // Entity of controlling console private EntityUid? _shuttleEntity; + partial void InitializeStarlight(); // Starlight + public NavScreen() { RobustXamlLoader.Load(this); @@ -30,6 +33,9 @@ public NavScreen() DockToggle.OnToggled += OnDockTogglePressed; DockToggle.Pressed = NavRadar.ShowDocks; + + InitializeStarlight(); // Starlight + } public void SetShuttle(EntityUid? shuttle) diff --git a/Content.Client/Shuttles/UI/ShuttleConsoleWindow.xaml.cs b/Content.Client/Shuttles/UI/ShuttleConsoleWindow.xaml.cs index fed15a581f4d..c4934abbbdea 100644 --- a/Content.Client/Shuttles/UI/ShuttleConsoleWindow.xaml.cs +++ b/Content.Client/Shuttles/UI/ShuttleConsoleWindow.xaml.cs @@ -1,10 +1,11 @@ +// ReSharper disable CheckNamespace using System.Numerics; -using Content.Client._Starlight.UserInterface; // Starlight +using Content.Client._Starlight.UserInterface; using Content.Client.Computer; -using Content.Client.UserInterface.Controls; +using Content.Shared._Starlight.Shuttles.BUIStates; using Content.Shared.Shuttles.BUIStates; using Robust.Client.AutoGenerated; -using Robust.Client.UserInterface; // Starlight +using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.XAML; using Robust.Shared.Map; @@ -12,12 +13,14 @@ namespace Content.Client.Shuttles.UI; [GenerateTypedNameReferences] -public sealed partial class ShuttleConsoleWindow : PopOutFancyWindow, // Starlight: popout support +public sealed partial class ShuttleConsoleWindow : PopOutFancyWindow, IComputerWindow { [Dependency] private IEntityManager _entManager = default!; - protected override Control Control => RootContainer; // Starlight: popout support + protected override Control Control => RootContainer; + + partial void InitializeStarlight(); // Starlight private ShuttleConsoleMode _mode = ShuttleConsoleMode.Nav; @@ -47,6 +50,8 @@ public ShuttleConsoleWindow() NavModeButton.Pressed = true; SetupMode(_mode); + InitializeStarlight(); // Starlight + MapContainer.RequestFTL += (coords, angle) => { RequestFTL?.Invoke(coords, angle); diff --git a/Content.Client/Silicons/StationAi/StationAiOverlay.cs b/Content.Client/Silicons/StationAi/StationAiOverlay.cs index 38eed0758e93..39f86a721e62 100644 --- a/Content.Client/Silicons/StationAi/StationAiOverlay.cs +++ b/Content.Client/Silicons/StationAi/StationAiOverlay.cs @@ -76,15 +76,25 @@ protected override void Draw(in OverlayDrawArgs args) // Check for cross-grid viewing (e.g., Abductor remote eye) BEFORE getting gridUid if (_entManager.TryGetComponent(playerEnt, out StationAiOverlayComponent? stationAiOverlay) - && stationAiOverlay.AllowCrossGrid + && (stationAiOverlay.AllowCrossGrid || _entManager.HasComponent(playerEnt)) && _entManager.TryGetComponent(playerEnt, out RelayInputMoverComponent? relay)) playerEnt = relay.RelayEntity; _entManager.TryGetComponent(playerEnt, out StationAiOverlayComponent? relayStationAiOverlay); _entManager.TryGetComponent(playerEnt, out TransformComponent? playerXform); + // We try to figure out where the AI is even coming from to make sure we don't render cameras for remote grids without access + EntityUid? sourceGrid = null; + var stationAiSystem = _entManager.System(); + if (_player.LocalEntity is EntityUid localEntity) + if (stationAiSystem.TryGetCore(localEntity, out var core) && core.Comp is not null) + sourceGrid = _entManager.GetComponent(core.Owner).GridUid; + var gridUid = playerXform?.GridUid - ?? (stationAiOverlay is { AllowCrossGrid: true } ? _lastGridUid : EntityUid.Invalid); + ?? (stationAiOverlay is { AllowCrossGrid: true } || + _entManager.HasComponent(_player.LocalEntity) + ? _lastGridUid + : EntityUid.Invalid); if (gridUid != EntityUid.Invalid) _lastGridUid = gridUid; @@ -113,7 +123,7 @@ protected override void Draw(in OverlayDrawArgs args) _visibleTiles.Clear(); // Starlight - start _visibleTileTags.Clear(); - _entManager.System().GetView((gridUid, broadphase, grid), worldBounds, _visibleTiles, _visibleTileTags); + _entManager.System().GetView((gridUid, broadphase, grid), worldBounds, _visibleTiles, _visibleTileTags, sourceGrid: sourceGrid); // Starlight - end } diff --git a/Content.Client/_Starlight/Shuttles/UI/NavScreen.cs b/Content.Client/_Starlight/Shuttles/UI/NavScreen.cs new file mode 100644 index 000000000000..6af5a813dd21 --- /dev/null +++ b/Content.Client/_Starlight/Shuttles/UI/NavScreen.cs @@ -0,0 +1,20 @@ +// ReSharper disable CheckNamespace + +using Robust.Shared.Map; + +namespace Content.Client.Shuttles.UI; + +public sealed partial class NavScreen +{ + public event Action? OnRadarClick; + + partial void InitializeStarlight() + { + NavRadar.OnRadarClick += OnRadarClickPressed; + } + + private void OnRadarClickPressed(EntityCoordinates coordinates) + { + OnRadarClick?.Invoke(coordinates); + } +} diff --git a/Content.Client/_Starlight/Shuttles/UI/ShuttleConsoleWindow.cs b/Content.Client/_Starlight/Shuttles/UI/ShuttleConsoleWindow.cs new file mode 100644 index 000000000000..a54c9f93949b --- /dev/null +++ b/Content.Client/_Starlight/Shuttles/UI/ShuttleConsoleWindow.cs @@ -0,0 +1,20 @@ +// ReSharper disable CheckNamespace + +using Robust.Shared.Map; + +namespace Content.Client.Shuttles.UI; + +public sealed partial class ShuttleConsoleWindow +{ + public event Action? RadarClicked; + + partial void InitializeStarlight() + { + NavContainer.OnRadarClick += OnRadarClick; + } + + private void OnRadarClick(EntityCoordinates coordinates) + { + RadarClicked?.Invoke(coordinates); + } +} diff --git a/Content.Server/DeviceNetwork/Systems/StationLimitedNetworkSystem.cs b/Content.Server/DeviceNetwork/Systems/StationLimitedNetworkSystem.cs index fbb8b2e35a0b..2857e24ab1ea 100644 --- a/Content.Server/DeviceNetwork/Systems/StationLimitedNetworkSystem.cs +++ b/Content.Server/DeviceNetwork/Systems/StationLimitedNetworkSystem.cs @@ -59,7 +59,8 @@ private void OnBeforePacketSent(EntityUid uid, StationLimitedNetworkComponent co if (!component.StationId.HasValue) TrySetStationId(uid, component); - if (!CheckStationId(args.Sender, component.AllowNonStationPackets, component.StationId)) + if (!CheckStationId(args.Sender, component.AllowNonStationPackets, component.StationId) && + !CanAccessSenderGrid(uid, args.Sender)) // Starlight: allow access if the sender and receiver are on grids that can access each other { args.Cancel(); } diff --git a/Content.Server/Holopad/HolopadSystem.cs b/Content.Server/Holopad/HolopadSystem.cs index 1afd6c2ba17f..ac7113cb29e8 100644 --- a/Content.Server/Holopad/HolopadSystem.cs +++ b/Content.Server/Holopad/HolopadSystem.cs @@ -105,6 +105,11 @@ private void OnHolopadStartNewCall(Entity source, ref HolopadS if (!TryComp(receiver, out var receiverTelephone)) return; + // Starlight - start + if (HasComp(args.Actor) && + !_stationAiSystem.CanAccessGrid((args.Actor, null), Transform(receiver).GridUid)) + return; + // Starlight - end LinkHolopadToUser(source, args.Actor); _telephoneSystem.CallTelephone((source, sourceTelephone), (receiver, receiverTelephone), args.Actor); } @@ -127,15 +132,6 @@ private void OnHolopadAnswerCall(Entity receiver, ref HolopadA if (_stationAiSystem.TryGetCore(args.Actor, out var stationAiCore)) _userInterfaceSystem.CloseUi(receiver.Owner, HolopadUiKey.AiRequestWindow, args.Actor); - // Try to warn the AI if the source of the call is out of its range - if (TryComp(stationAiCore, out var stationAiTelephone) && - TryComp(source, out var sourceTelephone) && - !_telephoneSystem.IsSourceInRangeOfReceiver((stationAiCore.Owner, stationAiTelephone), (source.Value.Owner, sourceTelephone))) - { - _popupSystem.PopupEntity(Loc.GetString("holopad-ai-is-unable-to-reach-holopad"), receiver, args.Actor); - return; - } - ActivateProjector(source.Value, args.Actor); } @@ -673,8 +669,18 @@ private void ActivateProjector(Entity entity, EntityUid user) var source = new Entity(stationAiCore, stationAiTelephone); + // Starlight - start + // We check if the AI has grid access. Normal projector call are range limited, not limited by grid access. The AI is special. + if (!_stationAiSystem.CanAccessGrid((user, userAiHeld), Transform(entity).GridUid)) + { + _popupSystem.PopupEntity(Loc.GetString("holopad-ai-is-unable-to-activate-projector"), receiver, user); + return; + } + // Starlight - end + // Check if the AI is unable to activate the projector (unlikely this will ever pass; its just a safeguard) - if (!_telephoneSystem.IsSourceInRangeOfReceiver(source, receiver)) + if (!_telephoneSystem.IsTelephonePowered(source) || + !_telephoneSystem.IsTelephonePowered(receiver)) // Starlight: check replaced with sensible check based on power { _popupSystem.PopupEntity(Loc.GetString("holopad-ai-is-unable-to-activate-projector"), receiver, user); return; @@ -685,6 +691,7 @@ private void ActivateProjector(Entity entity, EntityUid user) var callOptions = new TelephoneCallOptions() { + IgnoreRange = true, // Starlight: we do check things in advance and have safeguards in place ForceConnect = true, MuteReceiver = true }; diff --git a/Content.Server/Shuttles/Systems/ShuttleConsoleSystem.Drone.cs b/Content.Server/Shuttles/Systems/ShuttleConsoleSystem.Drone.cs index eb5e11b8ebeb..8883e28821b9 100644 --- a/Content.Server/Shuttles/Systems/ShuttleConsoleSystem.Drone.cs +++ b/Content.Server/Shuttles/Systems/ShuttleConsoleSystem.Drone.cs @@ -2,11 +2,23 @@ using Content.Server.Shuttles.Events; using Content.Shared.Station.Components; using Content.Shared.UserInterface; +using Content.Server.Power.EntitySystems; +using Content.Shared._Starlight.Maps; namespace Content.Server.Shuttles.Systems; public sealed partial class ShuttleConsoleSystem { + #region Starlight + [Dependency] private SharedGridAccessSystem _gridAccess = default!; + private readonly Dictionary _remoteGridAccess = new(); + + private void OnDroneConsoleStartup(EntityUid uid, DroneConsoleComponent component, ComponentStartup args) + { + UpdateRemoteGridAccess(uid, component); + } + #endregion Starlight + /// /// Gets the drone console target if applicable otherwise returns itself. /// @@ -30,26 +42,82 @@ public void RefreshDroneConsoles() while (query.MoveNext(out var uid, out var comp)) { - comp.Entity = GetShuttleConsole(uid, comp); + UpdateRemoteGridAccess(uid, comp); // Starlight } } private void OnDronePilotConsoleOpen(EntityUid uid, DroneConsoleComponent component, AfterActivatableUIOpenEvent args) { - component.Entity = GetShuttleConsole(uid); + UpdateRemoteGridAccess(uid, component); // Starlight } private void OnDronePilotConsoleClose(EntityUid uid, DroneConsoleComponent component, BoundUIClosedEvent args) { // Only if last person closed UI. if (!_ui.IsUiOpen(uid, args.UiKey)) + { component.Entity = null; + _remoteGridAccess.Remove(uid); // We only remove the access to the remote grid, not changing grid access + } } private void OnCargoGetConsole(EntityUid uid, DroneConsoleComponent component, ref ConsoleShuttleEvent args) { - args.Console = GetShuttleConsole(uid, component); + UpdateRemoteGridAccess(uid, component); // Starlight + args.Console = component.Entity; + } + + #region Starlight + private void UpdateRemoteGridAccess(EntityUid uid, DroneConsoleComponent component) + { + var targetConsole = GetShuttleConsole(uid, component); + var sourceGrid = Transform(uid).GridUid; + var targetGrid = targetConsole is { } target ? Transform(target).GridUid : null; + + if (_remoteGridAccess.TryGetValue(uid, out var previous) + && (sourceGrid != previous.SourceGrid || targetGrid != previous.TargetGrid + || !IsConsoleOperational(uid) || targetConsole is not { } currentTarget || !IsConsoleOperational(currentTarget))) + { + RemoveRemoteGridAccess(uid); + } + + component.Entity = targetConsole; + + if (sourceGrid is not { } source || targetGrid is not { } targetUid || + targetConsole is not { } console || + !IsConsoleOperational(uid) || !IsConsoleOperational(console)) + { + return; + } + + if (_remoteGridAccess.ContainsKey(uid)) + return; + + _gridAccess.AddAccessibleGrid((source, null), (targetUid, null)); + _remoteGridAccess[uid] = (source, targetUid); + } + + private void RemoveRemoteGridAccess(EntityUid uid) + { + if (!_remoteGridAccess.Remove(uid, out var access)) + return; + + foreach (var other in _remoteGridAccess.Values) + { + if (other != access) + continue; + + return; + } + + _gridAccess.RemoveAccessibleGrid((access.SourceGrid, null), (access.TargetGrid, null)); + } + + private bool IsConsoleOperational(EntityUid uid) + { + return TryComp(uid, out _) && MetaData(uid).EntityLifeStage < EntityLifeStage.Terminating && Transform(uid).Anchored && this.IsPowered(uid, EntityManager); } + #endregion Starlight /// /// Gets the relevant shuttle console to proxy from the drone console. diff --git a/Content.Server/Shuttles/Systems/ShuttleConsoleSystem.cs b/Content.Server/Shuttles/Systems/ShuttleConsoleSystem.cs index 401a7513334a..ed121fa1edbb 100644 --- a/Content.Server/Shuttles/Systems/ShuttleConsoleSystem.cs +++ b/Content.Server/Shuttles/Systems/ShuttleConsoleSystem.cs @@ -29,6 +29,9 @@ using Content.Server._Starlight.Shuttles.Components; using Content.Shared._Starlight.Shuttles.Components; using Content.Shared._Starlight.Shuttles.BUIStates; +using Content.Shared.Medical.CrewMonitoring; +using Content.Shared.Silicons.StationAi; +using Content.Server.Silicons.StationAi; namespace Content.Server.Shuttles.Systems; @@ -48,6 +51,7 @@ public sealed partial class ShuttleConsoleSystem : SharedShuttleConsoleSystem [Dependency] private ILogManager _log = default!; [Dependency] private RadarLaserSystem _laserSystem = default!; // _Starlight [Dependency] private IGameTiming _timing = default!; // _Starlight + [Dependency] private StationAiSystem _stationAiSystem = default!; // Starlight #region Starlight // Periodic blip/laser update @@ -86,11 +90,18 @@ public override void Initialize() SubscribeLocalEvent(OnConsoleShutdown); SubscribeLocalEvent(OnConsolePowerChange); SubscribeLocalEvent(OnConsoleAnchorChange); + // Starlight - start + SubscribeLocalEvent(OnDroneConsoleStartup); + // Starlight - end SubscribeLocalEvent(OnConsoleUIOpenAttempt); Subs.BuiEvents(ShuttleConsoleUiKey.Key, subs => { subs.Event(OnBeaconFTLMessage); subs.Event(OnPositionFTLMessage); + subs.Event((uid, component, args) => + { + HandleWarpRequest(args); + }); // Starlight subs.Event(OnConsoleUIClose); }); @@ -98,6 +109,10 @@ public override void Initialize() SubscribeLocalEvent(OnDronePilotConsoleOpen); Subs.BuiEvents(ShuttleConsoleUiKey.Key, subs => { + subs.Event((uid, component, args) => + { + HandleWarpRequest(args); + }); // Starlight subs.Event(OnDronePilotConsoleClose); }); @@ -187,6 +202,26 @@ private void OnConsoleUIOpenAttempt(EntityUid uid, ShuttleConsoleComponent compo TryPilot(args.User, uid); } + #region Starlight + private void HandleWarpRequest(CrewMonitoringWarpRequestMessage args) + { + if (args.Actor is not { Valid: true } actor || !HasComp(actor)) + return; + + EntityCoordinates coordinates; + try + { + coordinates = GetCoordinates(args.Coordinates); + } + catch + { + return; + } + + _stationAiSystem.TryWarpEyeToCoordinates(actor, coordinates); + } + #endregion + private void OnConsoleAnchorChange(EntityUid uid, ShuttleConsoleComponent component, ref AnchorStateChangedEvent args) { @@ -200,6 +235,9 @@ private void OnConsolePowerChange(EntityUid uid, ShuttleConsoleComponent compone DockingInterfaceState? dockState = null; DockingPortStates? dockingPortStates = null; // Starlight UpdateState(uid, ref dockState, ref dockingPortStates); // Starlight + // Starlight - start + RefreshDroneConsoles(); + // Starlight - end } private bool TryPilot(EntityUid user, EntityUid uid) @@ -414,6 +452,10 @@ protected override void HandlePilotShutdown(EntityUid uid, PilotComponent compon private void OnConsoleShutdown(EntityUid uid, ShuttleConsoleComponent component, ComponentShutdown args) { ClearPilots(component); + // Starlight - start + RemoveRemoteGridAccess(uid); + RefreshDroneConsoles(); + // Starlight - end } public void AddPilot(EntityUid uid, EntityUid entity, ShuttleConsoleComponent component) diff --git a/Content.Server/Silicons/StationAi/StationAiSystem.cs b/Content.Server/Silicons/StationAi/StationAiSystem.cs index c136705bf205..d006980b3e49 100644 --- a/Content.Server/Silicons/StationAi/StationAiSystem.cs +++ b/Content.Server/Silicons/StationAi/StationAiSystem.cs @@ -37,25 +37,17 @@ using Robust.Shared.Player; using Robust.Shared.Prototypes; using static Content.Server.Chat.Systems.ChatSystem; - -#region Starlight using Content.Server.Medical.SuitSensors; using Content.Shared.Follower.Components; using Content.Shared.Follower; using Content.Shared.Humanoid; -using Content.Shared.Intellicard; using Content.Shared.Medical.SuitSensor; using Content.Shared.Medical.SuitSensors; using Content.Shared.Mobs.Components; using Content.Shared.Warps; -using Content.Shared._Starlight.Silicons.Borgs; -using Robust.Shared.Localization; -using Robust.Shared.Log; using Robust.Shared.Map; -using System.Collections.Generic; using Content.Shared._Starlight.StationAi; using Content.Shared.Tag; -#endregion Starlight namespace Content.Server.Silicons.StationAi; @@ -199,11 +191,10 @@ private void OnStationAiWarpRequest(StationAiWarpRequestEvent msg, EntitySession return; } - var aiStation = _station.GetOwningStation(coreEntity.Owner); var targets = new List(); - CollectCrewWarpTargets(actor, aiStation, targets); - CollectLocationWarpTargets(actor, aiStation, coreEntity.Comp.RemoteEntity, targets); + CollectCrewWarpTargets(actor, targets); // Starlight + CollectLocationWarpTargets(actor, coreEntity.Comp.RemoteEntity, targets); // Starlight if (targets.Count == 0) _warpSawmill.Debug($"No warp targets available for Station AI {Name(actor)} ({actor})."); @@ -226,6 +217,12 @@ private void OnStationAiWarpToTarget(StationAiWarpToTargetEvent msg, EntitySessi return; } + if (!CanAccessGrid((actor, null), Transform(target).GridUid)) // Starlight + { + _warpSawmill.Debug($"Station AI {Name(actor)} ({actor}) attempted to warp to inaccessible target {Name(target)} ({target})."); + return; + } + if (!TryWarpEyeToEntity(actor, target)) _warpSawmill.Debug($"Station AI {Name(actor)} ({actor}) warp to {Name(target)} ({target}) rejected by TryWarpEyeToEntity."); } @@ -233,7 +230,7 @@ private void OnStationAiWarpToTarget(StationAiWarpToTargetEvent msg, EntitySessi /// /// Populates the warp target buffer with crew members whose suit sensors are broadcasting coordinates. /// - private void CollectCrewWarpTargets(EntityUid actor, EntityUid? aiStation, List buffer) + private void CollectCrewWarpTargets(EntityUid actor, List buffer) { var processed = new HashSet(); var enumerator = EntityQueryEnumerator(); @@ -257,12 +254,10 @@ private void CollectCrewWarpTargets(EntityUid actor, EntityUid? aiStation, List< if (!HasComp(ownerUid)) continue; - if (aiStation is { } station) - { - var ownerStation = _station.GetOwningStation(ownerUid); - if (ownerStation != station) - continue; - } + // Starlight - start + if (!CanAccessGrid((actor, null), Transform(ownerUid).GridUid)) + continue; + // Starlight - end // Don't show crew members outside of camera view if (_aiVision.IsOutsideCameraViewCached(ownerUid)) // starlight @@ -276,7 +271,7 @@ private void CollectCrewWarpTargets(EntityUid actor, EntityUid? aiStation, List< } } - private void CollectLocationWarpTargets(EntityUid actor, EntityUid? aiStation, EntityUid? remoteEntity, List buffer) + private void CollectLocationWarpTargets(EntityUid actor, EntityUid? remoteEntity, List buffer) { var query = AllEntityQuery(); @@ -296,15 +291,13 @@ private void CollectLocationWarpTargets(EntityUid actor, EntityUid? aiStation, E continue; // Starlight End - if (aiStation is { } station) + // Starlight - start + if (!CanAccessGrid((actor, null), Transform(uid).GridUid)) { - var warpStation = _station.GetOwningStation(uid); - if (warpStation != station) - { - _warpSawmill.Debug($"Skipping warp point {Name(uid)} ({uid}) outside AI station {station}."); - continue; - } + _warpSawmill.Debug($"Skipping warp point {Name(uid)} ({uid}) outside AI grid access list."); + continue; } + // Starlight - end var name = warp.Location ?? Name(uid); buffer.Add(new StationAiWarpTarget(GetNetEntity(uid), name, StationAiWarpTargetType.Location)); @@ -349,15 +342,10 @@ bool Fail() if (!_map.TryFindGridAt(mapCoordinates, out var gridUid, out _)) return Fail(); - var aiStation = _station.GetOwningStation(coreUid); - - if (aiStation != null) - { - var targetStation = _station.GetOwningStation(gridUid); - - if (targetStation != aiStation) - return Fail(); - } + // Starlight - start + if (!CanAccessGrid((user, null), gridUid)) + return Fail(); + // Starlight - end var targetCoords = _xforms.ToCoordinates((gridUid, Transform(gridUid)), mapCoordinates); @@ -393,6 +381,12 @@ bool Fail() var remoteXform = Transform(remoteEye); + if (!CanAccessGrid((user, null), Transform(target).GridUid)) + { + StopFollowingTarget(remoteEye, target); + return Fail(); + } + if ((TryComp(target, out WarpPointComponent? warp) && warp.Follow) || HasComp(target)) { var orbit = !HasComp(user); @@ -418,6 +412,19 @@ bool Fail() return TryWarpEyeToCoordinates(user, Transform(target).Coordinates, popupOnFailure); } + private void StopFollowingTarget(EntityUid remoteEye, EntityUid target) + { + if (_activeFollowTargets.TryGetValue(target, out var follower) && follower == remoteEye) + _activeFollowTargets.Remove(target); + + if (!HasComp(remoteEye)) + return; + + var parent = Transform(remoteEye).ParentUid; + if (parent == target) + _followerSystem.StopFollowingEntity(remoteEye, target); + } + private void OnSuitSensorModeChanged(Entity ent, ref SuitSensorModeChangedEvent args) { if (args.Mode == SuitSensorMode.SensorCords) diff --git a/Content.Server/_Starlight/DeviceNetwork/Systems/StationLimitedNetworkSystem.cs b/Content.Server/_Starlight/DeviceNetwork/Systems/StationLimitedNetworkSystem.cs new file mode 100644 index 000000000000..a742dcb72bea --- /dev/null +++ b/Content.Server/_Starlight/DeviceNetwork/Systems/StationLimitedNetworkSystem.cs @@ -0,0 +1,18 @@ +// ReSharper disable CheckNamespace +using Content.Shared._Starlight.Maps; +using Robust.Shared.Map; + +namespace Content.Server.DeviceNetwork.Systems; + +public sealed partial class StationLimitedNetworkSystem +{ + [Dependency] private SharedGridAccessSystem _gridAccess = default!; + + private bool CanAccessSenderGrid(EntityUid receiver, EntityUid sender) + { + var receiverGrid = Transform(receiver).GridUid; + var senderGrid = Transform(sender).GridUid; + + return receiverGrid is { } targetGrid && senderGrid is { } sourceGrid && (_gridAccess.CanAccess((sourceGrid, null), (targetGrid, null)) || _gridAccess.CanAccess((targetGrid, null), (sourceGrid, null))); + } +} diff --git a/Content.Shared/Movement/Systems/SharedMoverController.Input.cs b/Content.Shared/Movement/Systems/SharedMoverController.Input.cs index 4158dca64133..106d92d66a37 100644 --- a/Content.Shared/Movement/Systems/SharedMoverController.Input.cs +++ b/Content.Shared/Movement/Systems/SharedMoverController.Input.cs @@ -1,4 +1,4 @@ -using System.Numerics; +using System.Numerics; using System.Security.Cryptography; using Content.Shared._Starlight.Actions.Handlers; using Content.Shared.Alert; @@ -12,6 +12,7 @@ using Robust.Shared.Input.Binding; using Robust.Shared.Map.Components; using Robust.Shared.Physics; +using Robust.Shared.Physics.Components; using Robust.Shared.Player; using Robust.Shared.Prototypes; using Robust.Shared.Serialization; @@ -330,8 +331,11 @@ private void OnInputParentChange(Entity entity, ref EntPare private void OnAnchorState(Entity entity, ref AnchorStateChangedEvent args) { - if (!args.Anchored) - PhysicsSystem.SetBodyType(entity, BodyType.KinematicController); + if (args.Anchored || EntityManager.IsQueuedForDeletion(entity.Owner) || + !TryComp(entity.Owner, out _)) + return; // Starlight: cleaner checks that prevent a lot of exceptions + + PhysicsSystem.TrySetBodyType(entity, BodyType.KinematicController); // Starlight: we use try instead of set because sometimes its not available yet and causes exceptions } private void HandleDirChange(EntityUid entity, Direction dir, ushort subTick, bool state) diff --git a/Content.Shared/Silicons/StationAi/SharedStationAiSystem.Held.cs b/Content.Shared/Silicons/StationAi/SharedStationAiSystem.Held.cs index 6991c349abdf..662de9c69503 100644 --- a/Content.Shared/Silicons/StationAi/SharedStationAiSystem.Held.cs +++ b/Content.Shared/Silicons/StationAi/SharedStationAiSystem.Held.cs @@ -6,9 +6,7 @@ using Robust.Shared.Serialization; using Robust.Shared.Utility; using System.Diagnostics.CodeAnalysis; -#region Starlight using Content.Shared._Starlight.StationAi; -#endregion namespace Content.Shared.Silicons.StationAi; @@ -188,10 +186,11 @@ private void OnTargetVerbs(Entity ent, ref GetVerbs return; } - // Starlight Start - if (_vision.IsOutsideCameraViewCached(args.Target)) + // Starlight - start + if (!CanAccessGrid((args.User, null), Transform(args.Target).GridUid) || + _vision.IsOutsideCameraViewCached(args.Target)) return; - // Starlight End + // Starlight - end var user = args.User; var target = args.Target; diff --git a/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs b/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs index 9e6e8635fc37..8b5de2c91125 100644 --- a/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs +++ b/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs @@ -35,8 +35,6 @@ using Robust.Shared.Serialization; using Robust.Shared.Timing; using Robust.Shared.Utility; - -#region Starlight using Content.Shared._Starlight.Silicons.Borgs; using Content.Shared.Silicons.Borgs.Components; using Content.Shared._Starlight.TextToSpeech; @@ -46,7 +44,6 @@ using Content.Shared.DeviceLinking; using Content.Shared._Starlight; using Content.Shared.NameModifier.EntitySystems; -#endregion Starlight namespace Content.Shared.Silicons.StationAi; @@ -195,7 +192,6 @@ private void OnAiAccessible(Entity ent, ref Accessibl args.Accessible = true; } - private void OnAiMenu(Entity ent, ref MenuVisibilityEvent args) { args.Visibility &= ~MenuVisibility.NoFov; @@ -212,11 +208,12 @@ private void OnAiBuiCheck(Entity ent, ref BoundUser //// Similar to the inrange check but more optimised so server doesn't die. var targetXform = Transform(args.Target); - // No cross-grid - if (targetXform.GridUid != args.Actor.Comp.GridUid) + // Starlight - start + if (!CanAccessGrid((args.Actor, null), targetXform.GridUid)) // Starlight { return; } + // Starlight - end // //if (!_broadphaseQuery.TryComp(targetXform.GridUid, out var broadphase) || !_gridQuery.TryComp(targetXform.GridUid, out var grid)) //{ @@ -250,8 +247,15 @@ private void OnAiInRange(Entity ent, ref InRangeOverr var targetXform = Transform(target); - // No cross-grid - if (targetXform.GridUid != Transform(args.User).GridUid && !ent.Comp.AllowCrossGrid) + // Starlight the AI can access remote grids in some situation + if (HasComp(args.User)) + { + // Starlight - start + if (!CanAccessGrid((args.User, null), targetXform.GridUid)) + return; + // Starlight - end + } + else if (targetXform.GridUid != Transform(args.User).GridUid && !ent.Comp.AllowCrossGrid) { return; } diff --git a/Content.Shared/Silicons/StationAi/StationAiVisionSystem.cs b/Content.Shared/Silicons/StationAi/StationAiVisionSystem.cs index c0fad779622a..f4fd2dc51515 100644 --- a/Content.Shared/Silicons/StationAi/StationAiVisionSystem.cs +++ b/Content.Shared/Silicons/StationAi/StationAiVisionSystem.cs @@ -1,5 +1,6 @@ using Content.Shared.Power.EntitySystems; using Content.Shared.StationAi; +using Content.Shared._Starlight.Maps; using Robust.Shared.Map.Components; using Robust.Shared.Physics; using Robust.Shared.Threading; @@ -20,7 +21,11 @@ public sealed partial class StationAiVisionSystem : EntitySystem [Dependency] private SharedMapSystem _maps = default!; [Dependency] private SharedTransformSystem _xforms = default!; [Dependency] private SharedPowerReceiverSystem _power = default!; - [Dependency] private IGameTiming _timing = default!; // STARLIGHT + + #region Starlight + [Dependency] private SharedGridAccessSystem _gridAccess = default!; + [Dependency] private IGameTiming _timing = default!; + #endregion private SeedJob _seedJob; private ViewJob _job; @@ -264,7 +269,7 @@ private bool IsOccluded(Entity grid, Vect /// /// How much to expand the bounds before to find vision intersecting it. Makes this the largest vision size + 1 tile. /// Starlight: added visibleTileTags - public void GetView(Entity grid, Box2Rotated worldBounds, HashSet visibleTiles, Dictionary> visibleTileTags, float expansionSize = 8.5f) + public void GetView(Entity grid, Box2Rotated worldBounds, HashSet visibleTiles, Dictionary> visibleTileTags, float expansionSize = 8.5f, EntityUid? sourceGrid = null) { _viewportTiles.Clear(); // Starlight - start @@ -288,6 +293,15 @@ public void GetView(Entity grid, Box2Rota if (!seed.Comp.Enabled) continue; + // Starlight - start + // Ensure we only render for grids we have access to + if (sourceGrid is { } source && Transform(seed.Owner).GridUid is { } seedGrid && + !_gridAccess.CanAccess((source, null), (seedGrid, null))) + { + continue; + } + // Starlight - end + if (seed.Comp.NeedsPower && !_power.IsPowered(seed.Owner)) continue; diff --git a/Content.Shared/_Starlight/Maps/GridAccessComponent.cs b/Content.Shared/_Starlight/Maps/GridAccessComponent.cs new file mode 100644 index 000000000000..152be961ca28 --- /dev/null +++ b/Content.Shared/_Starlight/Maps/GridAccessComponent.cs @@ -0,0 +1,16 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared._Starlight.Maps; + +/// +/// Describes which grids can be accessed from this grid. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class GridAccessComponent : Component +{ + /// + /// Grids accessible from this grid. The owning grid is always included. + /// + [DataField, AutoNetworkedField] + public HashSet AccessibleGrids = new(); +} diff --git a/Content.Shared/_Starlight/Maps/SharedGridAccessSystem.cs b/Content.Shared/_Starlight/Maps/SharedGridAccessSystem.cs new file mode 100644 index 000000000000..b956429b9e1c --- /dev/null +++ b/Content.Shared/_Starlight/Maps/SharedGridAccessSystem.cs @@ -0,0 +1,74 @@ +using Robust.Shared.Map.Components; + +namespace Content.Shared._Starlight.Maps; + +/// +/// Manages access from one map grid to other map grids. +/// +public sealed class SharedGridAccessSystem : EntitySystem +{ + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnGridInitialize); + } + + /// + /// Returns whether can be accessed from . + /// + public bool CanAccess(Entity sourceGrid, Entity targetGrid) + { + Resolve(sourceGrid, ref sourceGrid.Comp); + Resolve(targetGrid, ref targetGrid.Comp); + + return TryComp(sourceGrid.Owner, out var access) && access.AccessibleGrids.Contains(targetGrid.Owner); + } + + /// + /// Adds a grid to the access list of another grid. + /// + public bool AddAccessibleGrid(Entity sourceGrid, Entity targetGrid) + { + Resolve(sourceGrid, ref sourceGrid.Comp); + Resolve(targetGrid, ref targetGrid.Comp); + + if (sourceGrid.Comp is null || targetGrid.Comp is null) + return false; + + var access = EnsureComp(sourceGrid.Owner); + if (!access.AccessibleGrids.Add(targetGrid.Owner)) + return false; + + Dirty(sourceGrid.Owner, access); + return true; + } + + /// + /// Removes a grid from the access list of another grid. A grid always retains access to itself. + /// + public bool RemoveAccessibleGrid(Entity sourceGrid, Entity targetGrid) + { + Resolve(sourceGrid, ref sourceGrid.Comp); + Resolve(targetGrid, ref targetGrid.Comp); + + if (sourceGrid.Owner == targetGrid.Owner || sourceGrid.Comp is null || targetGrid.Comp is null || + !TryComp(sourceGrid.Owner, out var access)) + return false; + + if (!access.AccessibleGrids.Remove(targetGrid.Owner)) + return false; + + Dirty(sourceGrid.Owner, access); + return true; + } + + private void OnGridInitialize(GridInitializeEvent ev) + { + if (!HasComp(ev.EntityUid)) + return; + + var access = EnsureComp(ev.EntityUid); + if (access.AccessibleGrids.Add(ev.EntityUid)) + Dirty(ev.EntityUid, access); + } +} diff --git a/Content.Shared/_Starlight/Silicons/StationAi/SharedStationAiSystem.GridAccess.cs b/Content.Shared/_Starlight/Silicons/StationAi/SharedStationAiSystem.GridAccess.cs new file mode 100644 index 000000000000..a114c525c49f --- /dev/null +++ b/Content.Shared/_Starlight/Silicons/StationAi/SharedStationAiSystem.GridAccess.cs @@ -0,0 +1,25 @@ +// ReSharper disable CheckNamespace + +using Content.Shared._Starlight.Maps; +using Robust.Shared.Map.Components; + +namespace Content.Shared.Silicons.StationAi; + +public abstract partial class SharedStationAiSystem +{ + [Dependency] private SharedGridAccessSystem _gridAccess = default!; + + public bool CanAccessGrid(Entity user, EntityUid? targetGrid) + { + Resolve(user, ref user.Comp); + + if (targetGrid is not { } target || !HasComp(target)) + return false; + + if (user.Comp is null || !TryGetCore(user.Owner, out var core) || core.Comp is null) + return false; + + var sourceGrid = Transform(core.Owner).GridUid; + return sourceGrid is { } source && _gridAccess.CanAccess((source, null), (target, null)); + } +} diff --git a/Resources/Prototypes/Entities/Structures/Machines/holopad.yml b/Resources/Prototypes/Entities/Structures/Machines/holopad.yml index f82610f0556b..2ec952d101e1 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/holopad.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/holopad.yml @@ -118,6 +118,7 @@ - type: Telephone transmissionRange: Map compatibleRanges: + - Grid # Starlight for SharedGridAccessSystem - Map - Unlimited ignoreTelephonesOnSameGrid: true