Cargo shuttle changes (#14363)

This commit is contained in:
metalgearsloth
2023-03-23 16:10:49 +11:00
committed by GitHub
parent 7f4bb7fe8a
commit 569f30b721
26 changed files with 523 additions and 628 deletions

View File

@@ -1,12 +1,12 @@
using Content.Client.Cargo.UI; using Content.Client.Cargo.UI;
using Content.Shared.Cargo.BUI; using Content.Shared.Cargo.BUI;
using Content.Shared.Cargo.Events; using JetBrains.Annotations;
using Robust.Client.GameObjects; using Robust.Client.GameObjects;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
namespace Content.Client.Cargo.BUI; namespace Content.Client.Cargo.BUI;
[UsedImplicitly]
public sealed class CargoShuttleConsoleBoundUserInterface : BoundUserInterface public sealed class CargoShuttleConsoleBoundUserInterface : BoundUserInterface
{ {
private CargoShuttleMenu? _menu; private CargoShuttleMenu? _menu;
@@ -21,9 +21,7 @@ public sealed class CargoShuttleConsoleBoundUserInterface : BoundUserInterface
if (collection == null) if (collection == null)
return; return;
_menu = new CargoShuttleMenu(collection.Resolve<IGameTiming>(), collection.Resolve<IPrototypeManager>(), collection.Resolve<IEntitySystemManager>().GetEntitySystem<SpriteSystem>()); _menu = new CargoShuttleMenu(collection.Resolve<IPrototypeManager>(), collection.Resolve<IEntitySystemManager>().GetEntitySystem<SpriteSystem>());
_menu.ShuttleCallRequested += OnShuttleCall;
_menu.ShuttleRecallRequested += OnShuttleRecall;
_menu.OnClose += Close; _menu.OnClose += Close;
_menu.OpenCentered(); _menu.OpenCentered();
@@ -38,24 +36,12 @@ public sealed class CargoShuttleConsoleBoundUserInterface : BoundUserInterface
} }
} }
private void OnShuttleRecall()
{
SendMessage(new CargoRecallShuttleMessage());
}
private void OnShuttleCall()
{
SendMessage(new CargoCallShuttleMessage());
}
protected override void UpdateState(BoundUserInterfaceState state) protected override void UpdateState(BoundUserInterfaceState state)
{ {
base.UpdateState(state); base.UpdateState(state);
if (state is not CargoShuttleConsoleBoundUserInterfaceState cargoState) return; if (state is not CargoShuttleConsoleBoundUserInterfaceState cargoState) return;
_menu?.SetAccountName(cargoState.AccountName); _menu?.SetAccountName(cargoState.AccountName);
_menu?.SetShuttleName(cargoState.ShuttleName); _menu?.SetShuttleName(cargoState.ShuttleName);
_menu?.SetShuttleETA(cargoState.ShuttleETA);
_menu?.SetOrders(cargoState.Orders); _menu?.SetOrders(cargoState.Orders);
_menu?.SetCanRecall(cargoState.CanRecall);
} }
} }

View File

@@ -22,12 +22,6 @@
<Label Name="ShuttleStatusLabel" <Label Name="ShuttleStatusLabel"
Text="{Loc 'cargo-console-menu-shuttle-status-away-text'}" /> Text="{Loc 'cargo-console-menu-shuttle-status-away-text'}" />
</BoxContainer> </BoxContainer>
<Button Name="ShuttleCallButton"
Text="Call Shuttle"/>
<Button Name="ShuttleRecallButton"
Text="Recall Shuttle"
ToolTip="Needs to be out of range to recall."
Visible="False"/>
<Label Text="{Loc 'cargo-console-menu-orders-label'}" /> <Label Text="{Loc 'cargo-console-menu-orders-label'}" />
<PanelContainer VerticalExpand="True" <PanelContainer VerticalExpand="True"
SizeFlagsStretchRatio="6"> SizeFlagsStretchRatio="6">

View File

@@ -3,8 +3,6 @@ using Content.Shared.Cargo;
using Content.Shared.Cargo.Prototypes; using Content.Shared.Cargo.Prototypes;
using Robust.Client.AutoGenerated; using Robust.Client.AutoGenerated;
using Robust.Client.GameObjects; using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML; using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using Robust.Shared.Timing; using Robust.Shared.Timing;
@@ -14,23 +12,14 @@ namespace Content.Client.Cargo.UI
[GenerateTypedNameReferences] [GenerateTypedNameReferences]
public sealed partial class CargoShuttleMenu : FancyWindow public sealed partial class CargoShuttleMenu : FancyWindow
{ {
private readonly IGameTiming _timing;
private readonly IPrototypeManager _protoManager; private readonly IPrototypeManager _protoManager;
private readonly SpriteSystem _spriteSystem; private readonly SpriteSystem _spriteSystem;
public Action? ShuttleCallRequested; public CargoShuttleMenu(IPrototypeManager protoManager, SpriteSystem spriteSystem)
public Action? ShuttleRecallRequested;
private TimeSpan? _shuttleEta;
public CargoShuttleMenu(IGameTiming timing, IPrototypeManager protoManager, SpriteSystem spriteSystem)
{ {
RobustXamlLoader.Load(this); RobustXamlLoader.Load(this);
_timing = timing;
_protoManager = protoManager; _protoManager = protoManager;
_spriteSystem = spriteSystem; _spriteSystem = spriteSystem;
ShuttleCallButton.OnPressed += OnCallPressed;
ShuttleRecallButton.OnPressed += OnRecallPressed;
Title = Loc.GetString("cargo-shuttle-console-menu-title"); Title = Loc.GetString("cargo-shuttle-console-menu-title");
} }
@@ -44,33 +33,6 @@ namespace Content.Client.Cargo.UI
ShuttleNameLabel.Text = name; ShuttleNameLabel.Text = name;
} }
public void SetShuttleETA(TimeSpan? eta)
{
_shuttleEta = eta;
if (eta == null)
{
ShuttleCallButton.Visible = false;
ShuttleRecallButton.Visible = true;
}
else
{
ShuttleRecallButton.Visible = false;
ShuttleCallButton.Visible = true;
ShuttleCallButton.Disabled = true;
}
}
private void OnRecallPressed(BaseButton.ButtonEventArgs obj)
{
ShuttleRecallRequested?.Invoke();
}
private void OnCallPressed(BaseButton.ButtonEventArgs obj)
{
ShuttleCallRequested?.Invoke();
}
public void SetOrders(List<CargoOrderData> orders) public void SetOrders(List<CargoOrderData> orders)
{ {
Orders.DisposeAllChildren(); Orders.DisposeAllChildren();
@@ -102,27 +64,5 @@ namespace Content.Client.Cargo.UI
Orders.AddChild(row); Orders.AddChild(row);
} }
} }
public void SetCanRecall(bool canRecall)
{
ShuttleRecallButton.Disabled = !canRecall;
}
protected override void Draw(DrawingHandleScreen handle)
{
base.Draw(handle);
var remaining = _shuttleEta - _timing.CurTime;
if (remaining == null || remaining <= TimeSpan.Zero)
{
ShuttleStatusLabel.Text = $"Available";
ShuttleCallButton.Disabled = false;
}
else
{
ShuttleStatusLabel.Text = $"Available in: {remaining.Value.TotalSeconds:0.0}";
}
}
} }
} }

View File

@@ -1,5 +1,6 @@
using Content.Shared.Shuttles.BUIStates; using Content.Shared.Shuttles.BUIStates;
using Content.Shared.Shuttles.Components; using Content.Shared.Shuttles.Components;
using Content.Shared.Shuttles.Systems;
using Robust.Client.Graphics; using Robust.Client.Graphics;
using Robust.Client.UserInterface; using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.Controls;
@@ -33,14 +34,14 @@ public sealed class RadarControl : Control
private Angle? _rotation; private Angle? _rotation;
private float _radarMinRange = 64f; private float _radarMinRange = SharedRadarConsoleSystem.DefaultMinRange;
private float _radarMaxRange = 256f; private float _radarMaxRange = SharedRadarConsoleSystem.DefaultMaxRange;
public float RadarRange { get; private set; } = 256f; public float RadarRange { get; private set; } = SharedRadarConsoleSystem.DefaultMinRange;
/// <summary> /// <summary>
/// We'll lerp between the radarrange and actual range /// We'll lerp between the radarrange and actual range
/// </summary> /// </summary>
private float _actualRadarRange = 256f; private float _actualRadarRange = SharedRadarConsoleSystem.DefaultMinRange;
/// <summary> /// <summary>
/// Controls the maximum distance that IFF labels will display. /// Controls the maximum distance that IFF labels will display.
@@ -87,14 +88,11 @@ public sealed class RadarControl : Control
{ {
_radarMaxRange = ls.MaxRange; _radarMaxRange = ls.MaxRange;
if (_radarMaxRange < RadarRange)
{
_actualRadarRange = _radarMaxRange;
}
if (_radarMaxRange < _radarMinRange) if (_radarMaxRange < _radarMinRange)
_radarMinRange = _radarMaxRange; _radarMinRange = _radarMaxRange;
_actualRadarRange = Math.Clamp(_actualRadarRange, _radarMinRange, _radarMaxRange);
_docks.Clear(); _docks.Clear();
foreach (var state in ls.Docks) foreach (var state in ls.Docks)

View File

@@ -19,7 +19,7 @@ namespace Content.Server.Alert.Click
if (entManager.TryGetComponent(player, out PilotComponent? pilotComponent) && if (entManager.TryGetComponent(player, out PilotComponent? pilotComponent) &&
pilotComponent.Console != null) pilotComponent.Console != null)
{ {
entManager.System<ShuttleConsoleSystem>().RemovePilot(pilotComponent); entManager.System<ShuttleConsoleSystem>().RemovePilot(player, pilotComponent);
} }
} }
} }

View File

@@ -263,20 +263,25 @@ namespace Content.Server.Cargo.Systems
private void UpdateOrders(StationCargoOrderDatabaseComponent component) private void UpdateOrders(StationCargoOrderDatabaseComponent component)
{ {
// Order added so all consoles need updating. // Order added so all consoles need updating.
foreach (var comp in EntityQuery<CargoOrderConsoleComponent>(true)) var orderQuery = AllEntityQuery<CargoOrderConsoleComponent>();
while (orderQuery.MoveNext(out var uid, out var comp))
{ {
var station = _station.GetOwningStation(component.Owner); var station = _station.GetOwningStation(uid);
if (station != component.Owner) continue; if (station != component.Owner)
continue;
UpdateOrderState(comp, station); UpdateOrderState(comp, station);
} }
foreach (var comp in EntityQuery<CargoShuttleConsoleComponent>(true)) var consoleQuery = AllEntityQuery<CargoShuttleConsoleComponent>();
while (consoleQuery.MoveNext(out var uid, out var comp))
{ {
var station = _station.GetOwningStation(component.Owner); var station = _station.GetOwningStation(uid);
if (station != component.Owner) continue; if (station != component.Owner)
continue;
UpdateShuttleState(comp, station); UpdateShuttleState(uid, comp, station);
} }
} }

View File

