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