Cargo shuttle changes (#14363)
This commit is contained in:
@@ -1,12 +1,12 @@
|
||||
using Content.Client.Cargo.UI;
|
||||
using Content.Shared.Cargo.BUI;
|
||||
using Content.Shared.Cargo.Events;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client.Cargo.BUI;
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed class CargoShuttleConsoleBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
private CargoShuttleMenu? _menu;
|
||||
@@ -21,9 +21,7 @@ public sealed class CargoShuttleConsoleBoundUserInterface : BoundUserInterface
|
||||
if (collection == null)
|
||||
return;
|
||||
|
||||
_menu = new CargoShuttleMenu(collection.Resolve<IGameTiming>(), collection.Resolve<IPrototypeManager>(), collection.Resolve<IEntitySystemManager>().GetEntitySystem<SpriteSystem>());
|
||||
_menu.ShuttleCallRequested += OnShuttleCall;
|
||||
_menu.ShuttleRecallRequested += OnShuttleRecall;
|
||||
_menu = new CargoShuttleMenu(collection.Resolve<IPrototypeManager>(), collection.Resolve<IEntitySystemManager>().GetEntitySystem<SpriteSystem>());
|
||||
_menu.OnClose += Close;
|
||||
|
||||
_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)
|
||||
{
|
||||
base.UpdateState(state);
|
||||
if (state is not CargoShuttleConsoleBoundUserInterfaceState cargoState) return;
|
||||
_menu?.SetAccountName(cargoState.AccountName);
|
||||
_menu?.SetShuttleName(cargoState.ShuttleName);
|
||||
_menu?.SetShuttleETA(cargoState.ShuttleETA);
|
||||
_menu?.SetOrders(cargoState.Orders);
|
||||
_menu?.SetCanRecall(cargoState.CanRecall);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,12 +22,6 @@
|
||||
<Label Name="ShuttleStatusLabel"
|
||||
Text="{Loc 'cargo-console-menu-shuttle-status-away-text'}" />
|
||||
</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'}" />
|
||||
<PanelContainer VerticalExpand="True"
|
||||
SizeFlagsStretchRatio="6">
|
||||
|
||||
@@ -3,8 +3,6 @@ using Content.Shared.Cargo;
|
||||
using Content.Shared.Cargo.Prototypes;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
@@ -14,23 +12,14 @@ namespace Content.Client.Cargo.UI
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class CargoShuttleMenu : FancyWindow
|
||||
{
|
||||
private readonly IGameTiming _timing;
|
||||
private readonly IPrototypeManager _protoManager;
|
||||
private readonly SpriteSystem _spriteSystem;
|
||||
|
||||
public Action? ShuttleCallRequested;
|
||||
public Action? ShuttleRecallRequested;
|
||||
|
||||
private TimeSpan? _shuttleEta;
|
||||
|
||||
public CargoShuttleMenu(IGameTiming timing, IPrototypeManager protoManager, SpriteSystem spriteSystem)
|
||||
public CargoShuttleMenu(IPrototypeManager protoManager, SpriteSystem spriteSystem)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
_timing = timing;
|
||||
_protoManager = protoManager;
|
||||
_spriteSystem = spriteSystem;
|
||||
ShuttleCallButton.OnPressed += OnCallPressed;
|
||||
ShuttleRecallButton.OnPressed += OnRecallPressed;
|
||||
Title = Loc.GetString("cargo-shuttle-console-menu-title");
|
||||
}
|
||||
|
||||
@@ -44,33 +33,6 @@ namespace Content.Client.Cargo.UI
|
||||
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)
|
||||
{
|
||||
Orders.DisposeAllChildren();
|
||||
@@ -102,27 +64,5 @@ namespace Content.Client.Cargo.UI
|
||||
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}";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Content.Shared.Shuttles.BUIStates;
|
||||
using Content.Shared.Shuttles.Components;
|
||||
using Content.Shared.Shuttles.Systems;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
@@ -33,14 +34,14 @@ public sealed class RadarControl : Control
|
||||
|
||||
private Angle? _rotation;
|
||||
|
||||
private float _radarMinRange = 64f;
|
||||
private float _radarMaxRange = 256f;
|
||||
public float RadarRange { get; private set; } = 256f;
|
||||
private float _radarMinRange = SharedRadarConsoleSystem.DefaultMinRange;
|
||||
private float _radarMaxRange = SharedRadarConsoleSystem.DefaultMaxRange;
|
||||
public float RadarRange { get; private set; } = SharedRadarConsoleSystem.DefaultMinRange;
|
||||
|
||||
/// <summary>
|
||||
/// We'll lerp between the radarrange and actual range
|
||||
/// </summary>
|
||||
private float _actualRadarRange = 256f;
|
||||
private float _actualRadarRange = SharedRadarConsoleSystem.DefaultMinRange;
|
||||
|
||||
/// <summary>
|
||||
/// Controls the maximum distance that IFF labels will display.
|
||||
@@ -87,14 +88,11 @@ public sealed class RadarControl : Control
|
||||
{
|
||||
_radarMaxRange = ls.MaxRange;
|
||||
|
||||
if (_radarMaxRange < RadarRange)
|
||||
{
|
||||
_actualRadarRange = _radarMaxRange;
|
||||
}
|
||||
|
||||
if (_radarMaxRange < _radarMinRange)
|
||||
_radarMinRange = _radarMaxRange;
|
||||
|
||||
_actualRadarRange = Math.Clamp(_actualRadarRange, _radarMinRange, _radarMaxRange);
|
||||
|
||||
_docks.Clear();
|
||||
|
||||
foreach (var state in ls.Docks)
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace Content.Server.Alert.Click
|
||||
if (entManager.TryGetComponent(player, out PilotComponent? pilotComponent) &&
|
||||
pilotComponent.Console != null)
|
||||
{
|
||||
entManager.System<ShuttleConsoleSystem>().RemovePilot(pilotComponent);
|
||||
entManager.System<ShuttleConsoleSystem>().RemovePilot(player, pilotComponent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -263,20 +263,25 @@ namespace Content.Server.Cargo.Systems
|
||||
private void UpdateOrders(StationCargoOrderDatabaseComponent component)
|
||||
{
|
||||
// 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);
|
||||
if (station != component.Owner) continue;
|
||||
var station = _station.GetOwningStation(uid);
|
||||
if (station != component.Owner)
|
||||
continue;
|
||||
|
||||
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);
|
||||
if (station != component.Owner) continue;
|
||||
var station = _station.GetOwningStation(uid);
|
||||
if (station != component.Owner)
|
||||
continue;
|
||||
|
||||
UpdateShuttleState(comp, station);
|
||||
UpdateShuttleState(uid, comp, station);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,20 +17,16 @@ using Content.Shared.Cargo.Prototypes;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Dataset;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Whitelist;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.Maps;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Content.Shared.Coordinates;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Robust.Shared.Map.Components;
|
||||
|
||||
namespace Content.Server.Cargo.Systems;
|
||||
|
||||
@@ -40,22 +36,19 @@ public sealed partial class CargoSystem
|
||||
* Handles cargo shuttle mechanics, including cargo shuttle consoles.
|
||||
*/
|
||||
|
||||
[Dependency] private readonly IComponentFactory _factory = default!;
|
||||
[Dependency] private readonly IConfigurationManager _configManager = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly EntityLookupSystem _lookup = default!;
|
||||
[Dependency] private readonly MapLoaderSystem _map = default!;
|
||||
[Dependency] private readonly MobStateSystem _mobState = default!;
|
||||
[Dependency] private readonly PricingSystem _pricing = default!;
|
||||
[Dependency] private readonly ShuttleConsoleSystem _console = default!;
|
||||
[Dependency] private readonly ShuttleSystem _shuttle = default!;
|
||||
[Dependency] private readonly StackSystem _stack = default!;
|
||||
public MapId? CargoMap { get; private set; }
|
||||
|
||||
private const float CallOffset = 50f;
|
||||
|
||||
private int _index;
|
||||
|
||||
/// <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.
|
||||
_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, CargoCallShuttleMessage>(OnCargoShuttleCall);
|
||||
SubscribeLocalEvent<CargoShuttleConsoleComponent, CargoRecallShuttleMessage>(RecallCargoShuttle);
|
||||
|
||||
SubscribeLocalEvent<CargoPalletConsoleComponent, CargoPalletSellMessage>(OnPalletSale);
|
||||
SubscribeLocalEvent<CargoPalletConsoleComponent, CargoPalletAppraiseMessage>(OnPalletAppraise);
|
||||
@@ -87,6 +81,16 @@ public sealed partial class CargoSystem
|
||||
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()
|
||||
{
|
||||
_configManager.UnsubValueChanged(CCVars.CargoShuttles, SetCargoShuttleEnabled);
|
||||
@@ -94,7 +98,9 @@ public sealed partial class CargoSystem
|
||||
|
||||
private void SetCargoShuttleEnabled(bool value)
|
||||
{
|
||||
if (_enabled == value) return;
|
||||
if (_enabled == value)
|
||||
return;
|
||||
|
||||
_enabled = value;
|
||||
|
||||
if (value)
|
||||
@@ -116,7 +122,7 @@ public sealed partial class CargoSystem
|
||||
|
||||
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)
|
||||
@@ -126,30 +132,50 @@ public sealed partial class CargoSystem
|
||||
|
||||
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) ||
|
||||
!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
|
||||
|
||||
#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);
|
||||
if (stationUid != component.Station) continue;
|
||||
UpdateShuttleState(console, stationUid);
|
||||
var stationUid = _station.GetOwningStation(uid);
|
||||
if (stationUid == null || stationUid != component.Station)
|
||||
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)
|
||||
{
|
||||
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<CargoShuttleComponent>(orderDatabase?.Shuttle, out var shuttle);
|
||||
@@ -209,12 +235,10 @@ public sealed partial class CargoSystem
|
||||
var orders = GetProjectedOrders(orderDatabase, shuttle);
|
||||
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(
|
||||
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,
|
||||
_shuttle.CanFTL(shuttle?.Owner, out _),
|
||||
shuttle?.NextCall,
|
||||
orders));
|
||||
}
|
||||
|
||||
@@ -222,32 +246,21 @@ public sealed partial class CargoSystem
|
||||
|
||||
#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;
|
||||
return comp.Owner;
|
||||
if (xform.ParentUid != uid)
|
||||
continue;
|
||||
|
||||
return cUid;
|
||||
}
|
||||
|
||||
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>
|
||||
/// Returns the orders that can fit on the cargo shuttle.
|
||||
/// </summary>
|
||||
@@ -329,7 +342,10 @@ public sealed partial class CargoSystem
|
||||
|
||||
if (CargoMap == null ||
|
||||
component.Shuttle != null ||
|
||||
component.CargoShuttleProto == null) return;
|
||||
component.CargoShuttleProto == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var prototype = _protoMan.Index<CargoShuttlePrototype>(component.CargoShuttleProto);
|
||||
var possibleNames = _protoMan.Index<DatasetPrototype>(prototype.NameDataset).Values;
|
||||
@@ -348,11 +364,9 @@ public sealed partial class CargoSystem
|
||||
xform.LocalPosition += 100 * _index;
|
||||
var comp = EnsureComp<CargoShuttleComponent>(shuttleUid);
|
||||
comp.Station = component.Owner;
|
||||
comp.Coordinates = xform.Coordinates;
|
||||
|
||||
component.Shuttle = shuttleUid;
|
||||
comp.NextCall = _timing.CurTime + TimeSpan.FromSeconds(comp.Cooldown);
|
||||
UpdateShuttleCargoConsoles(comp);
|
||||
UpdateCargoShuttleConsoles(comp);
|
||||
_index++;
|
||||
_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)
|
||||
{
|
||||
var xformQuery = GetEntityQuery<TransformComponent>();
|
||||
@@ -510,74 +450,37 @@ public sealed partial class CargoSystem
|
||||
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;
|
||||
|
||||
var stationUid = _station.GetOwningStation(uid);
|
||||
|
||||
if (!TryComp<StationCargoOrderDatabaseComponent>(stationUid, out var orderDatabase) ||
|
||||
!TryComp<StationBankAccountComponent>(stationUid, out var bank)) return;
|
||||
|
||||
if (!TryComp<CargoShuttleComponent>(orderDatabase.Shuttle, out var shuttle))
|
||||
// Called
|
||||
if (xform.MapID != CargoMap ||
|
||||
!TryComp<StationCargoOrderDatabaseComponent>(stationUid, out var orderDatabase))
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("cargo-no-shuttle"), args.Entity, args.Entity);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_shuttle.CanFTL(shuttle.Owner, out var reason))
|
||||
AddCargoContents(component, orderDatabase);
|
||||
UpdateOrders(orderDatabase);
|
||||
UpdateCargoShuttleConsoles(component);
|
||||
}
|
||||
|
||||
private void OnCargoFTLCompleted(EntityUid uid, CargoShuttleComponent component, ref FTLCompletedEvent args)
|
||||
{
|
||||
_popup.PopupEntity(reason, args.Entity, args.Entity);
|
||||
var xform = Transform(uid);
|
||||
// Recalled
|
||||
if (xform.MapID != CargoMap)
|
||||
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);
|
||||
_audio.PlayPvs(_audio.GetSound(component.DenySound), uid);
|
||||
return;
|
||||
SellPallets(uid, out var amount);
|
||||
bank.Balance += (int) amount;
|
||||
}
|
||||
|
||||
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
|
||||
@@ -600,13 +503,15 @@ public sealed partial class CargoSystem
|
||||
CargoMap = null;
|
||||
|
||||
// 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))
|
||||
{
|
||||
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.
|
||||
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))
|
||||
{
|
||||
AddShuttle(comp);
|
||||
}
|
||||
|
||||
_console.RefreshShuttleConsoles();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {}
|
||||
@@ -1,6 +1,8 @@
|
||||
using Content.Shared.Shuttles.Systems;
|
||||
using Content.Shared.Tag;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Server.Shuttles.Components;
|
||||
|
||||
@@ -37,6 +39,12 @@ public sealed class FTLComponent : Component
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("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")]
|
||||
public SoundSpecifier? TravelSound = new SoundPathSpecifier("/Audio/Effects/Shuttle/hyperspace_progress.ogg")
|
||||
{
|
||||
|
||||
18
Content.Server/Shuttles/Components/PriorityDockComponent.cs
Normal file
18
Content.Server/Shuttles/Components/PriorityDockComponent.cs
Normal 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;
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
using Content.Shared.Shuttles.Components;
|
||||
|
||||
namespace Content.Server.Shuttles.Components
|
||||
{
|
||||
[RegisterComponent]
|
||||
|
||||
@@ -9,6 +9,8 @@ namespace Content.Server.Shuttles;
|
||||
[AdminCommand(AdminFlags.Mapping)]
|
||||
public sealed class DockCommand : IConsoleCommand
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
|
||||
public string Command => "dock";
|
||||
public string Description => $"Attempts to dock 2 airlocks together. Doesn't check whether it is valid.";
|
||||
public string Help => $"{Command} <airlock entityuid1> <airlock entityuid2>";
|
||||
@@ -32,21 +34,19 @@ public sealed class DockCommand : IConsoleCommand
|
||||
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}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!entManager.TryGetComponent(airlock2, out DockingComponent? dock2))
|
||||
if (!_entManager.TryGetComponent(airlock2, out DockingComponent? dock2))
|
||||
{
|
||||
shell.WriteError($"No docking component found on {airlock2}");
|
||||
return;
|
||||
}
|
||||
|
||||
var dockSystem = EntitySystem.Get<DockingSystem>();
|
||||
dockSystem.Dock(dock1, dock2);
|
||||
var dockSystem = _entManager.System<DockingSystem>();
|
||||
dockSystem.Dock(airlock1, dock1, airlock2, dock2);
|
||||
}
|
||||
}
|
||||
|
||||
7
Content.Server/Shuttles/Events/FTLTagEvent.cs
Normal file
7
Content.Server/Shuttles/Events/FTLTagEvent.cs
Normal 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);
|
||||
@@ -16,23 +16,25 @@ public sealed partial class DockingSystem
|
||||
var dockingQuery = GetEntityQuery<DockingComponent>();
|
||||
var xformQuery = GetEntityQuery<TransformComponent>();
|
||||
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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
TryDock(dock, dockable);
|
||||
TryDock(dockUid, dock, dockable.Owner, dockable);
|
||||
}
|
||||
|
||||
// Work out recent docks that have gone past their designated threshold.
|
||||
|
||||
@@ -144,12 +144,13 @@ namespace Content.Server.Shuttles.Systems
|
||||
private void Cleanup(DockingComponent dockA)
|
||||
{
|
||||
_pathfinding.RemovePortal(dockA.PathfindHandle);
|
||||
_jointSystem.RemoveJoint(dockA.DockJoint!);
|
||||
|
||||
if (dockA.DockJoint != null)
|
||||
_jointSystem.RemoveJoint(dockA.DockJoint);
|
||||
|
||||
var dockBUid = dockA.DockedWith;
|
||||
|
||||
if (dockBUid == null ||
|
||||
dockA.DockJoint == null ||
|
||||
!TryComp(dockBUid, out DockingComponent? dockB))
|
||||
{
|
||||
DebugTools.Assert(false);
|
||||
@@ -187,9 +188,9 @@ namespace Content.Server.Shuttles.Systems
|
||||
GridBUid = gridBUid!.Value,
|
||||
};
|
||||
|
||||
EntityManager.EventBus.RaiseLocalEvent(dockA.Owner, msg, false);
|
||||
EntityManager.EventBus.RaiseLocalEvent(dockB.Owner, msg, false);
|
||||
EntityManager.EventBus.RaiseEvent(EventSource.Local, msg);
|
||||
RaiseLocalEvent(dockA.Owner, msg);
|
||||
RaiseLocalEvent(dockB.Owner, msg);
|
||||
RaiseLocalEvent(msg);
|
||||
}
|
||||
|
||||
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);
|
||||
DebugTools.Assert(otherDock.DockedWith != null);
|
||||
|
||||
Dock(component, otherDock);
|
||||
Dock(uid, component, component.DockedWith.Value, otherDock);
|
||||
DebugTools.Assert(component.Docked && otherDock.Docked);
|
||||
}
|
||||
}
|
||||
@@ -234,7 +235,7 @@ namespace Content.Server.Shuttles.Systems
|
||||
var other = Comp<DockingComponent>(component.DockedWith!.Value);
|
||||
|
||||
Undock(component);
|
||||
Dock(component, other);
|
||||
Dock(uid, component, component.DockedWith.Value, other);
|
||||
_console.RefreshShuttleConsoles();
|
||||
}
|
||||
|
||||
@@ -278,26 +279,30 @@ namespace Content.Server.Shuttles.Systems
|
||||
/// <summary>
|
||||
/// Docks 2 ports together and assumes it is valid.
|
||||
/// </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);
|
||||
}
|
||||
|
||||
_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
|
||||
|
||||
// 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 dockBXform = EntityManager.GetComponent<TransformComponent>(dockB.Owner);
|
||||
var dockAXform = EntityManager.GetComponent<TransformComponent>(dockAUid);
|
||||
var dockBXform = EntityManager.GetComponent<TransformComponent>(dockBUid);
|
||||
|
||||
DebugTools.Assert(dockAXform.GridUid != null);
|
||||
DebugTools.Assert(dockBXform.GridUid != null);
|
||||
var gridA = dockAXform.GridUid!.Value;
|
||||
var gridB = dockBXform.GridUid!.Value;
|
||||
|
||||
// May not be possible if map or the likes.
|
||||
if (TryComp<PhysicsComponent>(gridA, out var gridPhysicsA) &&
|
||||
TryComp<PhysicsComponent>(gridB, out var gridPhysicsB))
|
||||
{
|
||||
SharedJointSystem.LinearStiffness(
|
||||
2f,
|
||||
0.7f,
|
||||
@@ -318,7 +323,7 @@ namespace Content.Server.Shuttles.Systems
|
||||
}
|
||||
else
|
||||
{
|
||||
joint = _jointSystem.GetOrCreateWeldJoint(gridA, gridB, DockingJoint + dockA.Owner);
|
||||
joint = _jointSystem.GetOrCreateWeldJoint(gridA, gridB, DockingJoint + dockAUid);
|
||||
}
|
||||
|
||||
var gridAXform = EntityManager.GetComponent<TransformComponent>(gridA);
|
||||
@@ -334,35 +339,36 @@ namespace Content.Server.Shuttles.Systems
|
||||
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))
|
||||
dockA.DockedWith = dockBUid;
|
||||
dockB.DockedWith = dockAUid;
|
||||
|
||||
if (TryComp(dockAUid, out DoorComponent? doorA))
|
||||
{
|
||||
if (_doorSystem.TryOpen(doorA.Owner, doorA))
|
||||
{
|
||||
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))
|
||||
{
|
||||
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,
|
||||
};
|
||||
|
||||
EntityManager.EventBus.RaiseLocalEvent(dockA.Owner, msg, false);
|
||||
EntityManager.EventBus.RaiseLocalEvent(dockB.Owner, msg, false);
|
||||
EntityManager.EventBus.RaiseEvent(EventSource.Local, msg);
|
||||
RaiseLocalEvent(dockAUid, msg);
|
||||
RaiseLocalEvent(dockBUid, msg);
|
||||
RaiseLocalEvent(msg);
|
||||
}
|
||||
|
||||
private bool CanDock(DockingComponent dockA, DockingComponent dockB)
|
||||
@@ -433,11 +439,11 @@ namespace Content.Server.Shuttles.Systems
|
||||
/// <summary>
|
||||
/// Attempts to dock 2 ports together and will return early if it's not possible.
|
||||
/// </summary>
|
||||
private void TryDock(DockingComponent dockA, DockingComponent dockB)
|
||||
private void TryDock(EntityUid dockAUid, DockingComponent dockA, EntityUid dockBUid, DockingComponent dockB)
|
||||
{
|
||||
if (!CanDock(dockA, dockB)) return;
|
||||
|
||||
Dock(dockA, dockB);
|
||||
Dock(dockAUid, dockA, dockBUid, dockB);
|
||||
}
|
||||
|
||||
public void Undock(DockingComponent dock)
|
||||
|
||||
@@ -13,8 +13,8 @@ using Content.Shared.Shuttles.Systems;
|
||||
using Content.Shared.Tag;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
@@ -68,9 +68,10 @@ namespace Content.Server.Shuttles.Systems
|
||||
return;
|
||||
}
|
||||
|
||||
if (!dest.Enabled) return;
|
||||
if (!dest.Enabled)
|
||||
return;
|
||||
|
||||
EntityUid? entity = component.Owner;
|
||||
EntityUid? entity = uid;
|
||||
|
||||
var getShuttleEv = new ConsoleShuttleEvent
|
||||
{
|
||||
@@ -80,13 +81,14 @@ namespace Content.Server.Shuttles.Systems
|
||||
RaiseLocalEvent(entity.Value, ref getShuttleEv);
|
||||
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;
|
||||
}
|
||||
|
||||
if (!TryComp<TransformComponent>(entity, out var xform) ||
|
||||
!TryComp<ShuttleComponent>(xform.GridUid, out var shuttle))
|
||||
if (dest.Whitelist?.IsValid(entity.Value, EntityManager) == false &&
|
||||
dest.Whitelist?.IsValid(xform.GridUid.Value, EntityManager) == false)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -97,13 +99,17 @@ namespace Content.Server.Shuttles.Systems
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_shuttle.CanFTL(shuttle.Owner, out var reason))
|
||||
if (!_shuttle.CanFTL(xform.GridUid, out var reason))
|
||||
{
|
||||
_popup.PopupCursor(reason, args.Session);
|
||||
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)
|
||||
@@ -128,10 +134,11 @@ namespace Content.Server.Shuttles.Systems
|
||||
public void RefreshShuttleConsoles()
|
||||
{
|
||||
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)
|
||||
{
|
||||
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.
|
||||
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)
|
||||
{
|
||||
UpdateState(component);
|
||||
UpdateState(uid, component);
|
||||
}
|
||||
|
||||
private void OnConsolePowerChange(EntityUid uid, ShuttleConsoleComponent component, ref PowerChangedEvent args)
|
||||
{
|
||||
UpdateState(component);
|
||||
UpdateState(uid, component);
|
||||
}
|
||||
|
||||
private bool TryPilot(EntityUid user, EntityUid uid)
|
||||
@@ -184,7 +194,7 @@ namespace Content.Server.Shuttles.Systems
|
||||
|
||||
if (console != null)
|
||||
{
|
||||
RemovePilot(pilotComponent);
|
||||
RemovePilot(user, pilotComponent);
|
||||
|
||||
if (console == component)
|
||||
{
|
||||
@@ -208,16 +218,18 @@ namespace Content.Server.Shuttles.Systems
|
||||
{
|
||||
// TODO: NEED TO MAKE SURE THIS UPDATES ON ANCHORING CHANGES!
|
||||
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()
|
||||
{
|
||||
Coordinates = xform.Coordinates,
|
||||
Angle = xform.LocalRotation,
|
||||
Entity = comp.Owner,
|
||||
Entity = uid,
|
||||
Connected = comp.Docked,
|
||||
Color = comp.RadarColor,
|
||||
HighlightedColor = comp.HighlightedRadarColor,
|
||||
@@ -228,9 +240,9 @@ namespace Content.Server.Shuttles.Systems
|
||||
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
|
||||
{
|
||||
@@ -242,38 +254,44 @@ namespace Content.Server.Shuttles.Systems
|
||||
|
||||
TryComp<TransformComponent>(entity, out var consoleXform);
|
||||
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 ftlState = FTLState.Available;
|
||||
var ftlTime = TimeSpan.Zero;
|
||||
|
||||
if (TryComp<FTLComponent>(shuttle?.Owner, out var shuttleFtl))
|
||||
if (TryComp<FTLComponent>(shuttleGridUid, out var shuttleFtl))
|
||||
{
|
||||
ftlState = shuttleFtl.State;
|
||||
ftlTime = _timing.CurTime + TimeSpan.FromSeconds(shuttleFtl.Accumulator);
|
||||
}
|
||||
|
||||
// Mass too large
|
||||
if (entity != null && shuttle?.Owner != null && (!TryComp<PhysicsComponent>(shuttle?.Owner, out var shuttleBody) ||
|
||||
shuttleBody.Mass < 1000f))
|
||||
if (entity != null && shuttleGridUid != null &&
|
||||
(!TryComp<PhysicsComponent>(shuttleGridUid, out var shuttleBody) || shuttleBody.Mass < 1000f))
|
||||
{
|
||||
var metaQuery = GetEntityQuery<MetaDataComponent>();
|
||||
|
||||
// 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.
|
||||
// Include paused as we still want to show CentCom.
|
||||
foreach (var comp in EntityQuery<FTLDestinationComponent>(true))
|
||||
{
|
||||
// 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 destQuery = AllEntityQuery<FTLDestinationComponent>();
|
||||
|
||||
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;
|
||||
|
||||
if (string.IsNullOrEmpty(name))
|
||||
@@ -283,19 +301,19 @@ namespace Content.Server.Shuttles.Systems
|
||||
comp.Enabled &&
|
||||
(!TryComp<FTLComponent>(comp.Owner, out var ftl) || ftl.State == FTLState.Cooldown);
|
||||
|
||||
// Can't travel to same map.
|
||||
if (canTravel && consoleXform?.MapUid == Transform(comp.Owner).MapUid)
|
||||
// Can't travel to same map (yet)
|
||||
if (canTravel && consoleXform?.MapUid == Transform(destUid).MapUid)
|
||||
{
|
||||
canTravel = false;
|
||||
}
|
||||
|
||||
destinations.Add((comp.Owner, name, canTravel));
|
||||
destinations.Add((destUid, name, canTravel));
|
||||
}
|
||||
}
|
||||
|
||||
docks ??= GetAllDocks();
|
||||
|
||||
_ui.GetUiOrNull(component.Owner, ShuttleConsoleUiKey.Key)
|
||||
_ui.GetUiOrNull(consoleUid, ShuttleConsoleUiKey.Key)
|
||||
?.SetState(new ShuttleConsoleBoundInterfaceState(
|
||||
ftlState,
|
||||
ftlTime,
|
||||
@@ -311,12 +329,14 @@ namespace Content.Server.Shuttles.Systems
|
||||
base.Update(frameTime);
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -324,7 +344,7 @@ namespace Content.Server.Shuttles.Systems
|
||||
|
||||
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) &&
|
||||
distance < PilotComponent.BreakDistance) return;
|
||||
distance < PilotComponent.BreakDistance)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
RemovePilot(component);
|
||||
RemovePilot(uid, component);
|
||||
}
|
||||
|
||||
protected override void HandlePilotShutdown(EntityUid uid, PilotComponent component, ComponentShutdown args)
|
||||
{
|
||||
base.HandlePilotShutdown(uid, component, args);
|
||||
RemovePilot(component);
|
||||
RemovePilot(uid, component);
|
||||
}
|
||||
|
||||
private void OnConsoleShutdown(EntityUid uid, ShuttleConsoleComponent component, ComponentShutdown args)
|
||||
@@ -380,42 +403,43 @@ namespace Content.Server.Shuttles.Systems
|
||||
Dirty(pilotComponent);
|
||||
}
|
||||
|
||||
public void RemovePilot(PilotComponent pilotComponent)
|
||||
public void RemovePilot(EntityUid pilotUid, PilotComponent pilotComponent)
|
||||
{
|
||||
var console = pilotComponent.Console;
|
||||
|
||||
if (console is not ShuttleConsoleComponent helmsman) return;
|
||||
if (console is not ShuttleConsoleComponent helmsman)
|
||||
return;
|
||||
|
||||
pilotComponent.Console = 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);
|
||||
}
|
||||
|
||||
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"));
|
||||
|
||||
if (pilotComponent.LifeStage < ComponentLifeStage.Stopping)
|
||||
EntityManager.RemoveComponent<PilotComponent>(pilotComponent.Owner);
|
||||
EntityManager.RemoveComponent<PilotComponent>(pilotUid);
|
||||
}
|
||||
|
||||
public void RemovePilot(EntityUid entity)
|
||||
{
|
||||
if (!EntityManager.TryGetComponent(entity, out PilotComponent? pilotComponent)) return;
|
||||
|
||||
RemovePilot(pilotComponent);
|
||||
RemovePilot(entity, pilotComponent);
|
||||
}
|
||||
|
||||
public void ClearPilots(ShuttleConsoleComponent component)
|
||||
{
|
||||
while (component.SubscribedPilots.TryGetValue(0, out var pilot))
|
||||
{
|
||||
RemovePilot(pilot);
|
||||
RemovePilot(pilot.Owner, pilot);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,7 +152,7 @@ public sealed partial class ShuttleSystem
|
||||
else
|
||||
{
|
||||
FTLTravel(shuttle,
|
||||
CentCom.Value, _consoleAccumulator, TransitTime, dock: true);
|
||||
CentCom.Value, _consoleAccumulator, TransitTime, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ using Content.Shared.CCVar;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Shuttles.Events;
|
||||
using Content.Shared.Tiles;
|
||||
using Content.Shared.Tag;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.Maps;
|
||||
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>
|
||||
/// Calls the emergency shuttle for the station.
|
||||
/// </summary>
|
||||
@@ -305,6 +186,7 @@ public sealed partial class ShuttleSystem
|
||||
TransformComponent gridDockXform,
|
||||
Angle targetGridRotation,
|
||||
Box2 shuttleAABB,
|
||||
EntityUid gridUid,
|
||||
MapGridComponent grid,
|
||||
[NotNullWhen(true)] out Box2? shuttleDockedAABB,
|
||||
out Matrix3 matty,
|
||||
@@ -337,7 +219,8 @@ public sealed partial class ShuttleSystem
|
||||
// Rounding moment
|
||||
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;
|
||||
return true;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using Content.Server.Doors.Components;
|
||||
using Content.Server.Doors.Systems;
|
||||
using Content.Server.Shuttles.Components;
|
||||
using Content.Server.Station.Systems;
|
||||
@@ -12,10 +11,12 @@ using Robust.Shared.Map;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Utility;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Content.Server.Shuttles.Events;
|
||||
using Content.Shared.Buckle.Components;
|
||||
using Content.Shared.Doors.Components;
|
||||
using Content.Shared.Shuttles.Components;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Components;
|
||||
@@ -46,7 +47,7 @@ public sealed partial class ShuttleSystem
|
||||
/// <summary>
|
||||
/// Minimum mass a grid needs to be to block a shuttle recall.
|
||||
/// </summary>
|
||||
private const float ShuttleFTLMassThreshold = 300f;
|
||||
public const float ShuttleFTLMassThreshold = 300f;
|
||||
|
||||
// I'm too lazy to make CVars.
|
||||
|
||||
@@ -69,6 +70,11 @@ public sealed partial class ShuttleSystem
|
||||
/// </summary>
|
||||
private const int FTLProximityIterations = 3;
|
||||
|
||||
/// <summary>
|
||||
/// Minimum mass for an FTL destination
|
||||
/// </summary>
|
||||
public const float FTLDestinationMass = 500f;
|
||||
|
||||
private void InitializeFTL()
|
||||
{
|
||||
SubscribeLocalEvent<StationGridAddedEvent>(OnStationGridAdd);
|
||||
@@ -76,7 +82,9 @@ public sealed partial class ShuttleSystem
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -133,9 +141,11 @@ public sealed partial class ShuttleSystem
|
||||
return destination;
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public void RemoveFTLDestination(EntityUid uid)
|
||||
{
|
||||
if (!RemComp<FTLDestinationComponent>(uid)) return;
|
||||
if (!RemComp<FTLDestinationComponent>(uid))
|
||||
return;
|
||||
_console.RefreshShuttleConsoles();
|
||||
}
|
||||
|
||||
@@ -145,7 +155,8 @@ public sealed partial class ShuttleSystem
|
||||
public void FTLTravel(ShuttleComponent component,
|
||||
EntityCoordinates coordinates,
|
||||
float startupTime = DefaultStartupTime,
|
||||
float hyperspaceTime = DefaultTravelTime)
|
||||
float hyperspaceTime = DefaultTravelTime,
|
||||
string? priorityTag = null)
|
||||
{
|
||||
if (!TrySetupFTL(component, out var hyperspace))
|
||||
return;
|
||||
@@ -155,6 +166,7 @@ public sealed partial class ShuttleSystem
|
||||
hyperspace.Accumulator = hyperspace.StartupTime;
|
||||
hyperspace.TargetCoordinates = coordinates;
|
||||
hyperspace.Dock = false;
|
||||
hyperspace.PriorityTag = priorityTag;
|
||||
_console.RefreshShuttleConsoles();
|
||||
}
|
||||
|
||||
@@ -165,7 +177,8 @@ public sealed partial class ShuttleSystem
|
||||
EntityUid target,
|
||||
float startupTime = DefaultStartupTime,
|
||||
float hyperspaceTime = DefaultTravelTime,
|
||||
bool dock = false)
|
||||
bool dock = false,
|
||||
string? priorityTag = null)
|
||||
{
|
||||
if (!TrySetupFTL(component, out var hyperspace))
|
||||
return;
|
||||
@@ -175,6 +188,7 @@ public sealed partial class ShuttleSystem
|
||||
hyperspace.Accumulator = hyperspace.StartupTime;
|
||||
hyperspace.TargetUid = target;
|
||||
hyperspace.Dock = dock;
|
||||
hyperspace.PriorityTag = priorityTag;
|
||||
_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);
|
||||
// Make sure the map is setup before we leave to avoid pop-in (e.g. parallax).
|
||||
SetupHyperspace();
|
||||
|
||||
var ev = new FTLStartedEvent();
|
||||
RaiseLocalEvent(uid, ref ev);
|
||||
return true;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
if (comp.Accumulator > 0f) continue;
|
||||
if (comp.Accumulator > 0f)
|
||||
continue;
|
||||
|
||||
var uid = comp.Owner;
|
||||
var xform = Transform(uid);
|
||||
PhysicsComponent? body;
|
||||
ShuttleComponent? shuttle;
|
||||
@@ -280,13 +299,21 @@ public sealed partial class ShuttleSystem
|
||||
SetDockBolts(uid, false);
|
||||
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);
|
||||
MapId mapId;
|
||||
|
||||
if (comp.TargetUid != null && shuttle != null)
|
||||
{
|
||||
if (comp.Dock)
|
||||
TryFTLDock(shuttle, comp.TargetUid.Value);
|
||||
TryFTLDock(shuttle, comp.TargetUid.Value, comp.PriorityTag);
|
||||
else
|
||||
TryFTLProximity(shuttle, comp.TargetUid.Value);
|
||||
|
||||
@@ -439,7 +466,8 @@ public sealed partial class ShuttleSystem
|
||||
/// <summary>
|
||||
/// Tries to dock with the target grid, otherwise falls back to proximity.
|
||||
/// </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) ||
|
||||
!TryComp<TransformComponent>(targetUid, out var targetXform) ||
|
||||
@@ -449,7 +477,7 @@ public sealed partial class ShuttleSystem
|
||||
return false;
|
||||
}
|
||||
|
||||
var config = GetDockingConfig(component, targetUid);
|
||||
var config = GetDockingConfig(component, targetUid, priorityTag);
|
||||
|
||||
if (config != null)
|
||||
{
|
||||
@@ -460,7 +488,7 @@ public sealed partial class ShuttleSystem
|
||||
// Connect everything
|
||||
foreach (var (dockA, dockB) in config.Docks)
|
||||
{
|
||||
_dockSystem.Dock(dockA, dockB);
|
||||
_dockSystem.Dock(dockA.Owner, dockA, dockB.Owner, dockB);
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -476,7 +504,6 @@ public sealed partial class ShuttleSystem
|
||||
public bool TryFTLProximity(ShuttleComponent component, EntityUid targetUid, TransformComponent? xform = null, TransformComponent? targetXform = null)
|
||||
{
|
||||
if (!Resolve(targetUid, ref targetXform) ||
|
||||
targetXform.GridUid == null ||
|
||||
targetXform.MapUid == null ||
|
||||
!targetXform.MapUid.Value.IsValid() ||
|
||||
!Resolve(component.Owner, ref xform))
|
||||
@@ -502,9 +529,9 @@ public sealed partial class ShuttleSystem
|
||||
|
||||
var targetAABB = _transform.GetWorldMatrix(targetXform, xformQuery)
|
||||
.TransformBox(targetLocalAABB).Enlarged(shuttleAABB.Size.Length);
|
||||
var nearbyGrids = new HashSet<EntityUid>(1) { targetXform.GridUid.Value };
|
||||
var nearbyGrids = new HashSet<EntityUid>();
|
||||
var iteration = 0;
|
||||
var lastCount = 1;
|
||||
var lastCount = nearbyGrids.Count;
|
||||
var mapId = targetXform.MapID;
|
||||
|
||||
while (iteration < FTLProximityIterations)
|
||||
@@ -552,7 +579,7 @@ public sealed partial class ShuttleSystem
|
||||
}
|
||||
|
||||
// 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;
|
||||
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);
|
||||
|
||||
if (!HasComp<MapComponent>(targetXform.GridUid.Value))
|
||||
if (!HasComp<MapComponent>(targetXform.GridUid))
|
||||
{
|
||||
_transform.SetLocalRotation(xform, _random.NextAngle());
|
||||
}
|
||||
@@ -581,4 +608,138 @@ public sealed partial class ShuttleSystem
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,18 +8,6 @@ public sealed class CargoShuttleConsoleBoundUserInterfaceState : BoundUserInterf
|
||||
public string AccountName;
|
||||
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>
|
||||
/// List of orders expected on the delivery.
|
||||
/// </summary>
|
||||
@@ -28,14 +16,10 @@ public sealed class CargoShuttleConsoleBoundUserInterfaceState : BoundUserInterf
|
||||
public CargoShuttleConsoleBoundUserInterfaceState(
|
||||
string accountName,
|
||||
string shuttleName,
|
||||
bool canRecall,
|
||||
TimeSpan? shuttleETA,
|
||||
List<CargoOrderData> orders)
|
||||
{
|
||||
AccountName = accountName;
|
||||
ShuttleName = shuttleName;
|
||||
CanRecall = canRecall;
|
||||
ShuttleETA = shuttleETA;
|
||||
Orders = orders;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,21 +10,6 @@ namespace Content.Shared.Cargo.Components;
|
||||
[RegisterComponent, Access(typeof(SharedCargoSystem))]
|
||||
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>
|
||||
/// The assigned station for this cargo shuttle.
|
||||
/// </summary>
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
|
||||
}
|
||||
@@ -6,6 +6,9 @@ namespace Content.Shared.Shuttles.Systems;
|
||||
|
||||
public abstract class SharedRadarConsoleSystem : EntitySystem
|
||||
{
|
||||
public const float DefaultMinRange = 64f;
|
||||
public const float DefaultMaxRange = 256f;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
@@ -727,7 +727,8 @@
|
||||
id: AirlockExternalGlassShuttleEmergencyLocked
|
||||
suffix: External, Emergency, Glass, Docking, Locked
|
||||
components:
|
||||
- type: EmergencyDock
|
||||
- type: PriorityDock
|
||||
tag: DockEmergency
|
||||
- type: AccessReader
|
||||
access: [["External"]]
|
||||
|
||||
|
||||
@@ -182,6 +182,12 @@
|
||||
- type: Tag
|
||||
id: DiscreteHealthAnalyzer #So construction recipes don't eat medical PDAs
|
||||
|
||||
- type: Tag
|
||||
id: DockCargo
|
||||
|
||||
- type: Tag
|
||||
id: DockEmergency
|
||||
|
||||
- type: Tag
|
||||
id: Document
|
||||
|
||||
|
||||
Reference in New Issue
Block a user