@@ -17,20 +17,16 @@ using Content.Shared.Cargo.Prototypes;
using Content.Shared.CCVar; using Content.Shared.CCVar;
using Content.Shared.Dataset; using Content.Shared.Dataset;
using Content.Shared.GameTicking; using Content.Shared.GameTicking;
using Content.Shared.Mobs.Components; using Content.Shared.Whitelist;
using Content.Shared.Mobs.Systems;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;
using Robust.Server.Maps;
using Robust.Shared.Audio;
using Robust.Shared.Configuration; using Robust.Shared.Configuration;
using Robust.Shared.Map; using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Player;
using Robust.Shared.Random; using Robust.Shared.Random;
using Robust.Shared.Timing;
using Robust.Shared.Utility; using Robust.Shared.Utility;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using Content.Shared.Coordinates; using Content.Shared.Coordinates;
using Content.Shared.Mobs.Components;
using Robust.Shared.Map.Components;
namespace Content.Server.Cargo.Systems; namespace Content.Server.Cargo.Systems;
@@ -40,22 +36,19 @@ public sealed partial class CargoSystem
* Handles cargo shuttle mechanics, including cargo shuttle consoles. * Handles cargo shuttle mechanics, including cargo shuttle consoles.
*/ */
[Dependency] private readonly IComponentFactory _factory = default!;
[Dependency] private readonly IConfigurationManager _configManager = default!; [Dependency] private readonly IConfigurationManager _configManager = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly EntityLookupSystem _lookup = default!; [Dependency] private readonly EntityLookupSystem _lookup = default!;
[Dependency] private readonly MapLoaderSystem _map = default!; [Dependency] private readonly MapLoaderSystem _map = default!;
[Dependency] private readonly MobStateSystem _mobState = default!;
[Dependency] private readonly PricingSystem _pricing = default!; [Dependency] private readonly PricingSystem _pricing = default!;
[Dependency] private readonly ShuttleConsoleSystem _console = default!; [Dependency] private readonly ShuttleConsoleSystem _console = default!;
[Dependency] private readonly ShuttleSystem _shuttle = default!; [Dependency] private readonly ShuttleSystem _shuttle = default!;
[Dependency] private readonly StackSystem _stack = default!; [Dependency] private readonly StackSystem _stack = default!;
public MapId? CargoMap { get; private set; } public MapId? CargoMap { get; private set; }
private const float CallOffset = 50f;
private int _index; private int _index;
/// <summary> /// <summary>
@@ -69,10 +62,11 @@ public sealed partial class CargoSystem
// Don't want to immediately call this as shuttles will get setup in the natural course of things. // Don't want to immediately call this as shuttles will get setup in the natural course of things.
_configManager.OnValueChanged(CCVars.CargoShuttles, SetCargoShuttleEnabled); _configManager.OnValueChanged(CCVars.CargoShuttles, SetCargoShuttleEnabled);
SubscribeLocalEvent<CargoShuttleComponent, MoveEvent>(OnCargoShuttleMove); SubscribeLocalEvent<CargoShuttleComponent, FTLStartedEvent>(OnCargoFTLStarted);
SubscribeLocalEvent<CargoShuttleComponent, FTLCompletedEvent>(OnCargoFTLCompleted);
SubscribeLocalEvent<CargoShuttleComponent, FTLTagEvent>(OnCargoFTLTag);
SubscribeLocalEvent<CargoShuttleConsoleComponent, ComponentStartup>(OnCargoShuttleConsoleStartup); SubscribeLocalEvent<CargoShuttleConsoleComponent, ComponentStartup>(OnCargoShuttleConsoleStartup);
SubscribeLocalEvent<CargoShuttleConsoleComponent, CargoCallShuttleMessage>(OnCargoShuttleCall);
SubscribeLocalEvent<CargoShuttleConsoleComponent, CargoRecallShuttleMessage>(RecallCargoShuttle);
SubscribeLocalEvent<CargoPalletConsoleComponent, CargoPalletSellMessage>(OnPalletSale); SubscribeLocalEvent<CargoPalletConsoleComponent, CargoPalletSellMessage>(OnPalletSale);
SubscribeLocalEvent<CargoPalletConsoleComponent, CargoPalletAppraiseMessage>(OnPalletAppraise); SubscribeLocalEvent<CargoPalletConsoleComponent, CargoPalletAppraiseMessage>(OnPalletAppraise);
@@ -87,6 +81,16 @@ public sealed partial class CargoSystem
SubscribeLocalEvent<RoundRestartCleanupEvent>(OnRoundRestart); SubscribeLocalEvent<RoundRestartCleanupEvent>(OnRoundRestart);
} }
private void OnCargoFTLTag(EntityUid uid, CargoShuttleComponent component, ref FTLTagEvent args)
{
if (args.Handled)
return;
// Just saves mappers forgetting.
args.Handled = true;
args.Tag = "DockCargo";
}
private void ShutdownShuttle() private void ShutdownShuttle()
{ {
_configManager.UnsubValueChanged(CCVars.CargoShuttles, SetCargoShuttleEnabled); _configManager.UnsubValueChanged(CCVars.CargoShuttles, SetCargoShuttleEnabled);
@@ -94,7 +98,9 @@ public sealed partial class CargoSystem
private void SetCargoShuttleEnabled(bool value) private void SetCargoShuttleEnabled(bool value)
{ {
if (_enabled == value) return; if (_enabled == value)
return;
_enabled = value; _enabled = value;
if (value) if (value)
@@ -116,7 +122,7 @@ public sealed partial class CargoSystem
private void OnCargoPilotConsoleOpen(EntityUid uid, CargoPilotConsoleComponent component, AfterActivatableUIOpenEvent args) private void OnCargoPilotConsoleOpen(EntityUid uid, CargoPilotConsoleComponent component, AfterActivatableUIOpenEvent args)
{ {
component.Entity = GetShuttleConsole(component); component.Entity = GetShuttleConsole(uid);
} }
private void OnCargoPilotConsoleClose(EntityUid uid, CargoPilotConsoleComponent component, BoundUIClosedEvent args) private void OnCargoPilotConsoleClose(EntityUid uid, CargoPilotConsoleComponent component, BoundUIClosedEvent args)
@@ -126,30 +132,50 @@ public sealed partial class CargoSystem
private void OnCargoGetConsole(EntityUid uid, CargoPilotConsoleComponent component, ref ConsoleShuttleEvent args) private void OnCargoGetConsole(EntityUid uid, CargoPilotConsoleComponent component, ref ConsoleShuttleEvent args)
{ {
args.Console = GetShuttleConsole(component); args.Console = GetShuttleConsole(uid);
} }
private EntityUid? GetShuttleConsole(CargoPilotConsoleComponent component) private EntityUid? GetShuttleConsole(EntityUid uid)
{ {
var stationUid = _station.GetOwningStation(component.Owner); var stationUid = _station.GetOwningStation(uid);
if (!TryComp<StationCargoOrderDatabaseComponent>(stationUid, out var orderDatabase) || if (!TryComp<StationCargoOrderDatabaseComponent>(stationUid, out var orderDatabase) ||
!TryComp<CargoShuttleComponent>(orderDatabase.Shuttle, out var shuttle)) return null; !TryComp<CargoShuttleComponent>(orderDatabase.Shuttle, out var shuttle))
{
return null;
}
return GetShuttleConsole(shuttle); return GetShuttleConsole(orderDatabase.Shuttle.Value, shuttle);
} }
#endregion #endregion
#region Console #region Console
private void UpdateShuttleCargoConsoles(CargoShuttleComponent component) private void UpdateCargoShuttleConsoles(CargoShuttleComponent component)
{ {
foreach (var console in EntityQuery<CargoShuttleConsoleComponent>(true)) // Update pilot consoles that are already open.
var pilotConsoleQuery = AllEntityQuery<CargoPilotConsoleComponent>();
while (pilotConsoleQuery.MoveNext(out var uid, out var console))
{ {
var stationUid = _station.GetOwningStation(console.Owner); var stationUid = _station.GetOwningStation(uid);
if (stationUid != component.Station) continue; if (stationUid == null || stationUid != component.Station)
UpdateShuttleState(console, stationUid); continue;
console.Entity = GetShuttleConsole(stationUid.Value);
}
// Update order consoles.
var shuttleConsoleQuery = AllEntityQuery<CargoShuttleConsoleComponent>();
while (shuttleConsoleQuery.MoveNext(out var uid, out var console))
{
var stationUid = _station.GetOwningStation(uid);
if (stationUid != component.Station)
continue;
UpdateShuttleState(uid, console, stationUid);
} }
} }
@@ -198,10 +224,10 @@ public sealed partial class CargoSystem
private void OnCargoShuttleConsoleStartup(EntityUid uid, CargoShuttleConsoleComponent component, ComponentStartup args) private void OnCargoShuttleConsoleStartup(EntityUid uid, CargoShuttleConsoleComponent component, ComponentStartup args)
{ {
var station = _station.GetOwningStation(uid); var station = _station.GetOwningStation(uid);
UpdateShuttleState(component, station); UpdateShuttleState(uid, component, station);
} }
private void UpdateShuttleState(CargoShuttleConsoleComponent component, EntityUid? station = null) private void UpdateShuttleState(EntityUid uid, CargoShuttleConsoleComponent component, EntityUid? station = null)
{ {
TryComp<StationCargoOrderDatabaseComponent>(station, out var orderDatabase); TryComp<StationCargoOrderDatabaseComponent>(station, out var orderDatabase);
TryComp<CargoShuttleComponent>(orderDatabase?.Shuttle, out var shuttle); TryComp<CargoShuttleComponent>(orderDatabase?.Shuttle, out var shuttle);
@@ -209,12 +235,10 @@ public sealed partial class CargoSystem
var orders = GetProjectedOrders(orderDatabase, shuttle); var orders = GetProjectedOrders(orderDatabase, shuttle);
var shuttleName = orderDatabase?.Shuttle != null ? MetaData(orderDatabase.Shuttle.Value).EntityName : string.Empty; var shuttleName = orderDatabase?.Shuttle != null ? MetaData(orderDatabase.Shuttle.Value).EntityName : string.Empty;
_uiSystem.GetUiOrNull(component.Owner, CargoConsoleUiKey.Shuttle)?.SetState( _uiSystem.GetUiOrNull(uid, CargoConsoleUiKey.Shuttle)?.SetState(
new CargoShuttleConsoleBoundUserInterfaceState( new CargoShuttleConsoleBoundUserInterfaceState(
station != null ? MetaData(station.Value).EntityName : Loc.GetString("cargo-shuttle-console-station-unknown"), station != null ? MetaData(station.Value).EntityName : Loc.GetString("cargo-shuttle-console-station-unknown"),
string.IsNullOrEmpty(shuttleName) ? Loc.GetString("cargo-shuttle-console-shuttle-not-found") : shuttleName, string.IsNullOrEmpty(shuttleName) ? Loc.GetString("cargo-shuttle-console-shuttle-not-found") : shuttleName,
_shuttle.CanFTL(shuttle?.Owner, out _),
shuttle?.NextCall,
orders)); orders));
} }
@@ -222,32 +246,21 @@ public sealed partial class CargoSystem
#region Shuttle #region Shuttle
public EntityUid? GetShuttleConsole(CargoShuttleComponent component) public EntityUid? GetShuttleConsole(EntityUid uid, CargoShuttleComponent component)
{ {
foreach (var (comp, xform) in EntityQuery<ShuttleConsoleComponent, TransformComponent>(true)) var query = AllEntityQuery<ShuttleConsoleComponent, TransformComponent>();
while (query.MoveNext(out var cUid, out var comp, out var xform))
{ {
if (xform.ParentUid != component.Owner) continue; if (xform.ParentUid != uid)
return comp.Owner; continue;
return cUid;
} }
return null; return null;
} }
private void OnCargoShuttleMove(EntityUid uid, CargoShuttleComponent component, ref MoveEvent args)
{
if (component.Station == null) return;
var oldCanRecall = component.CanRecall;
// Check if we can update the recall status.
var canRecall = _shuttle.CanFTL(uid, out _, args.Component);
if (oldCanRecall == canRecall) return;
component.CanRecall = canRecall;
_sawmill.Debug($"Updated CanRecall for {ToPrettyString(uid)}");
UpdateShuttleCargoConsoles(component);
}
/// <summary> /// <summary>
/// Returns the orders that can fit on the cargo shuttle. /// Returns the orders that can fit on the cargo shuttle.
/// </summary> /// </summary>
@@ -329,7 +342,10 @@ public sealed partial class CargoSystem
if (CargoMap == null || if (CargoMap == null ||
component.Shuttle != null || component.Shuttle != null ||
component.CargoShuttleProto == null) return; component.CargoShuttleProto == null)
{
return;
}
var prototype = _protoMan.Index<CargoShuttlePrototype>(component.CargoShuttleProto); var prototype = _protoMan.Index<CargoShuttlePrototype>(component.CargoShuttleProto);
var possibleNames = _protoMan.Index<DatasetPrototype>(prototype.NameDataset).Values; var possibleNames = _protoMan.Index<DatasetPrototype>(prototype.NameDataset).Values;
@@ -348,11 +364,9 @@ public sealed partial class CargoSystem
xform.LocalPosition += 100 * _index; xform.LocalPosition += 100 * _index;
var comp = EnsureComp<CargoShuttleComponent>(shuttleUid); var comp = EnsureComp<CargoShuttleComponent>(shuttleUid);
comp.Station = component.Owner; comp.Station = component.Owner;
comp.Coordinates = xform.Coordinates;
component.Shuttle = shuttleUid; component.Shuttle = shuttleUid;
comp.NextCall = _timing.CurTime + TimeSpan.FromSeconds(comp.Cooldown); UpdateCargoShuttleConsoles(comp);
UpdateShuttleCargoConsoles(comp);
_index++; _index++;
_sawmill.Info($"Added cargo shuttle to {ToPrettyString(shuttleUid)}"); _sawmill.Info($"Added cargo shuttle to {ToPrettyString(shuttleUid)}");
} }
@@ -400,80 +414,6 @@ public sealed partial class CargoSystem
} }
} }
private void SendToCargoMap(EntityUid uid, CargoShuttleComponent? component = null)
{
if (!Resolve(uid, ref component))
return;
component.NextCall = _timing.CurTime + TimeSpan.FromSeconds(component.Cooldown);
Transform(uid).Coordinates = component.Coordinates;
DebugTools.Assert(MetaData(uid).EntityPaused);
UpdateShuttleCargoConsoles(component);
_sawmill.Info($"Stashed cargo shuttle {ToPrettyString(uid)}");
}
/// <summary>
/// Retrieves a shuttle for delivery.
/// </summary>
public void CallShuttle(StationCargoOrderDatabaseComponent orderDatabase)
{
if (!TryComp<CargoShuttleComponent>(orderDatabase.Shuttle, out var shuttle))
return;
// Already called / not available
if (shuttle.NextCall == null || _timing.CurTime < shuttle.NextCall)
return;
if (!TryComp<StationDataComponent>(orderDatabase.Owner, out var stationData))
return;
var targetGrid = _station.GetLargestGrid(stationData);
// Nowhere to warp in to.
if (!TryComp<TransformComponent>(targetGrid, out var xform))
return;
shuttle.NextCall = null;
// Find a valid free area nearby to spawn in on
// TODO: Make this use hyperspace now.
var center = new Vector2();
var minRadius = 0f;
Box2? aabb = null;
var xformQuery = GetEntityQuery<TransformComponent>();
foreach (var grid in _mapManager.GetAllMapGrids(xform.MapID))
{
var worldAABB = xformQuery.GetComponent(grid.Owner).WorldMatrix.TransformBox(grid.LocalAABB);
aabb = aabb?.Union(worldAABB) ?? worldAABB;
}
if (aabb != null)
{
center = aabb.Value.Center;
minRadius = MathF.Max(aabb.Value.Width, aabb.Value.Height);
}
var offset = 0f;
if (TryComp<MapGridComponent>(orderDatabase.Shuttle, out var shuttleGrid))
{
var bounds = shuttleGrid.LocalAABB;
offset = MathF.Max(bounds.Width, bounds.Height) / 2f;
}
Transform(shuttle.Owner).Coordinates = new EntityCoordinates(xform.ParentUid,
center + _random.NextVector2(minRadius + offset, minRadius + CallOffset + offset));
DebugTools.Assert(!MetaData(shuttle.Owner).EntityPaused);
AddCargoContents(shuttle, orderDatabase);
UpdateOrders(orderDatabase);
UpdateShuttleCargoConsoles(shuttle);
_console.RefreshShuttleConsoles();
_sawmill.Info($"Retrieved cargo shuttle {ToPrettyString(shuttle.Owner)} from {ToPrettyString(orderDatabase.Owner)}");
}
private void AddCargoContents(CargoShuttleComponent shuttle, StationCargoOrderDatabaseComponent orderDatabase) private void AddCargoContents(CargoShuttleComponent shuttle, StationCargoOrderDatabaseComponent orderDatabase)
{ {
var xformQuery = GetEntityQuery<TransformComponent>(); var xformQuery = GetEntityQuery<TransformComponent>();
@@ -510,74 +450,37 @@ public sealed partial class CargoSystem
UpdatePalletConsoleInterface(uid, component); UpdatePalletConsoleInterface(uid, component);
} }
private void RecallCargoShuttle(EntityUid uid, CargoShuttleConsoleComponent component, CargoRecallShuttleMessage args) private void OnCargoFTLStarted(EntityUid uid, CargoShuttleComponent component, ref FTLStartedEvent args)
{ {
var player = args.Session.AttachedEntity; var xform = Transform(uid);
var stationUid = component.Station;
if (player == null) return; // Called
if (xform.MapID != CargoMap ||
var stationUid = _station.GetOwningStation(uid); !TryComp<StationCargoOrderDatabaseComponent>(stationUid, out var orderDatabase))
if (!TryComp<StationCargoOrderDatabaseComponent>(stationUid, out var orderDatabase) ||
!TryComp<StationBankAccountComponent>(stationUid, out var bank)) return;
if (!TryComp<CargoShuttleComponent>(orderDatabase.Shuttle, out var shuttle))
{ {
_popup.PopupEntity(Loc.GetString("cargo-no-shuttle"), args.Entity, args.Entity);
return; return;
} }
if (!_shuttle.CanFTL(shuttle.Owner, out var reason)) AddCargoContents(component, orderDatabase);
{ UpdateOrders(orderDatabase);
_popup.PopupEntity(reason, args.Entity, args.Entity); UpdateCargoShuttleConsoles(component);
}
private void OnCargoFTLCompleted(EntityUid uid, CargoShuttleComponent component, ref FTLCompletedEvent args)
{
var xform = Transform(uid);
// Recalled
if (xform.MapID != CargoMap)
return; return;
}
if (IsBlocked(shuttle)) var stationUid = component.Station;
if (TryComp<StationBankAccountComponent>(stationUid, out var bank))
{ {
_popup.PopupEntity(Loc.GetString("cargo-shuttle-console-organics"), player.Value, player.Value); SellPallets(uid, out var amount);
_audio.PlayPvs(_audio.GetSound(component.DenySound), uid); bank.Balance += (int) amount;
return;
} }
SellPallets((EntityUid) orderDatabase.Shuttle, out double price);
bank.Balance += (int) price;
_console.RefreshShuttleConsoles();
SendToCargoMap(orderDatabase.Shuttle.Value);
}
/// <summary>
///
/// </summary>
/// <param name="component"></param>
private bool IsBlocked(CargoShuttleComponent component)
{
// TODO: Would be good to rate-limit this on the console.
var mobQuery = GetEntityQuery<MobStateComponent>();
var xformQuery = GetEntityQuery<TransformComponent>();
return FoundOrganics(component.Owner, mobQuery, xformQuery);
}
public bool FoundOrganics(EntityUid uid, EntityQuery<MobStateComponent> mobQuery, EntityQuery<TransformComponent> xformQuery)
{
var xform = xformQuery.GetComponent(uid);
var childEnumerator = xform.ChildEnumerator;
while (childEnumerator.MoveNext(out var child))
{
if ((mobQuery.TryGetComponent(child.Value, out var mobState) && !_mobState.IsDead(child.Value, mobState))
|| FoundOrganics(child.Value, mobQuery, xformQuery)) return true;
}
return false;
}
private void OnCargoShuttleCall(EntityUid uid, CargoShuttleConsoleComponent component, CargoCallShuttleMessage args)
{
var stationUid = _station.GetOwningStation(args.Entity);
if (!TryComp<StationCargoOrderDatabaseComponent>(stationUid, out var orderDatabase)) return;
CallShuttle(orderDatabase);
} }
#endregion #endregion
@@ -600,13 +503,15 @@ public sealed partial class CargoSystem
CargoMap = null; CargoMap = null;
// Shuttle may not have been in the cargo dimension (e.g. on the station map) so need to delete. // Shuttle may not have been in the cargo dimension (e.g. on the station map) so need to delete.
foreach (var comp in EntityQuery<CargoShuttleComponent>()) var query = AllEntityQuery<CargoShuttleComponent>();
while (query.MoveNext(out var uid, out var comp))
{ {
if (TryComp<StationCargoOrderDatabaseComponent>(comp.Station, out var station)) if (TryComp<StationCargoOrderDatabaseComponent>(comp.Station, out var station))
{ {
station.Shuttle = null; station.Shuttle = null;
} }
QueueDel(comp.Owner); QueueDel(uid);
} }
} }
@@ -619,11 +524,23 @@ public sealed partial class CargoSystem
// It gets mapinit which is okay... buuutt we still want it paused to avoid power draining. // It gets mapinit which is okay... buuutt we still want it paused to avoid power draining.
CargoMap = _mapManager.CreateMap(); CargoMap = _mapManager.CreateMap();
_mapManager.SetMapPaused(CargoMap!.Value, true); var mapUid = _mapManager.GetMapEntityId(CargoMap.Value);
var ftl = EnsureComp<FTLDestinationComponent>(_mapManager.GetMapEntityId(CargoMap.Value));
ftl.Whitelist = new EntityWhitelist()
{
Components = new[]
{
_factory.GetComponentName(typeof(CargoShuttleComponent))
}
};
MetaData(mapUid).EntityName = $"Trading post {_random.Next(1000):000}";
foreach (var comp in EntityQuery<StationCargoOrderDatabaseComponent>(true)) foreach (var comp in EntityQuery<StationCargoOrderDatabaseComponent>(true))
{ {
AddShuttle(comp); AddShuttle(comp);
} }
_console.RefreshShuttleConsoles();
} }
} }

