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;
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();
}
}
- 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);
}
}
<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">
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;
[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");
}
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();
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}";
- }
- }
}
}
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;
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.
{
_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)
if (entManager.TryGetComponent(player, out PilotComponent? pilotComponent) &&
pilotComponent.Console != null)
{
- entManager.System<ShuttleConsoleSystem>().RemovePilot(pilotComponent);
+ entManager.System<ShuttleConsoleSystem>().RemovePilot(player, pilotComponent);
}
}
}
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);
}
}
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;
* 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>
// 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);
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);
private void SetCargoShuttleEnabled(bool value)
{
- if (_enabled == value) return;
+ if (_enabled == value)
+ return;
+
_enabled = value;
if (value)
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 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);
}
}
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);
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));
}
#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>
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;
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)}");
}
}
}
- 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>();
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;
-
- 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))
- {
- _popup.PopupEntity(Loc.GetString("cargo-no-shuttle"), args.Entity, args.Entity);
- return;
- }
+ var xform = Transform(uid);
+ var stationUid = component.Station;
- if (!_shuttle.CanFTL(shuttle.Owner, out var reason))
+ // Called
+ if (xform.MapID != CargoMap ||
+ !TryComp<StationCargoOrderDatabaseComponent>(stationUid, out var orderDatabase))
{
- _popup.PopupEntity(reason, args.Entity, args.Entity);
return;
}
- if (IsBlocked(shuttle))
- {
- _popup.PopupEntity(Loc.GetString("cargo-shuttle-console-organics"), player.Value, player.Value);
- _audio.PlayPvs(_audio.GetSound(component.DenySound), uid);
- return;
- }
-
- SellPallets((EntityUid) orderDatabase.Shuttle, out double price);
- bank.Balance += (int) price;
- _console.RefreshShuttleConsoles();
- SendToCargoMap(orderDatabase.Shuttle.Value);
+ AddCargoContents(component, orderDatabase);
+ UpdateOrders(orderDatabase);
+ UpdateCargoShuttleConsoles(component);
}
- /// <summary>
- ///
- /// </summary>
- /// <param name="component"></param>
- private bool IsBlocked(CargoShuttleComponent component)
+ private void OnCargoFTLCompleted(EntityUid uid, CargoShuttleComponent component, ref FTLCompletedEvent args)
{
- // 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);
- }
+ var xform = Transform(uid);
+ // Recalled
+ if (xform.MapID != CargoMap)
+ return;
- public bool FoundOrganics(EntityUid uid, EntityQuery<MobStateComponent> mobQuery, EntityQuery<TransformComponent> xformQuery)
- {
- var xform = xformQuery.GetComponent(uid);
- var childEnumerator = xform.ChildEnumerator;
+ var stationUid = component.Station;
- while (childEnumerator.MoveNext(out var child))
+ if (TryComp<StationBankAccountComponent>(stationUid, out var bank))
{
- if ((mobQuery.TryGetComponent(child.Value, out var mobState) && !_mobState.IsDead(child.Value, mobState))
- || FoundOrganics(child.Value, mobQuery, xformQuery)) return true;
+ SellPallets(uid, out var amount);
+ bank.Balance += (int) amount;
}
-
- 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
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);
}
}
// 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();
}
}
+++ /dev/null
-namespace Content.Server.Shuttles.Components;
-
-/// <summary>
-/// Given priority when considering where to dock an emergency shuttle.
-/// </summary>
-[RegisterComponent]
-public sealed class EmergencyDockComponent : Component {}
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;
[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")
{
--- /dev/null
+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;
+}
-using Content.Shared.Shuttles.Components;
-
namespace Content.Server.Shuttles.Components
{
[RegisterComponent]
[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>";
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);
}
}
--- /dev/null
+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);
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.
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);
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)
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);
}
}
var other = Comp<DockingComponent>(component.DockedWith!.Value);
Undock(component);
- Dock(component, other);
+ Dock(uid, component, component.DockedWith.Value, other);
_console.RefreshShuttleConsoles();
}
/// <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;
- SharedJointSystem.LinearStiffness(
- 2f,
- 0.7f,
- 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);
- joint = _jointSystem.GetOrCreateWeldJoint(gridA, gridB, dockA.DockJointId);
- }
- else
+ // May not be possible if map or the likes.
+ if (TryComp<PhysicsComponent>(gridA, out var gridPhysicsA) &&
+ TryComp<PhysicsComponent>(gridB, out var gridPhysicsB))
{
- joint = _jointSystem.GetOrCreateWeldJoint(gridA, gridB, DockingJoint + dockA.Owner);
- }
+ SharedJointSystem.LinearStiffness(
+ 2f,
+ 0.7f,
+ 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);
+ 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 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;
+ 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;
+ 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;
- dockA.DockJoint = joint;
- dockA.DockJointId = joint.ID;
+ dockB.DockJoint = joint;
+ dockB.DockJointId = joint.ID;
+ }
- dockB.DockJoint = joint;
- dockB.DockJointId = joint.ID;
+ dockA.DockedWith = dockBUid;
+ dockB.DockedWith = dockAUid;
- if (TryComp(dockA.Owner, out DoorComponent? doorA))
+ 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);
}
}
}
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)
/// <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)
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;
return;
}
- if (!dest.Enabled) return;
+ if (!dest.Enabled)
+ return;
- EntityUid? entity = component.Owner;
+ EntityUid? entity = uid;
var getShuttleEv = new ConsoleShuttleEvent
{
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;
}
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)
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);
}
}
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))
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)
if (console != null)
{
- RemovePilot(pilotComponent);
+ RemovePilot(user, pilotComponent);
if (console == component)
{
{
// 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,
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
{
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))
+ var destQuery = AllEntityQuery<FTLDestinationComponent>();
+
+ while (destQuery.MoveNext(out var destUid, out var comp))
{
- // 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;
+ // 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(comp.Owner);
+ var meta = metaQuery.GetComponent(destUid);
var name = meta.EntityName;
if (string.IsNullOrEmpty(name))
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,
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);
}
foreach (var comp in toRemove)
{
- RemovePilot(comp);
+ RemovePilot(comp.Owner, comp);
}
}
}
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)
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);
}
}
}
else
{
FTLTravel(shuttle,
- CentCom.Value, _consoleAccumulator, TransitTime, dock: true);
+ CentCom.Value, _consoleAccumulator, TransitTime, true);
}
}
}
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;
});
}
- /// <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>
TransformComponent gridDockXform,
Angle targetGridRotation,
Box2 shuttleAABB,
+ EntityUid gridUid,
MapGridComponent grid,
[NotNullWhen(true)] out Box2? shuttleDockedAABB,
out Matrix3 matty,
// 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;
-using Content.Server.Doors.Components;
using Content.Server.Doors.Systems;
using Content.Server.Shuttles.Components;
using Content.Server.Station.Systems;
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;
/// <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.
/// </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);
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);
}
return destination;
}
+ [PublicAPI]
public void RemoveFTLDestination(EntityUid uid)
{
- if (!RemComp<FTLDestinationComponent>(uid)) return;
+ if (!RemComp<FTLDestinationComponent>(uid))
+ return;
_console.RefreshShuttleConsoles();
}
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;
hyperspace.Accumulator = hyperspace.StartupTime;
hyperspace.TargetCoordinates = coordinates;
hyperspace.Dock = false;
+ hyperspace.PriorityTag = priorityTag;
_console.RefreshShuttleConsoles();
}
EntityUid target,
float startupTime = DefaultStartupTime,
float hyperspaceTime = DefaultTravelTime,
- bool dock = false)
+ bool dock = false,
+ string? priorityTag = null)
{
if (!TrySetupFTL(component, out var hyperspace))
return;
hyperspace.Accumulator = hyperspace.StartupTime;
hyperspace.TargetUid = target;
hyperspace.Dock = dock;
+ hyperspace.PriorityTag = priorityTag;
_console.RefreshShuttleConsoles();
}
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;
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);
/// <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) ||
return false;
}
- var config = GetDockingConfig(component, targetUid);
+ var config = GetDockingConfig(component, targetUid, priorityTag);
if (config != null)
{
// Connect everything
foreach (var (dockA, dockB) in config.Docks)
{
- _dockSystem.Dock(dockA, dockB);
+ _dockSystem.Dock(dockA.Owner, dockA, dockB.Owner, dockB);
}
return true;
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))
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)
}
// 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);
xform.Coordinates = new EntityCoordinates(targetXform.MapUid.Value, spawnPos);
- if (!HasComp<MapComponent>(targetXform.GridUid.Value))
+ if (!HasComp<MapComponent>(targetXform.GridUid))
{
_transform.SetLocalRotation(xform, _random.NextAngle());
}
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;
+ }
}
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>
public CargoShuttleConsoleBoundUserInterfaceState(
string accountName,
string shuttleName,
- bool canRecall,
- TimeSpan? shuttleETA,
List<CargoOrderData> orders)
{
AccountName = accountName;
ShuttleName = shuttleName;
- CanRecall = canRecall;
- ShuttleETA = shuttleETA;
Orders = orders;
}
}
[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>
+++ /dev/null
-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
-{
-
-}
+++ /dev/null
-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
-{
-
-}
public abstract class SharedRadarConsoleSystem : EntitySystem
{
+ public const float DefaultMinRange = 64f;
+ public const float DefaultMaxRange = 256f;
+
public override void Initialize()
{
base.Initialize();
components:
- type: AccessReader
access: [["External"]]
-
+
- type: entity
parent: AirlockExternal
id: AirlockExternalCargoLocked
suffix: External, Cargo, Locked
components:
- type: AccessReader
- access: [["Cargo"]]
-
+ access: [["Cargo"]]
+
- type: entity
parent: AirlockExternal
id: AirlockExternalEngineeringLocked
components:
- type: AccessReader
access: [["Atmospherics"]]
-
+
- type: entity
parent: AirlockFreezer
id: AirlockFreezerLocked
components:
- type: AccessReader
access: [["Cargo"]]
-
+
- type: entity
parent: AirlockExternalGlass
id: AirlockExternalGlassEngineeringLocked
suffix: External, Glass, Engineering, Locked
components:
- type: AccessReader
- access: [["Engineering"]]
+ access: [["Engineering"]]
- type: entity
parent: AirlockExternalGlass
suffix: External, Glass, Atmospherics, Locked
components:
- type: AccessReader
- access: [["Atmospherics"]]
-
+ access: [["Atmospherics"]]
+
- type: entity
parent: AirlockGlass
id: AirlockKitchenGlassLocked
id: AirlockExternalGlassShuttleEmergencyLocked
suffix: External, Emergency, Glass, Docking, Locked
components:
- - type: EmergencyDock
+ - type: PriorityDock
+ tag: DockEmergency
- type: AccessReader
access: [["External"]]
- type: Tag
id: DiscreteHealthAnalyzer #So construction recipes don't eat medical PDAs
+- type: Tag
+ id: DockCargo
+
+- type: Tag
+ id: DockEmergency
+
- type: Tag
id: Document