View File

@@ -1,7 +0,0 @@
namespace Content.Server.Shuttles.Components;
/// <summary>
/// Given priority when considering where to dock an emergency shuttle.
/// </summary>
[RegisterComponent]
public sealed class EmergencyDockComponent : Component {}

View File

@@ -1,6 +1,8 @@
using Content.Shared.Shuttles.Systems; using Content.Shared.Shuttles.Systems;
using Content.Shared.Tag;
using Robust.Shared.Audio; using Robust.Shared.Audio;
using Robust.Shared.Map; using Robust.Shared.Map;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Server.Shuttles.Components; namespace Content.Server.Shuttles.Components;
@@ -37,6 +39,12 @@ public sealed class FTLComponent : Component
[ViewVariables(VVAccess.ReadWrite), DataField("dock")] [ViewVariables(VVAccess.ReadWrite), DataField("dock")]
public bool Dock; public bool Dock;
/// <summary>
/// If we're docking after FTL what is the prioritised dock tag (if applicable).
/// </summary>
[ViewVariables(VVAccess.ReadWrite), DataField("priorityTag", customTypeSerializer:typeof(PrototypeIdSerializer<TagPrototype>))]
public string? PriorityTag;
[ViewVariables(VVAccess.ReadWrite), DataField("soundTravel")] [ViewVariables(VVAccess.ReadWrite), DataField("soundTravel")]
public SoundSpecifier? TravelSound = new SoundPathSpecifier("/Audio/Effects/Shuttle/hyperspace_progress.ogg") public SoundSpecifier? TravelSound = new SoundPathSpecifier("/Audio/Effects/Shuttle/hyperspace_progress.ogg")
{ {

View File

@@ -0,0 +1,18 @@
using Content.Shared.Tag;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Server.Shuttles.Components;
/// <summary>
/// Given priority when considering where to dock.
/// </summary>
[RegisterComponent]
public sealed class PriorityDockComponent : Component
{
/// <summary>
/// Tag to match on the docking request, if this dock is to be prioritised.
/// </summary>
[ViewVariables(VVAccess.ReadWrite),
DataField("tag", customTypeSerializer: typeof(PrototypeIdSerializer<TagPrototype>))]
public string? Tag;
}

View File

@@ -1,5 +1,3 @@
using Content.Shared.Shuttles.Components;
namespace Content.Server.Shuttles.Components namespace Content.Server.Shuttles.Components
{ {
[RegisterComponent] [RegisterComponent]

View File

@@ -9,6 +9,8 @@ namespace Content.Server.Shuttles;
[AdminCommand(AdminFlags.Mapping)] [AdminCommand(AdminFlags.Mapping)]
public sealed class DockCommand : IConsoleCommand public sealed class DockCommand : IConsoleCommand
{ {
[Dependency] private readonly IEntityManager _entManager = default!;
public string Command => "dock"; public string Command => "dock";
public string Description => $"Attempts to dock 2 airlocks together. Doesn't check whether it is valid."; public string Description => $"Attempts to dock 2 airlocks together. Doesn't check whether it is valid.";
public string Help => $"{Command} <airlock entityuid1> <airlock entityuid2>"; public string Help => $"{Command} <airlock entityuid1> <airlock entityuid2>";
@@ -32,21 +34,19 @@ public sealed class DockCommand : IConsoleCommand
return; return;
} }
var entManager = IoCManager.Resolve<IEntityManager>(); if (!_entManager.TryGetComponent(airlock1, out DockingComponent? dock1))
if (!entManager.TryGetComponent(airlock1, out DockingComponent? dock1))
{ {
shell.WriteError($"No docking component found on {airlock1}"); shell.WriteError($"No docking component found on {airlock1}");
return; return;
} }
if (!entManager.TryGetComponent(airlock2, out DockingComponent? dock2)) if (!_entManager.TryGetComponent(airlock2, out DockingComponent? dock2))
{ {
shell.WriteError($"No docking component found on {airlock2}"); shell.WriteError($"No docking component found on {airlock2}");
return; return;
} }
var dockSystem = EntitySystem.Get<DockingSystem>(); var dockSystem = _entManager.System<DockingSystem>();
dockSystem.Dock(dock1, dock2); dockSystem.Dock(airlock1, dock1, airlock2, dock2);
} }
} }

View File

@@ -0,0 +1,7 @@
namespace Content.Server.Shuttles.Events;
/// <summary>
/// Raised when trying to get a priority tag for docking.
/// </summary>
[ByRefEvent]
public record struct FTLTagEvent(bool Handled, string? Tag);

View File

@@ -16,23 +16,25 @@ public sealed partial class DockingSystem
var dockingQuery = GetEntityQuery<DockingComponent>(); var dockingQuery = GetEntityQuery<DockingComponent>();
var xformQuery = GetEntityQuery<TransformComponent>(); var xformQuery = GetEntityQuery<TransformComponent>();
var recentQuery = GetEntityQuery<RecentlyDockedComponent>(); var recentQuery = GetEntityQuery<RecentlyDockedComponent>();
var query = EntityQueryEnumerator<AutoDockComponent, PhysicsComponent>();
foreach (var (comp, body) in EntityQuery<AutoDockComponent, PhysicsComponent>()) while (query.MoveNext(out var dockUid, out var comp, out var body))
{ {
if (comp.Requesters.Count == 0 || !dockingQuery.TryGetComponent(comp.Owner, out var dock)) if (comp.Requesters.Count == 0 || !dockingQuery.TryGetComponent(dockUid, out var dock))
{ {
RemComp<AutoDockComponent>(comp.Owner); RemComp<AutoDockComponent>(dockUid);
continue; continue;
} }
// Don't re-dock if we're already docked or recently were. // Don't re-dock if we're already docked or recently were.
if (dock.Docked || recentQuery.HasComponent(comp.Owner)) continue; if (dock.Docked || recentQuery.HasComponent(dockUid))
continue;
var dockable = GetDockable(body, xformQuery.GetComponent(comp.Owner)); var dockable = GetDockable(body, xformQuery.GetComponent(dockUid));
if (dockable == null) continue; if (dockable == null) continue;
TryDock(dock, dockable); TryDock(dockUid, dock, dockable.Owner, dockable);
} }
// Work out recent docks that have gone past their designated threshold. // Work out recent docks that have gone past their designated threshold.

View File

@@ -144,12 +144,13 @@ namespace Content.Server.Shuttles.Systems
private void Cleanup(DockingComponent dockA) private void Cleanup(DockingComponent dockA)
{ {
_pathfinding.RemovePortal(dockA.PathfindHandle); _pathfinding.RemovePortal(dockA.PathfindHandle);
_jointSystem.RemoveJoint(dockA.DockJoint!);
if (dockA.DockJoint != null)
_jointSystem.RemoveJoint(dockA.DockJoint);
var dockBUid = dockA.DockedWith; var dockBUid = dockA.DockedWith;
if (dockBUid == null || if (dockBUid == null ||
dockA.DockJoint == null ||
!TryComp(dockBUid, out DockingComponent? dockB)) !TryComp(dockBUid, out DockingComponent? dockB))
{ {
DebugTools.Assert(false); DebugTools.Assert(false);
@@ -187,9 +188,9 @@ namespace Content.Server.Shuttles.Systems
GridBUid = gridBUid!.Value, GridBUid = gridBUid!.Value,
}; };
EntityManager.EventBus.RaiseLocalEvent(dockA.Owner, msg, false); RaiseLocalEvent(dockA.Owner, msg);
EntityManager.EventBus.RaiseLocalEvent(dockB.Owner, msg, false); RaiseLocalEvent(dockB.Owner, msg);
EntityManager.EventBus.RaiseEvent(EventSource.Local, msg); RaiseLocalEvent(msg);
} }
private void OnStartup(EntityUid uid, DockingComponent component, ComponentStartup args) private void OnStartup(EntityUid uid, DockingComponent component, ComponentStartup args)
@@ -208,7 +209,7 @@ namespace Content.Server.Shuttles.Systems
var otherDock = EntityManager.GetComponent<DockingComponent>(component.DockedWith.Value); var otherDock = EntityManager.GetComponent<DockingComponent>(component.DockedWith.Value);
DebugTools.Assert(otherDock.DockedWith != null); DebugTools.Assert(otherDock.DockedWith != null);
Dock(component, otherDock); Dock(uid, component, component.DockedWith.Value, otherDock);
DebugTools.Assert(component.Docked && otherDock.Docked); DebugTools.Assert(component.Docked && otherDock.Docked);
} }
} }
@@ -234,7 +235,7 @@ namespace Content.Server.Shuttles.Systems
var other = Comp<DockingComponent>(component.DockedWith!.Value); var other = Comp<DockingComponent>(component.DockedWith!.Value);
Undock(component); Undock(component);
Dock(component, other); Dock(uid, component, component.DockedWith.Value, other);
_console.RefreshShuttleConsoles(); _console.RefreshShuttleConsoles();
} }
@@ -278,91 +279,96 @@ namespace Content.Server.Shuttles.Systems
/// <summary> /// <summary>
/// Docks 2 ports together and assumes it is valid. /// Docks 2 ports together and assumes it is valid.
/// </summary> /// </summary>
public void Dock(DockingComponent dockA, DockingComponent dockB) public void Dock(EntityUid dockAUid, DockingComponent dockA, EntityUid dockBUid, DockingComponent dockB)
{ {
if (dockB.Owner.GetHashCode() < dockA.Owner.GetHashCode()) if (dockBUid.GetHashCode() < dockAUid.GetHashCode())
{ {
(dockA, dockB) = (dockB, dockA); (dockA, dockB) = (dockB, dockA);
} }
_sawmill.Debug($"Docking between {dockA.Owner} and {dockB.Owner}"); _sawmill.Debug($"Docking between {dockAUid} and {dockBUid}");
// https://gamedev.stackexchange.com/questions/98772/b2distancejoint-with-frequency-equal-to-0-vs-b2weldjoint // https://gamedev.stackexchange.com/questions/98772/b2distancejoint-with-frequency-equal-to-0-vs-b2weldjoint
// We could also potentially use a prismatic joint? Depending if we want clamps that can extend or whatever // We could also potentially use a prismatic joint? Depending if we want clamps that can extend or whatever
var dockAXform = EntityManager.GetComponent<TransformComponent>(dockA.Owner); var dockAXform = EntityManager.GetComponent<TransformComponent>(dockAUid);
var dockBXform = EntityManager.GetComponent<TransformComponent>(dockB.Owner); var dockBXform = EntityManager.GetComponent<TransformComponent>(dockBUid);
DebugTools.Assert(dockAXform.GridUid != null); DebugTools.Assert(dockAXform.GridUid != null);
DebugTools.Assert(dockBXform.GridUid != null); DebugTools.Assert(dockBXform.GridUid != null);
var gridA = dockAXform.GridUid!.Value; var gridA = dockAXform.GridUid!.Value;
var gridB = dockBXform.GridUid!.Value; var gridB = dockBXform.GridUid!.Value;
SharedJointSystem.LinearStiffness( // May not be possible if map or the likes.
2f, if (TryComp<PhysicsComponent>(gridA, out var gridPhysicsA) &&
0.7f, TryComp<PhysicsComponent>(gridB, out var gridPhysicsB))
EntityManager.GetComponent<PhysicsComponent>(gridA).Mass,
EntityManager.GetComponent<PhysicsComponent>(gridB).Mass,
out var stiffness,
out var damping);
// These need playing around with
// Could also potentially have collideconnected false and stiffness 0 but it was a bit more suss???
WeldJoint joint;
// Pre-existing joint so use that.
if (dockA.DockJointId != null)
{ {
DebugTools.Assert(dockB.DockJointId == dockA.DockJointId); SharedJointSystem.LinearStiffness(
joint = _jointSystem.GetOrCreateWeldJoint(gridA, gridB, dockA.DockJointId); 2f,
} 0.7f,
else EntityManager.GetComponent<PhysicsComponent>(gridA).Mass,
{ EntityManager.GetComponent<PhysicsComponent>(gridB).Mass,
joint = _jointSystem.GetOrCreateWeldJoint(gridA, gridB, DockingJoint + dockA.Owner); out var stiffness,
out var damping);
// These need playing around with
// Could also potentially have collideconnected false and stiffness 0 but it was a bit more suss???
WeldJoint joint;
// Pre-existing joint so use that.
if (dockA.DockJointId != null)
{
DebugTools.Assert(dockB.DockJointId == dockA.DockJointId);
joint = _jointSystem.GetOrCreateWeldJoint(gridA, gridB, dockA.DockJointId);
}
else
{
joint = _jointSystem.GetOrCreateWeldJoint(gridA, gridB, DockingJoint + dockAUid);
}
var gridAXform = EntityManager.GetComponent<TransformComponent>(gridA);
var gridBXform = EntityManager.GetComponent<TransformComponent>(gridB);
var anchorA = dockAXform.LocalPosition + dockAXform.LocalRotation.ToWorldVec() / 2f;
var anchorB = dockBXform.LocalPosition + dockBXform.LocalRotation.ToWorldVec() / 2f;
joint.LocalAnchorA = anchorA;
joint.LocalAnchorB = anchorB;
joint.ReferenceAngle = (float) (gridBXform.WorldRotation - gridAXform.WorldRotation);
joint.CollideConnected = true;
joint.Stiffness = stiffness;
joint.Damping = damping;
dockA.DockJoint = joint;
dockA.DockJointId = joint.ID;
dockB.DockJoint = joint;
dockB.DockJointId = joint.ID;
} }
var gridAXform = EntityManager.GetComponent<TransformComponent>(gridA); dockA.DockedWith = dockBUid;
var gridBXform = EntityManager.GetComponent<TransformComponent>(gridB); dockB.DockedWith = dockAUid;
var anchorA = dockAXform.LocalPosition + dockAXform.LocalRotation.ToWorldVec() / 2f; if (TryComp(dockAUid, out DoorComponent? doorA))
var anchorB = dockBXform.LocalPosition + dockBXform.LocalRotation.ToWorldVec() / 2f;
joint.LocalAnchorA = anchorA;
joint.LocalAnchorB = anchorB;
joint.ReferenceAngle = (float) (gridBXform.WorldRotation - gridAXform.WorldRotation);
joint.CollideConnected = true;
joint.Stiffness = stiffness;
joint.Damping = damping;
dockA.DockedWith = dockB.Owner;
dockB.DockedWith = dockA.Owner;
dockA.DockJoint = joint;
dockA.DockJointId = joint.ID;
dockB.DockJoint = joint;
dockB.DockJointId = joint.ID;
if (TryComp(dockA.Owner, out DoorComponent? doorA))
{ {
if (_doorSystem.TryOpen(doorA.Owner, doorA)) if (_doorSystem.TryOpen(doorA.Owner, doorA))
{ {
doorA.ChangeAirtight = false; doorA.ChangeAirtight = false;
if (TryComp<AirlockComponent>(dockA.Owner, out var airlockA)) if (TryComp<AirlockComponent>(dockAUid, out var airlockA))
{ {
_airlocks.SetBoltsWithAudio(dockA.Owner, airlockA, true); _airlocks.SetBoltsWithAudio(dockAUid, airlockA, true);
} }
} }
} }
if (TryComp(dockB.Owner, out DoorComponent? doorB)) if (TryComp(dockBUid, out DoorComponent? doorB))
{ {
if (_doorSystem.TryOpen(doorB.Owner, doorB)) if (_doorSystem.TryOpen(doorB.Owner, doorB))
{ {
doorB.ChangeAirtight = false; doorB.ChangeAirtight = false;
if (TryComp<AirlockComponent>(dockB.Owner, out var airlockB)) if (TryComp<AirlockComponent>(dockBUid, out var airlockB))
{ {
_airlocks.SetBoltsWithAudio(dockB.Owner, airlockB, true); _airlocks.SetBoltsWithAudio(dockBUid, airlockB, true);
} }
} }
} }
@@ -381,9 +387,9 @@ namespace Content.Server.Shuttles.Systems
GridBUid = gridB, GridBUid = gridB,
}; };
EntityManager.EventBus.RaiseLocalEvent(dockA.Owner, msg, false); RaiseLocalEvent(dockAUid, msg);
EntityManager.EventBus.RaiseLocalEvent(dockB.Owner, msg, false); RaiseLocalEvent(dockBUid, msg);
EntityManager.EventBus.RaiseEvent(EventSource.Local, msg); RaiseLocalEvent(msg);
} }
private bool CanDock(DockingComponent dockA, DockingComponent dockB) private bool CanDock(DockingComponent dockA, DockingComponent dockB)
@@ -433,11 +439,11 @@ namespace Content.Server.Shuttles.Systems
/// <summary> /// <summary>
/// Attempts to dock 2 ports together and will return early if it's not possible. /// Attempts to dock 2 ports together and will return early if it's not possible.
/// </summary> /// </summary>
private void TryDock(DockingComponent dockA, DockingComponent dockB) private void TryDock(EntityUid dockAUid, DockingComponent dockA, EntityUid dockBUid, DockingComponent dockB)
{ {
if (!CanDock(dockA, dockB)) return; if (!CanDock(dockA, dockB)) return;
Dock(dockA, dockB); Dock(dockAUid, dockA, dockBUid, dockB);
} }
public void Undock(DockingComponent dock) public void Undock(DockingComponent dock)

View File

@@ -13,8 +13,8 @@ using Content.Shared.Shuttles.Systems;
using Content.Shared.Tag; using Content.Shared.Tag;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;
using Robust.Shared.GameStates; using Robust.Shared.GameStates;
using Robust.Shared.Map.Components;
using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Components;
using Robust.Shared.Player;
using Robust.Shared.Timing; using Robust.Shared.Timing;
using Robust.Shared.Utility; using Robust.Shared.Utility;
@@ -68,9 +68,10 @@ namespace Content.Server.Shuttles.Systems
return; return;
} }
if (!dest.Enabled) return; if (!dest.Enabled)
return;
EntityUid? entity = component.Owner; EntityUid? entity = uid;
var getShuttleEv = new ConsoleShuttleEvent var getShuttleEv = new ConsoleShuttleEvent
{ {
@@ -80,13 +81,14 @@ namespace Content.Server.Shuttles.Systems
RaiseLocalEvent(entity.Value, ref getShuttleEv); RaiseLocalEvent(entity.Value, ref getShuttleEv);
entity = getShuttleEv.Console; entity = getShuttleEv.Console;
if (entity == null || dest.Whitelist?.IsValid(entity.Value, EntityManager) == false) if (!TryComp<TransformComponent>(entity, out var xform) ||
!TryComp<ShuttleComponent>(xform.GridUid, out var shuttle))
{ {
return; return;
} }
if (!TryComp<TransformComponent>(entity, out var xform) || if (dest.Whitelist?.IsValid(entity.Value, EntityManager) == false &&
!TryComp<ShuttleComponent>(xform.GridUid, out var shuttle)) dest.Whitelist?.IsValid(xform.GridUid.Value, EntityManager) == false)
{ {
return; return;
} }
@@ -97,13 +99,17 @@ namespace Content.Server.Shuttles.Systems
return; return;
} }
if (!_shuttle.CanFTL(shuttle.Owner, out var reason)) if (!_shuttle.CanFTL(xform.GridUid, out var reason))
{ {
_popup.PopupCursor(reason, args.Session); _popup.PopupCursor(reason, args.Session);
return; return;
} }
_shuttle.FTLTravel(shuttle, args.Destination); var dock = HasComp<MapComponent>(args.Destination) && HasComp<MapGridComponent>(args.Destination);
var tagEv = new FTLTagEvent();
RaiseLocalEvent(xform.GridUid.Value, ref tagEv);
_shuttle.FTLTravel(shuttle, args.Destination, dock: dock, priorityTag: tagEv.Tag);
} }
private void OnDock(DockEvent ev) private void OnDock(DockEvent ev)
@@ -128,10 +134,11 @@ namespace Content.Server.Shuttles.Systems
public void RefreshShuttleConsoles() public void RefreshShuttleConsoles()
{ {
var docks = GetAllDocks(); var docks = GetAllDocks();
var query = AllEntityQuery<ShuttleConsoleComponent>();
foreach (var comp in EntityQuery<ShuttleConsoleComponent>(true)) while (query.MoveNext(out var uid, out var comp))
{ {
UpdateState(comp, docks); UpdateState(uid, comp, docks);
} }
} }
@@ -141,7 +148,10 @@ namespace Content.Server.Shuttles.Systems
private void OnConsoleUIClose(EntityUid uid, ShuttleConsoleComponent component, BoundUIClosedEvent args) private void OnConsoleUIClose(EntityUid uid, ShuttleConsoleComponent component, BoundUIClosedEvent args)
{ {
if ((ShuttleConsoleUiKey) args.UiKey != ShuttleConsoleUiKey.Key || if ((ShuttleConsoleUiKey) args.UiKey != ShuttleConsoleUiKey.Key ||
args.Session.AttachedEntity is not {} user) return; args.Session.AttachedEntity is not { } user)
{
return;
}
// In case they D/C should still clean them up. // In case they D/C should still clean them up.
foreach (var comp in EntityQuery<AutoDockComponent>(true)) foreach (var comp in EntityQuery<AutoDockComponent>(true))
@@ -160,12 +170,12 @@ namespace Content.Server.Shuttles.Systems
private void OnConsoleAnchorChange(EntityUid uid, ShuttleConsoleComponent component, ref AnchorStateChangedEvent args) private void OnConsoleAnchorChange(EntityUid uid, ShuttleConsoleComponent component, ref AnchorStateChangedEvent args)
{ {
UpdateState(component); UpdateState(uid, component);
} }
private void OnConsolePowerChange(EntityUid uid, ShuttleConsoleComponent component, ref PowerChangedEvent args) private void OnConsolePowerChange(EntityUid uid, ShuttleConsoleComponent component, ref PowerChangedEvent args)
{ {
UpdateState(component); UpdateState(uid, component);
} }
private bool TryPilot(EntityUid user, EntityUid uid) private bool TryPilot(EntityUid user, EntityUid uid)
@@ -184,7 +194,7 @@ namespace Content.Server.Shuttles.Systems
if (console != null) if (console != null)
{ {
RemovePilot(pilotComponent); RemovePilot(user, pilotComponent);
if (console == component) if (console == component)
{ {
@@ -208,16 +218,18 @@ namespace Content.Server.Shuttles.Systems
{ {
// TODO: NEED TO MAKE SURE THIS UPDATES ON ANCHORING CHANGES! // TODO: NEED TO MAKE SURE THIS UPDATES ON ANCHORING CHANGES!
var result = new List<DockingInterfaceState>(); var result = new List<DockingInterfaceState>();
var query = AllEntityQuery<DockingComponent, TransformComponent>();
foreach (var (comp, xform) in EntityQuery<DockingComponent, TransformComponent>(true)) while (query.MoveNext(out var uid, out var comp, out var xform))
{ {
if (xform.ParentUid != xform.GridUid) continue; if (xform.ParentUid != xform.GridUid)
continue;
var state = new DockingInterfaceState() var state = new DockingInterfaceState()
{ {
Coordinates = xform.Coordinates, Coordinates = xform.Coordinates,
Angle = xform.LocalRotation, Angle = xform.LocalRotation,
Entity = comp.Owner, Entity = uid,
Connected = comp.Docked, Connected = comp.Docked,
Color = comp.RadarColor, Color = comp.RadarColor,
HighlightedColor = comp.HighlightedRadarColor, HighlightedColor = comp.HighlightedRadarColor,
@@ -228,9 +240,9 @@ namespace Content.Server.Shuttles.Systems
return result; return result;
} }
private void UpdateState(ShuttleConsoleComponent component, List<DockingInterfaceState>? docks = null) private void UpdateState(EntityUid consoleUid, ShuttleConsoleComponent component, List<DockingInterfaceState>? docks = null)
{ {
EntityUid? entity = component.Owner; EntityUid? entity = consoleUid;
var getShuttleEv = new ConsoleShuttleEvent var getShuttleEv = new ConsoleShuttleEvent
{ {
@@ -242,38 +254,44 @@ namespace Content.Server.Shuttles.Systems
TryComp<TransformComponent>(entity, out var consoleXform); TryComp<TransformComponent>(entity, out var consoleXform);
TryComp<RadarConsoleComponent>(entity, out var radar); TryComp<RadarConsoleComponent>(entity, out var radar);
var range = radar?.MaxRange ?? 0f; var range = radar?.MaxRange ?? SharedRadarConsoleSystem.DefaultMaxRange;
TryComp<ShuttleComponent>(consoleXform?.GridUid, out var shuttle); var shuttleGridUid = consoleXform?.GridUid;
var destinations = new List<(EntityUid, string, bool)>(); var destinations = new List<(EntityUid, string, bool)>();
var ftlState = FTLState.Available; var ftlState = FTLState.Available;
var ftlTime = TimeSpan.Zero; var ftlTime = TimeSpan.Zero;
if (TryComp<FTLComponent>(shuttle?.Owner, out var shuttleFtl)) if (TryComp<FTLComponent>(shuttleGridUid, out var shuttleFtl))
{ {
ftlState = shuttleFtl.State; ftlState = shuttleFtl.State;
ftlTime = _timing.CurTime + TimeSpan.FromSeconds(shuttleFtl.Accumulator); ftlTime = _timing.CurTime + TimeSpan.FromSeconds(shuttleFtl.Accumulator);
} }
// Mass too large // Mass too large
if (entity != null && shuttle?.Owner != null && (!TryComp<PhysicsComponent>(shuttle?.Owner, out var shuttleBody) || if (entity != null && shuttleGridUid != null &&
shuttleBody.Mass < 1000f)) (!TryComp<PhysicsComponent>(shuttleGridUid, out var shuttleBody) || shuttleBody.Mass < 1000f))
{ {
var metaQuery = GetEntityQuery<MetaDataComponent>(); var metaQuery = GetEntityQuery<MetaDataComponent>();
// Can't go anywhere when in FTL. // Can't go anywhere when in FTL.
var locked = shuttleFtl != null || Paused(shuttle!.Owner); var locked = shuttleFtl != null || Paused(shuttleGridUid.Value);
// Can't cache it because it may have a whitelist for the particular console. // Can't cache it because it may have a whitelist for the particular console.
// Include paused as we still want to show CentCom. // Include paused as we still want to show CentCom.
foreach (var comp in EntityQuery<FTLDestinationComponent>(true)) var destQuery = AllEntityQuery<FTLDestinationComponent>();
{
// Can't warp to itself or if it's not on the whitelist.
if (comp.Owner == shuttle?.Owner ||
comp.Whitelist?.IsValid(entity.Value) == false) continue;
var meta = metaQuery.GetComponent(comp.Owner); while (destQuery.MoveNext(out var destUid, out var comp))
{
// Can't warp to itself or if it's not on the whitelist (console or shuttle).
if (destUid == shuttleGridUid ||
comp.Whitelist?.IsValid(entity.Value) == false &&
(shuttleGridUid == null || comp.Whitelist?.IsValid(shuttleGridUid.Value, EntityManager) == false))
{
continue;
}
var meta = metaQuery.GetComponent(destUid);
var name = meta.EntityName; var name = meta.EntityName;
if (string.IsNullOrEmpty(name)) if (string.IsNullOrEmpty(name))
@@ -283,19 +301,19 @@ namespace Content.Server.Shuttles.Systems
comp.Enabled && comp.Enabled &&
(!TryComp<FTLComponent>(comp.Owner, out var ftl) || ftl.State == FTLState.Cooldown); (!TryComp<FTLComponent>(comp.Owner, out var ftl) || ftl.State == FTLState.Cooldown);
// Can't travel to same map. // Can't travel to same map (yet)
if (canTravel && consoleXform?.MapUid == Transform(comp.Owner).MapUid) if (canTravel && consoleXform?.MapUid == Transform(destUid).MapUid)
{ {
canTravel = false; canTravel = false;
} }
destinations.Add((comp.Owner, name, canTravel)); destinations.Add((destUid, name, canTravel));
} }
} }
docks ??= GetAllDocks(); docks ??= GetAllDocks();
_ui.GetUiOrNull(component.Owner, ShuttleConsoleUiKey.Key) _ui.GetUiOrNull(consoleUid, ShuttleConsoleUiKey.Key)
?.SetState(new ShuttleConsoleBoundInterfaceState( ?.SetState(new ShuttleConsoleBoundInterfaceState(
ftlState, ftlState,
ftlTime, ftlTime,
@@ -311,12 +329,14 @@ namespace Content.Server.Shuttles.Systems
base.Update(frameTime); base.Update(frameTime);
var toRemove = new RemQueue<PilotComponent>(); var toRemove = new RemQueue<PilotComponent>();
var query = EntityQueryEnumerator<PilotComponent>();
foreach (var comp in EntityManager.EntityQuery<PilotComponent>()) while (query.MoveNext(out var uid, out var comp))
{ {
if (comp.Console == null) continue; if (comp.Console == null)
continue;
if (!_blocker.CanInteract(comp.Owner, comp.Console.Owner)) if (!_blocker.CanInteract(uid, comp.Console.Owner))
{ {
toRemove.Add(comp); toRemove.Add(comp);
} }
@@ -324,7 +344,7 @@ namespace Content.Server.Shuttles.Systems
foreach (var comp in toRemove) foreach (var comp in toRemove)
{ {
RemovePilot(comp); RemovePilot(comp.Owner, comp);
} }
} }
@@ -341,15 +361,18 @@ namespace Content.Server.Shuttles.Systems
} }
if (args.NewPosition.TryDistance(EntityManager, component.Position.Value, out var distance) && if (args.NewPosition.TryDistance(EntityManager, component.Position.Value, out var distance) &&
distance < PilotComponent.BreakDistance) return; distance < PilotComponent.BreakDistance)
{
return;
}
RemovePilot(component); RemovePilot(uid, component);
} }
protected override void HandlePilotShutdown(EntityUid uid, PilotComponent component, ComponentShutdown args) protected override void HandlePilotShutdown(EntityUid uid, PilotComponent component, ComponentShutdown args)
{ {
base.HandlePilotShutdown(uid, component, args); base.HandlePilotShutdown(uid, component, args);
RemovePilot(component); RemovePilot(uid, component);
} }
private void OnConsoleShutdown(EntityUid uid, ShuttleConsoleComponent component, ComponentShutdown args) private void OnConsoleShutdown(EntityUid uid, ShuttleConsoleComponent component, ComponentShutdown args)
@@ -380,42 +403,43 @@ namespace Content.Server.Shuttles.Systems
Dirty(pilotComponent); Dirty(pilotComponent);
} }
public void RemovePilot(PilotComponent pilotComponent) public void RemovePilot(EntityUid pilotUid, PilotComponent pilotComponent)
{ {
var console = pilotComponent.Console; var console = pilotComponent.Console;
if (console is not ShuttleConsoleComponent helmsman) return; if (console is not ShuttleConsoleComponent helmsman)
return;
pilotComponent.Console = null; pilotComponent.Console = null;
pilotComponent.Position = null; pilotComponent.Position = null;
if (TryComp<SharedEyeComponent>(pilotComponent.Owner, out var eye)) if (TryComp<SharedEyeComponent>(pilotUid, out var eye))
{ {
eye.Zoom = new(1.0f, 1.0f); eye.Zoom = new(1.0f, 1.0f);
} }
if (!helmsman.SubscribedPilots.Remove(pilotComponent)) return; if (!helmsman.SubscribedPilots.Remove(pilotComponent)) return;
_alertsSystem.ClearAlert(pilotComponent.Owner, AlertType.PilotingShuttle); _alertsSystem.ClearAlert(pilotUid, AlertType.PilotingShuttle);
pilotComponent.Owner.PopupMessage(Loc.GetString("shuttle-pilot-end")); pilotComponent.Owner.PopupMessage(Loc.GetString("shuttle-pilot-end"));
if (pilotComponent.LifeStage < ComponentLifeStage.Stopping) if (pilotComponent.LifeStage < ComponentLifeStage.Stopping)
EntityManager.RemoveComponent<PilotComponent>(pilotComponent.Owner); EntityManager.RemoveComponent<PilotComponent>(pilotUid);
} }
public void RemovePilot(EntityUid entity) public void RemovePilot(EntityUid entity)
{ {
if (!EntityManager.TryGetComponent(entity, out PilotComponent? pilotComponent)) return; if (!EntityManager.TryGetComponent(entity, out PilotComponent? pilotComponent)) return;
RemovePilot(pilotComponent); RemovePilot(entity, pilotComponent);
} }
public void ClearPilots(ShuttleConsoleComponent component) public void ClearPilots(ShuttleConsoleComponent component)
{ {
while (component.SubscribedPilots.TryGetValue(0, out var pilot)) while (component.SubscribedPilots.TryGetValue(0, out var pilot))
{ {
RemovePilot(pilot); RemovePilot(pilot.Owner, pilot);
} }
} }
} }

View File

@@ -152,7 +152,7 @@ public sealed partial class ShuttleSystem
else else
{ {
FTLTravel(shuttle, FTLTravel(shuttle,
CentCom.Value, _consoleAccumulator, TransitTime, dock: true); CentCom.Value, _consoleAccumulator, TransitTime, true);
} }
} }
} }

View File

@@ -12,6 +12,7 @@ using Content.Shared.CCVar;
using Content.Shared.Database; using Content.Shared.Database;
using Content.Shared.Shuttles.Events; using Content.Shared.Shuttles.Events;
using Content.Shared.Tiles; using Content.Shared.Tiles;
using Content.Shared.Tag;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;
using Robust.Server.Maps; using Robust.Server.Maps;
using Robust.Server.Player; using Robust.Server.Player;
@@ -109,126 +110,6 @@ public sealed partial class ShuttleSystem
}); });
} }
/// <summary>
/// Checks whether the emergency shuttle can warp to the specified position.
/// </summary>
private bool ValidSpawn(MapGridComponent grid, Box2 area)
{
return !grid.GetLocalTilesIntersecting(area).Any();
}
private DockingConfig? GetDockingConfig(ShuttleComponent component, EntityUid targetGrid)
{
var gridDocks = GetDocks(targetGrid);
if (gridDocks.Count <= 0) return null;
var xformQuery = GetEntityQuery<TransformComponent>();
var targetGridGrid = Comp<MapGridComponent>(targetGrid);
var targetGridXform = xformQuery.GetComponent(targetGrid);
var targetGridAngle = targetGridXform.WorldRotation.Reduced();
var shuttleDocks = GetDocks(component.Owner);
var shuttleAABB = Comp<MapGridComponent>(component.Owner).LocalAABB;
var validDockConfigs = new List<DockingConfig>();
if (shuttleDocks.Count > 0)
{
// We'll try all combinations of shuttle docks and see which one is most suitable
foreach (var shuttleDock in shuttleDocks)
{
var shuttleDockXform = xformQuery.GetComponent(shuttleDock.Owner);
foreach (var gridDock in gridDocks)
{
var gridXform = xformQuery.GetComponent(gridDock.Owner);
if (!CanDock(
shuttleDock, shuttleDockXform,
gridDock, gridXform,
targetGridAngle,
shuttleAABB,
targetGridGrid,
out var dockedAABB,
out var matty,
out var targetAngle)) continue;
// Can't just use the AABB as we want to get bounds as tight as possible.
var spawnPosition = new EntityCoordinates(targetGrid, matty.Transform(Vector2.Zero));
spawnPosition = new EntityCoordinates(targetGridXform.MapUid!.Value, spawnPosition.ToMapPos(EntityManager));
var dockedBounds = new Box2Rotated(shuttleAABB.Translated(spawnPosition.Position), targetGridAngle, spawnPosition.Position);
// Check if there's no intersecting grids (AKA oh god it's docking at cargo).
if (_mapManager.FindGridsIntersecting(targetGridXform.MapID,
dockedBounds).Any(o => o.Owner != targetGrid))
{
continue;
}
// Alright well the spawn is valid now to check how many we can connect
// Get the matrix for each shuttle dock and test it against the grid docks to see
// if the connected position / direction matches.
var dockedPorts = new List<(DockingComponent DockA, DockingComponent DockB)>()
{
(shuttleDock, gridDock),
};
// TODO: Check shuttle orientation as the tiebreaker.
foreach (var other in shuttleDocks)
{
if (other == shuttleDock) continue;
foreach (var otherGrid in gridDocks)
{
if (otherGrid == gridDock) continue;
if (!CanDock(
other,
xformQuery.GetComponent(other.Owner),
otherGrid,
xformQuery.GetComponent(otherGrid.Owner),
targetGridAngle,
shuttleAABB, targetGridGrid,
out var otherDockedAABB,
out _,
out var otherTargetAngle) ||
!otherDockedAABB.Equals(dockedAABB) ||
!targetAngle.Equals(otherTargetAngle)) continue;
dockedPorts.Add((other, otherGrid));
}
}
validDockConfigs.Add(new DockingConfig()
{
Docks = dockedPorts,
Area = dockedAABB.Value,
Coordinates = spawnPosition,
Angle = targetAngle,
});
}
}
}
if (validDockConfigs.Count <= 0) return null;
// Prioritise by priority docks, then by maximum connected ports, then by most similar angle.
validDockConfigs = validDockConfigs
.OrderByDescending(x => x.Docks.Any(docks => HasComp<EmergencyDockComponent>(docks.DockB.Owner)))
.ThenByDescending(x => x.Docks.Count)
.ThenBy(x => Math.Abs(Angle.ShortestDistance(x.Angle.Reduced(), targetGridAngle).Theta)).ToList();
var location = validDockConfigs.First();
location.TargetGrid = targetGrid;
// TODO: Ideally do a hyperspace warpin, just have it run on like a 10 second timer.
return location;
}
/// <summary> /// <summary>
/// Calls the emergency shuttle for the station. /// Calls the emergency shuttle for the station.
/// </summary> /// </summary>
@@ -305,6 +186,7 @@ public sealed partial class ShuttleSystem
TransformComponent gridDockXform, TransformComponent gridDockXform,
Angle targetGridRotation, Angle targetGridRotation,
Box2 shuttleAABB, Box2 shuttleAABB,
EntityUid gridUid,
MapGridComponent grid, MapGridComponent grid,
[NotNullWhen(true)] out Box2? shuttleDockedAABB, [NotNullWhen(true)] out Box2? shuttleDockedAABB,
out Matrix3 matty, out Matrix3 matty,
@@ -337,7 +219,8 @@ public sealed partial class ShuttleSystem
// Rounding moment // Rounding moment
shuttleDockedAABB = shuttleDockedAABB.Value.Enlarged(-0.01f); shuttleDockedAABB = shuttleDockedAABB.Value.Enlarged(-0.01f);
if (!ValidSpawn(grid, shuttleDockedAABB.Value)) return false; if (!ValidSpawn(gridUid, grid, shuttleDockedAABB.Value))
return false;
gridRotation = targetGridRotation + gridDockAngle - shuttleDockAngle; gridRotation = targetGridRotation + gridDockAngle - shuttleDockAngle;
return true; return true;

View File

@@ -1,4 +1,3 @@
using Content.Server.Doors.Components;
using Content.Server.Doors.Systems; using Content.Server.Doors.Systems;
using Content.Server.Shuttles.Components; using Content.Server.Shuttles.Components;
using Content.Server.Station.Systems; using Content.Server.Station.Systems;
@@ -12,10 +11,12 @@ using Robust.Shared.Map;
using Robust.Shared.Player; using Robust.Shared.Player;
using Robust.Shared.Utility; using Robust.Shared.Utility;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Content.Server.Shuttles.Events; using Content.Server.Shuttles.Events;
using Content.Shared.Buckle.Components; using Content.Shared.Buckle.Components;
using Content.Shared.Doors.Components; using Content.Shared.Doors.Components;
using Content.Shared.Shuttles.Components; using Content.Shared.Shuttles.Components;
using JetBrains.Annotations;
using Robust.Shared.Map.Components; using Robust.Shared.Map.Components;
using Robust.Shared.Physics; using Robust.Shared.Physics;
using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Components;
@@ -46,7 +47,7 @@ public sealed partial class ShuttleSystem
/// <summary> /// <summary>
/// Minimum mass a grid needs to be to block a shuttle recall. /// Minimum mass a grid needs to be to block a shuttle recall.
/// </summary> /// </summary>
private const float ShuttleFTLMassThreshold = 300f; public const float ShuttleFTLMassThreshold = 300f;
// I'm too lazy to make CVars. // I'm too lazy to make CVars.
@@ -69,6 +70,11 @@ public sealed partial class ShuttleSystem
/// </summary> /// </summary>
private const int FTLProximityIterations = 3; private const int FTLProximityIterations = 3;
/// <summary>
/// Minimum mass for an FTL destination
/// </summary>
public const float FTLDestinationMass = 500f;
private void InitializeFTL() private void InitializeFTL()
{ {
SubscribeLocalEvent<StationGridAddedEvent>(OnStationGridAdd); SubscribeLocalEvent<StationGridAddedEvent>(OnStationGridAdd);
@@ -76,7 +82,9 @@ public sealed partial class ShuttleSystem
private void OnStationGridAdd(StationGridAddedEvent ev) private void OnStationGridAdd(StationGridAddedEvent ev)
{ {
if (TryComp<PhysicsComponent>(ev.GridId, out var body) && body.Mass > 500f) if (HasComp<MapComponent>(ev.GridId) ||
TryComp<PhysicsComponent>(ev.GridId, out var body) &&
body.Mass > FTLDestinationMass)
{ {
AddFTLDestination(ev.GridId, true); AddFTLDestination(ev.GridId, true);
} }
@@ -133,9 +141,11 @@ public sealed partial class ShuttleSystem
return destination; return destination;
} }
[PublicAPI]
public void RemoveFTLDestination(EntityUid uid) public void RemoveFTLDestination(EntityUid uid)
{ {
if (!RemComp<FTLDestinationComponent>(uid)) return; if (!RemComp<FTLDestinationComponent>(uid))
return;
_console.RefreshShuttleConsoles(); _console.RefreshShuttleConsoles();
} }
@@ -145,7 +155,8 @@ public sealed partial class ShuttleSystem
public void FTLTravel(ShuttleComponent component, public void FTLTravel(ShuttleComponent component,
EntityCoordinates coordinates, EntityCoordinates coordinates,
float startupTime = DefaultStartupTime, float startupTime = DefaultStartupTime,
float hyperspaceTime = DefaultTravelTime) float hyperspaceTime = DefaultTravelTime,
string? priorityTag = null)
{ {
if (!TrySetupFTL(component, out var hyperspace)) if (!TrySetupFTL(component, out var hyperspace))
return; return;
@@ -155,6 +166,7 @@ public sealed partial class ShuttleSystem
hyperspace.Accumulator = hyperspace.StartupTime; hyperspace.Accumulator = hyperspace.StartupTime;
hyperspace.TargetCoordinates = coordinates; hyperspace.TargetCoordinates = coordinates;
hyperspace.Dock = false; hyperspace.Dock = false;
hyperspace.PriorityTag = priorityTag;
_console.RefreshShuttleConsoles(); _console.RefreshShuttleConsoles();
} }
@@ -165,7 +177,8 @@ public sealed partial class ShuttleSystem
EntityUid target, EntityUid target,
float startupTime = DefaultStartupTime, float startupTime = DefaultStartupTime,
float hyperspaceTime = DefaultTravelTime, float hyperspaceTime = DefaultTravelTime,
bool dock = false) bool dock = false,
string? priorityTag = null)
{ {
if (!TrySetupFTL(component, out var hyperspace)) if (!TrySetupFTL(component, out var hyperspace))
return; return;
@@ -175,6 +188,7 @@ public sealed partial class ShuttleSystem
hyperspace.Accumulator = hyperspace.StartupTime; hyperspace.Accumulator = hyperspace.StartupTime;
hyperspace.TargetUid = target; hyperspace.TargetUid = target;
hyperspace.Dock = dock; hyperspace.Dock = dock;
hyperspace.PriorityTag = priorityTag;
_console.RefreshShuttleConsoles(); _console.RefreshShuttleConsoles();
} }
@@ -206,18 +220,23 @@ public sealed partial class ShuttleSystem
SoundSystem.Play(_startupSound.GetSound(), Filter.Empty().AddInRange(Transform(uid).MapPosition, GetSoundRange(component.Owner)), _startupSound.Params); SoundSystem.Play(_startupSound.GetSound(), Filter.Empty().AddInRange(Transform(uid).MapPosition, GetSoundRange(component.Owner)), _startupSound.Params);
// Make sure the map is setup before we leave to avoid pop-in (e.g. parallax). // Make sure the map is setup before we leave to avoid pop-in (e.g. parallax).
SetupHyperspace(); SetupHyperspace();
var ev = new FTLStartedEvent();
RaiseLocalEvent(uid, ref ev);
return true; return true;
} }
private void UpdateHyperspace(float frameTime) private void UpdateHyperspace(float frameTime)
{ {
foreach (var comp in EntityQuery<FTLComponent>()) var query = EntityQueryEnumerator<FTLComponent>();
while (query.MoveNext(out var uid, out var comp))
{ {
comp.Accumulator -= frameTime; comp.Accumulator -= frameTime;
if (comp.Accumulator > 0f) continue; if (comp.Accumulator > 0f)
continue;
var uid = comp.Owner;
var xform = Transform(uid); var xform = Transform(uid);
PhysicsComponent? body; PhysicsComponent? body;
ShuttleComponent? shuttle; ShuttleComponent? shuttle;
@@ -280,13 +299,21 @@ public sealed partial class ShuttleSystem
SetDockBolts(uid, false); SetDockBolts(uid, false);
SetDocks(uid, true); SetDocks(uid, true);
if (TryComp(uid, out body))
{
_physics.SetLinearVelocity(uid, Vector2.Zero, body: body);
_physics.SetAngularVelocity(uid, 0f, body: body);
_physics.SetLinearDamping(body, ShuttleLinearDamping);
_physics.SetAngularDamping(body, ShuttleAngularDamping);
}
TryComp(uid, out shuttle); TryComp(uid, out shuttle);
MapId mapId; MapId mapId;
if (comp.TargetUid != null && shuttle != null) if (comp.TargetUid != null && shuttle != null)
{ {
if (comp.Dock) if (comp.Dock)
TryFTLDock(shuttle, comp.TargetUid.Value); TryFTLDock(shuttle, comp.TargetUid.Value, comp.PriorityTag);
else else
TryFTLProximity(shuttle, comp.TargetUid.Value); TryFTLProximity(shuttle, comp.TargetUid.Value);
@@ -439,7 +466,8 @@ public sealed partial class ShuttleSystem
/// <summary> /// <summary>
/// Tries to dock with the target grid, otherwise falls back to proximity. /// Tries to dock with the target grid, otherwise falls back to proximity.
/// </summary> /// </summary>
public bool TryFTLDock(ShuttleComponent component, EntityUid targetUid) /// <param name="priorityTag">Priority docking tag to prefer, e.g. for emergency shuttle</param>
public bool TryFTLDock(ShuttleComponent component, EntityUid targetUid, string? priorityTag = null)
{ {
if (!TryComp<TransformComponent>(component.Owner, out var xform) || if (!TryComp<TransformComponent>(component.Owner, out var xform) ||
!TryComp<TransformComponent>(targetUid, out var targetXform) || !TryComp<TransformComponent>(targetUid, out var targetXform) ||
@@ -449,7 +477,7 @@ public sealed partial class ShuttleSystem
return false; return false;
} }
var config = GetDockingConfig(component, targetUid); var config = GetDockingConfig(component, targetUid, priorityTag);
if (config != null) if (config != null)
{ {
@@ -460,7 +488,7 @@ public sealed partial class ShuttleSystem
// Connect everything // Connect everything
foreach (var (dockA, dockB) in config.Docks) foreach (var (dockA, dockB) in config.Docks)
{ {
_dockSystem.Dock(dockA, dockB); _dockSystem.Dock(dockA.Owner, dockA, dockB.Owner, dockB);
} }
return true; return true;
@@ -476,7 +504,6 @@ public sealed partial class ShuttleSystem
public bool TryFTLProximity(ShuttleComponent component, EntityUid targetUid, TransformComponent? xform = null, TransformComponent? targetXform = null) public bool TryFTLProximity(ShuttleComponent component, EntityUid targetUid, TransformComponent? xform = null, TransformComponent? targetXform = null)
{ {
if (!Resolve(targetUid, ref targetXform) || if (!Resolve(targetUid, ref targetXform) ||
targetXform.GridUid == null ||
targetXform.MapUid == null || targetXform.MapUid == null ||
!targetXform.MapUid.Value.IsValid() || !targetXform.MapUid.Value.IsValid() ||
!Resolve(component.Owner, ref xform)) !Resolve(component.Owner, ref xform))
@@ -502,9 +529,9 @@ public sealed partial class ShuttleSystem
var targetAABB = _transform.GetWorldMatrix(targetXform, xformQuery) var targetAABB = _transform.GetWorldMatrix(targetXform, xformQuery)
.TransformBox(targetLocalAABB).Enlarged(shuttleAABB.Size.Length); .TransformBox(targetLocalAABB).Enlarged(shuttleAABB.Size.Length);
var nearbyGrids = new HashSet<EntityUid>(1) { targetXform.GridUid.Value }; var nearbyGrids = new HashSet<EntityUid>();
var iteration = 0; var iteration = 0;
var lastCount = 1; var lastCount = nearbyGrids.Count;
var mapId = targetXform.MapID; var mapId = targetXform.MapID;
while (iteration < FTLProximityIterations) while (iteration < FTLProximityIterations)
@@ -552,7 +579,7 @@ public sealed partial class ShuttleSystem
} }
// TODO: This is pretty crude for multiple landings. // TODO: This is pretty crude for multiple landings.
if (nearbyGrids.Count > 1 || !HasComp<MapComponent>(targetXform.GridUid.Value)) if (nearbyGrids.Count > 1 || !HasComp<MapComponent>(targetXform.GridUid))
{ {
var minRadius = (MathF.Max(targetAABB.Width, targetAABB.Height) + MathF.Max(shuttleAABB.Width, shuttleAABB.Height)) / 2f; var minRadius = (MathF.Max(targetAABB.Width, targetAABB.Height) + MathF.Max(shuttleAABB.Width, shuttleAABB.Height)) / 2f;
spawnPos = targetAABB.Center + _random.NextVector2(minRadius, minRadius + 64f); spawnPos = targetAABB.Center + _random.NextVector2(minRadius, minRadius + 64f);
@@ -570,7 +597,7 @@ public sealed partial class ShuttleSystem
xform.Coordinates = new EntityCoordinates(targetXform.MapUid.Value, spawnPos); xform.Coordinates = new EntityCoordinates(targetXform.MapUid.Value, spawnPos);
if (!HasComp<MapComponent>(targetXform.GridUid.Value)) if (!HasComp<MapComponent>(targetXform.GridUid))
{ {
_transform.SetLocalRotation(xform, _random.NextAngle()); _transform.SetLocalRotation(xform, _random.NextAngle());
} }
@@ -581,4 +608,138 @@ public sealed partial class ShuttleSystem
return true; return true;
} }
/// <summary>
/// Checks whether the emergency shuttle can warp to the specified position.
/// </summary>
private bool ValidSpawn(EntityUid gridUid, MapGridComponent grid, Box2 area)
{
// If the target is a map then any tile is valid.
// TODO: We already need the entities-under check
if (HasComp<MapComponent>(gridUid))
return true;
return !grid.GetLocalTilesIntersecting(area).Any();
}
/// <summary>
/// Tries to get a valid docking configuration for the shuttle to the target grid.
/// </summary>
/// <param name="priorityTag">Priority docking tag to prefer, e.g. for emergency shuttle</param>
private DockingConfig? GetDockingConfig(ShuttleComponent component, EntityUid targetGrid, string? priorityTag = null)
{
var gridDocks = GetDocks(targetGrid);
if (gridDocks.Count <= 0)
return null;
var xformQuery = GetEntityQuery<TransformComponent>();
var targetGridGrid = Comp<MapGridComponent>(targetGrid);
var targetGridXform = xformQuery.GetComponent(targetGrid);
var targetGridAngle = targetGridXform.WorldRotation.Reduced();
var shuttleDocks = GetDocks(component.Owner);
var shuttleAABB = Comp<MapGridComponent>(component.Owner).LocalAABB;
var validDockConfigs = new List<DockingConfig>();
if (shuttleDocks.Count > 0)
{
// We'll try all combinations of shuttle docks and see which one is most suitable
foreach (var shuttleDock in shuttleDocks)
{
var shuttleDockXform = xformQuery.GetComponent(shuttleDock.Owner);
foreach (var gridDock in gridDocks)
{
var gridXform = xformQuery.GetComponent(gridDock.Owner);
if (!CanDock(
shuttleDock, shuttleDockXform,
gridDock, gridXform,
targetGridAngle,
shuttleAABB,
targetGrid,
targetGridGrid,
out var dockedAABB,
out var matty,
out var targetAngle)) continue;
// Can't just use the AABB as we want to get bounds as tight as possible.
var spawnPosition = new EntityCoordinates(targetGrid, matty.Transform(Vector2.Zero));
spawnPosition = new EntityCoordinates(targetGridXform.MapUid!.Value, spawnPosition.ToMapPos(EntityManager));
var dockedBounds = new Box2Rotated(shuttleAABB.Translated(spawnPosition.Position), targetGridAngle, spawnPosition.Position);
// Check if there's no intersecting grids (AKA oh god it's docking at cargo).
if (_mapManager.FindGridsIntersecting(targetGridXform.MapID,
dockedBounds).Any(o => o.Owner != targetGrid))
{
continue;
}
// Alright well the spawn is valid now to check how many we can connect
// Get the matrix for each shuttle dock and test it against the grid docks to see
// if the connected position / direction matches.
var dockedPorts = new List<(DockingComponent DockA, DockingComponent DockB)>()
{
(shuttleDock, gridDock),
};
foreach (var other in shuttleDocks)
{
if (other == shuttleDock) continue;
foreach (var otherGrid in gridDocks)
{
if (otherGrid == gridDock) continue;
if (!CanDock(
other,
xformQuery.GetComponent(other.Owner),
otherGrid,
xformQuery.GetComponent(otherGrid.Owner),
targetGridAngle,
shuttleAABB,
targetGrid,
targetGridGrid,
out var otherDockedAABB,
out _,
out var otherTargetAngle) ||
!otherDockedAABB.Equals(dockedAABB) ||
!targetAngle.Equals(otherTargetAngle)) continue;
dockedPorts.Add((other, otherGrid));
}
}
validDockConfigs.Add(new DockingConfig()
{
Docks = dockedPorts,
Area = dockedAABB.Value,
Coordinates = spawnPosition,
Angle = targetAngle,
});
}
}
}
if (validDockConfigs.Count <= 0)
return null;
// Prioritise by priority docks, then by maximum connected ports, then by most similar angle.
validDockConfigs = validDockConfigs
.OrderByDescending(x => x.Docks.Any(docks =>
TryComp<PriorityDockComponent>(docks.DockB.Owner, out var priority) &&
priority.Tag?.Equals(priorityTag) == true))
.ThenByDescending(x => x.Docks.Count)
.ThenBy(x => Math.Abs(Angle.ShortestDistance(x.Angle.Reduced(), targetGridAngle).Theta)).ToList();
var location = validDockConfigs.First();
location.TargetGrid = targetGrid;
// TODO: Ideally do a hyperspace warpin, just have it run on like a 10 second timer.
return location;
}
} }

View File

@@ -8,18 +8,6 @@ public sealed class CargoShuttleConsoleBoundUserInterfaceState : BoundUserInterf
public string AccountName; public string AccountName;
public string ShuttleName; public string ShuttleName;
// Unfortunately shuttles have essentially 3 states so can't just use a nullable var for it:
// 1. stowed
// 2. called but not recallable
// 3. called and recallable
// The reason we have 2 is so people don't spam the recall button in the UI.
public bool CanRecall;
/// <summary>
/// When the shuttle is expected to be usable.
/// </summary>
public TimeSpan? ShuttleETA;
/// <summary> /// <summary>
/// List of orders expected on the delivery. /// List of orders expected on the delivery.
/// </summary> /// </summary>
@@ -28,14 +16,10 @@ public sealed class CargoShuttleConsoleBoundUserInterfaceState : BoundUserInterf
public CargoShuttleConsoleBoundUserInterfaceState( public CargoShuttleConsoleBoundUserInterfaceState(
string accountName, string accountName,
string shuttleName, string shuttleName,
bool canRecall,
TimeSpan? shuttleETA,
List<CargoOrderData> orders) List<CargoOrderData> orders)
{ {
AccountName = accountName; AccountName = accountName;
ShuttleName = shuttleName; ShuttleName = shuttleName;
CanRecall = canRecall;
ShuttleETA = shuttleETA;
Orders = orders; Orders = orders;
} }
} }

View File

@@ -10,21 +10,6 @@ namespace Content.Shared.Cargo.Components;
[RegisterComponent, Access(typeof(SharedCargoSystem))] [RegisterComponent, Access(typeof(SharedCargoSystem))]
public sealed class CargoShuttleComponent : Component public sealed class CargoShuttleComponent : Component
{ {
[ViewVariables(VVAccess.ReadWrite), DataField("nextCall")]
public TimeSpan? NextCall;
[ViewVariables(VVAccess.ReadWrite), DataField("cooldown")]
public float Cooldown = 30f;
[ViewVariables]
public bool CanRecall;
/// <summary>
/// The shuttle's assigned coordinates on the cargo map.
/// </summary>
[ViewVariables]
public EntityCoordinates Coordinates;
/// <summary> /// <summary>
/// The assigned station for this cargo shuttle. /// The assigned station for this cargo shuttle.
/// </summary> /// </summary>

View File

@@ -1,12 +0,0 @@
using Robust.Shared.Serialization;
namespace Content.Shared.Cargo.Events;
/// <summary>
/// Raised on a cargo console requesting the cargo shuttle.
/// </summary>
[Serializable, NetSerializable]
public sealed class CargoCallShuttleMessage : BoundUserInterfaceMessage
{
}

View File

@@ -1,12 +0,0 @@
using Robust.Shared.Serialization;
namespace Content.Shared.Cargo.Events;
/// <summary>
/// Raised on a client request cargo shuttle recall
/// </summary>
[Serializable, NetSerializable]
public sealed class CargoRecallShuttleMessage : BoundUserInterfaceMessage
{
}

View File

@@ -6,6 +6,9 @@ namespace Content.Shared.Shuttles.Systems;
public abstract class SharedRadarConsoleSystem : EntitySystem public abstract class SharedRadarConsoleSystem : EntitySystem
{ {
public const float DefaultMinRange = 64f;
public const float DefaultMaxRange = 256f;
public override void Initialize() public override void Initialize()
{ {
base.Initialize(); base.Initialize();

View File

@@ -70,15 +70,15 @@
components: components:
- type: AccessReader - type: AccessReader
access: [["External"]] access: [["External"]]
- type: entity - type: entity
parent: AirlockExternal parent: AirlockExternal
id: AirlockExternalCargoLocked id: AirlockExternalCargoLocked
suffix: External, Cargo, Locked suffix: External, Cargo, Locked
components: components:
- type: AccessReader - type: AccessReader
access: [["Cargo"]] access: [["Cargo"]]
- type: entity - type: entity
parent: AirlockExternal parent: AirlockExternal
id: AirlockExternalEngineeringLocked id: AirlockExternalEngineeringLocked
@@ -94,7 +94,7 @@
components: components:
- type: AccessReader - type: AccessReader
access: [["Atmospherics"]] access: [["Atmospherics"]]
- type: entity - type: entity
parent: AirlockFreezer parent: AirlockFreezer
id: AirlockFreezerLocked id: AirlockFreezerLocked
@@ -327,14 +327,14 @@
components: components:
- type: AccessReader - type: AccessReader
access: [["Cargo"]] access: [["Cargo"]]
- type: entity - type: entity
parent: AirlockExternalGlass parent: AirlockExternalGlass
id: AirlockExternalGlassEngineeringLocked id: AirlockExternalGlassEngineeringLocked
suffix: External, Glass, Engineering, Locked suffix: External, Glass, Engineering, Locked
components: components:
- type: AccessReader - type: AccessReader
access: [["Engineering"]] access: [["Engineering"]]
- type: entity - type: entity
parent: AirlockExternalGlass parent: AirlockExternalGlass
@@ -342,8 +342,8 @@
suffix: External, Glass, Atmospherics, Locked suffix: External, Glass, Atmospherics, Locked
components: components:
- type: AccessReader - type: AccessReader
access: [["Atmospherics"]] access: [["Atmospherics"]]
- type: entity - type: entity
parent: AirlockGlass parent: AirlockGlass
id: AirlockKitchenGlassLocked id: AirlockKitchenGlassLocked
@@ -727,7 +727,8 @@
id: AirlockExternalGlassShuttleEmergencyLocked id: AirlockExternalGlassShuttleEmergencyLocked
suffix: External, Emergency, Glass, Docking, Locked suffix: External, Emergency, Glass, Docking, Locked
components: components:
- type: EmergencyDock - type: PriorityDock
tag: DockEmergency
- type: AccessReader - type: AccessReader
access: [["External"]] access: [["External"]]

View File

@@ -182,6 +182,12 @@
- type: Tag - type: Tag
id: DiscreteHealthAnalyzer #So construction recipes don't eat medical PDAs id: DiscreteHealthAnalyzer #So construction recipes don't eat medical PDAs
- type: Tag
id: DockCargo
- type: Tag
id: DockEmergency
- type: Tag - type: Tag
id: Document id: Document