Add FTL destinations (#9685)

This commit is contained in:
metalgearsloth
2022-07-15 14:11:41 +10:00
committed by GitHub
parent 5e1d019f17
commit 1251b3aeda
34 changed files with 9133 additions and 227 deletions

View File

@@ -1,4 +1,6 @@
using Content.Shared.Cargo;
using Content.Shared.Cargo.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Server.Cargo.Components;
@@ -22,7 +24,7 @@ public sealed class StationCargoOrderDatabaseComponent : Component
/// </summary>
public int Index;
[ViewVariables, DataField("cargoShuttleProto")]
[ViewVariables, DataField("cargoShuttleProto", customTypeSerializer:typeof(PrototypeIdSerializer<CargoShuttlePrototype>))]
public string? CargoShuttleProto = "CargoShuttle";
/// <summary>

View File

@@ -1,4 +1,3 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Content.Server.Cargo.Components;
using Content.Server.Labels.Components;
@@ -6,6 +5,7 @@ using Content.Server.Shuttles.Components;
using Content.Server.Shuttles.Events;
using Content.Server.UserInterface;
using Content.Server.Paper;
using Content.Server.Shuttles.Systems;
using Content.Shared.Cargo;
using Content.Shared.Cargo.BUI;
using Content.Shared.Cargo.Components;
@@ -15,7 +15,6 @@ using Content.Shared.CCVar;
using Content.Shared.Dataset;
using Content.Shared.GameTicking;
using Content.Shared.MobState.Components;
using Robust.Server.Maps;
using Robust.Shared.Audio;
using Robust.Shared.Configuration;
@@ -40,16 +39,11 @@ public sealed partial class CargoSystem
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly EntityLookupSystem _lookup = default!;
[Dependency] private readonly PricingSystem _pricing = default!;
[Dependency] private readonly ShuttleConsoleSystem _console = default!;
[Dependency] private readonly ShuttleSystem _shuttle = default!;
public MapId? CargoMap { get; private set; }
private const float ShuttleRecallRange = 100f;
/// <summary>
/// Minimum mass a grid needs to be to block a shuttle recall.
/// </summary>
private const float ShuttleCallMassThreshold = 300f;
private const float CallOffset = 50f;
private int _index;
@@ -162,12 +156,11 @@ public sealed partial class CargoSystem
var orders = GetProjectedOrders(orderDatabase, shuttle);
var shuttleName = orderDatabase?.Shuttle != null ? MetaData(orderDatabase.Shuttle.Value).EntityName : string.Empty;
// TODO: Loc
_uiSystem.GetUiOrNull(component.Owner, 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,
CanRecallShuttle(shuttle?.Owner, out _),
_shuttle.CanFTL(shuttle?.Owner, out _),
shuttle?.NextCall,
orders));
}
@@ -194,7 +187,7 @@ public sealed partial class CargoSystem
var oldCanRecall = component.CanRecall;
// Check if we can update the recall status.
var canRecall = CanRecallShuttle(uid, out _, args.Component);
var canRecall = _shuttle.CanFTL(uid, out _, args.Component);
if (oldCanRecall == canRecall) return;
component.CanRecall = canRecall;
@@ -370,6 +363,7 @@ public sealed partial class CargoSystem
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;
@@ -399,6 +393,7 @@ public sealed partial class CargoSystem
AddCargoContents(shuttle, orderDatabase);
UpdateOrders(orderDatabase);
UpdateShuttleCargoConsoles(shuttle);
_console.RefreshShuttleConsoles();
_sawmill.Info($"Retrieved cargo shuttle {ToPrettyString(shuttle.Owner)} from {ToPrettyString(orderDatabase.Owner)}");
}
@@ -469,29 +464,6 @@ public sealed partial class CargoSystem
}
}
public bool CanRecallShuttle(EntityUid? uid, [NotNullWhen(false)] out string? reason, TransformComponent? xform = null)
{
reason = null;
if (!TryComp<IMapGridComponent>(uid, out var grid) ||
!Resolve(uid.Value, ref xform)) return true;
var bounds = grid.Grid.WorldAABB.Enlarged(ShuttleRecallRange);
var bodyQuery = GetEntityQuery<PhysicsComponent>();
foreach (var other in _mapManager.FindGridsIntersecting(xform.MapID, bounds))
{
if (grid.GridIndex == other.Index ||
!bodyQuery.TryGetComponent(other.GridEntityId, out var body) ||
body.Mass < ShuttleCallMassThreshold) continue;
reason = Loc.GetString("cargo-shuttle-console-proximity");
return false;
}
return true;
}
private void RecallCargoShuttle(EntityUid uid, CargoShuttleConsoleComponent component, CargoRecallShuttleMessage args)
{
var player = args.Session.AttachedEntity;
@@ -505,11 +477,11 @@ public sealed partial class CargoSystem
if (!TryComp<CargoShuttleComponent>(orderDatabase.Shuttle, out var shuttle))
{
_popup.PopupEntity($"No cargo shuttle found!", args.Entity, Filter.Entities(args.Entity));
_popup.PopupEntity(Loc.GetString("cargo-no-shuttle"), args.Entity, Filter.Entities(args.Entity));
return;
}
if (!CanRecallShuttle(shuttle.Owner, out var reason))
if (!_shuttle.CanFTL(shuttle.Owner, out var reason))
{
_popup.PopupEntity(reason, args.Entity, Filter.Entities(args.Entity));
return;
@@ -523,7 +495,7 @@ public sealed partial class CargoSystem
};
SellPallets(shuttle, bank);
_console.RefreshShuttleConsoles();
SendToCargoMap(orderDatabase.Shuttle.Value);
}

View File

@@ -43,9 +43,4 @@ public sealed partial class CargoSystem : SharedCargoSystem
UpdateConsole(frameTime);
UpdateTelepad(frameTime);
}
public void UpdateBankAccount(StationBankAccountComponent component, int BalanceAdded)
{
component.Balance += BalanceAdded;
}
}

View File

@@ -6,6 +6,8 @@ using Content.Server.Nuke;
using Content.Server.Players;
using Content.Server.Roles;
using Content.Server.RoundEnd;
using Content.Server.Shuttles.Components;
using Content.Server.Shuttles.Systems;
using Content.Server.Spawners.Components;
using Content.Server.Station.Components;
using Content.Server.Station.Systems;
@@ -185,43 +187,28 @@ public sealed class NukeopsRuleSystem : GameRuleSystem
}
// TODO: Make this a prototype
var map = "/Maps/infiltrator.yml";
// so true PAUL!
var path = "/Maps/nukieplanet.yml";
var shuttlePath = "/Maps/infiltrator.yml";
var mapId = _mapManager.CreateMap();
var center = new Vector2();
var minRadius = 0f;
Box2? aabb = null;
var (_, outpost) = _mapLoader.LoadBlueprint(mapId, "/Maps/nukieplanet.yml");
foreach (var uid in _stationSystem.Stations)
if (outpost == null)
{
if (TryComp<StationDataComponent>(uid, out var stationData))
{
foreach (var grid in stationData.Grids)
{
if (TryComp<IMapGridComponent>(grid, out var gridComp))
aabb = aabb?.Union(gridComp.Grid.WorldAABB) ?? gridComp.Grid.WorldAABB;
}
}
Logger.ErrorS("nukies", $"Error loading map {path} for nukies!");
return;
}
if (aabb != null)
// Listen I just don't want it to overlap.
var (_, shuttleId) = _mapLoader.LoadBlueprint(mapId, shuttlePath, new MapLoadOptions()
{
center = aabb.Value.Center;
minRadius = MathF.Max(aabb.Value.Width, aabb.Value.Height);
}
var (_, gridUid) = _mapLoader.LoadBlueprint(GameTicker.DefaultMap, map, new MapLoadOptions
{
Offset = center + MathF.Max(minRadius, minRadius) + 1000f,
Offset = Vector2.One * 1000f,
});
if (!gridUid.HasValue)
if (TryComp<ShuttleComponent>(shuttleId, out var shuttle))
{
Logger.ErrorS("NUKEOPS", $"Gridid was null when loading \"{map}\", aborting.");
foreach (var session in operatives)
{
ev.PlayerPool.Add(session);
}
return;
IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<ShuttleSystem>().TryFTLDock(shuttle, outpost.Value);
}
// TODO: Loot table or something
@@ -236,14 +223,18 @@ public sealed class NukeopsRuleSystem : GameRuleSystem
// Forgive me for hardcoding prototypes
foreach (var (_, meta, xform) in EntityManager.EntityQuery<SpawnPointComponent, MetaDataComponent, TransformComponent>(true))
{
if (meta.EntityPrototype?.ID != "SpawnPointNukies" || xform.ParentUid != gridUid) continue;
if (meta.EntityPrototype?.ID != "SpawnPointNukies") continue;
spawns.Add(xform.Coordinates);
if (xform.ParentUid == outpost)
{
spawns.Add(xform.Coordinates);
break;
}
}
if (spawns.Count == 0)
{
spawns.Add(EntityManager.GetComponent<TransformComponent>(gridUid.Value).Coordinates);
spawns.Add(EntityManager.GetComponent<TransformComponent>(outpost.Value).Coordinates);
Logger.WarningS("nukies", $"Fell back to default spawn for nukies!");
}

View File

@@ -6,6 +6,7 @@ using Content.Shared.Movement;
using Content.Shared.Movement.Components;
using Content.Shared.Movement.Systems;
using Content.Shared.Shuttles.Components;
using Content.Shared.Shuttles.Systems;
using Robust.Shared.Map;
using Robust.Shared.Player;
using Robust.Shared.Utility;
@@ -95,7 +96,7 @@ namespace Content.Server.Physics.Controllers
// Reset inputs for non-piloted shuttles.
foreach (var (shuttle, _) in _shuttlePilots)
{
if (newPilots.ContainsKey(shuttle)) continue;
if (newPilots.ContainsKey(shuttle) || FTLLocked(shuttle)) continue;
_thruster.DisableLinearThrusters(shuttle);
}
@@ -106,7 +107,7 @@ namespace Content.Server.Physics.Controllers
// then do the movement input once for it.
foreach (var (shuttle, pilots) in _shuttlePilots)
{
if (Paused(shuttle.Owner) || !TryComp(shuttle.Owner, out PhysicsComponent? body)) continue;
if (Paused(shuttle.Owner) || FTLLocked(shuttle) || !TryComp(shuttle.Owner, out PhysicsComponent? body)) continue;
// Collate movement linear and angular inputs together
var linearInput = Vector2.Zero;
@@ -255,6 +256,13 @@ namespace Content.Server.Physics.Controllers
}
}
}
private bool FTLLocked(ShuttleComponent shuttle)
{
return (TryComp<FTLComponent>(shuttle.Owner, out var ftl) &&
(ftl.State & (FTLState.Starting | FTLState.Travelling | FTLState.Arriving)) != 0x0);
}
/// <summary>
/// Add mobs riding vehicles to the list of mobs whose input
/// should be ignored.

View File

@@ -13,6 +13,9 @@ namespace Content.Server.Shuttles.Components
[ViewVariables]
public Joint? DockJoint;
[ViewVariables, DataField("dockJointId")]
public string? DockJointId;
[ViewVariables]
public override bool Docked => DockedWith != null;

View File

@@ -0,0 +1,52 @@
using Content.Shared.Shuttles.Systems;
using Content.Shared.Sound;
using Robust.Shared.Audio;
using Robust.Shared.Map;
namespace Content.Server.Shuttles.Components;
/// <summary>
/// Added to a component when it is queued or is travelling via FTL.
/// </summary>
[RegisterComponent]
public sealed class FTLComponent : Component
{
[ViewVariables]
public FTLState State = FTLState.Available;
[ViewVariables(VVAccess.ReadWrite)]
public float StartupTime = 0f;
[ViewVariables(VVAccess.ReadWrite)]
public float TravelTime = 0f;
[ViewVariables(VVAccess.ReadWrite)]
public float Accumulator = 0f;
/// <summary>
/// Target Uid to dock with at the end of FTL.
/// </summary>
[ViewVariables(VVAccess.ReadWrite), DataField("targetUid")]
public EntityUid? TargetUid;
[ViewVariables(VVAccess.ReadWrite), DataField("targetCoordinates")]
public EntityCoordinates TargetCoordinates;
/// <summary>
/// Should we dock with the target when arriving or show up nearby.
/// </summary>
[ViewVariables(VVAccess.ReadWrite), DataField("dock")]
public bool Dock;
[ViewVariables(VVAccess.ReadWrite), DataField("soundTravel")]
public SoundSpecifier? TravelSound = new SoundPathSpecifier("/Audio/Effects/Shuttle/hyperspace_progress.ogg")
{
Params =
{
Volume = -10,
Loop = true,
}
};
public IPlayingAudioStream? TravelStream;
}

View File

@@ -0,0 +1,19 @@
using Content.Shared.Whitelist;
namespace Content.Server.Shuttles.Components;
[RegisterComponent]
public sealed class FTLDestinationComponent : Component
{
/// <summary>
/// Should this destination be restricted in some form from console visibility.
/// </summary>
[ViewVariables(VVAccess.ReadWrite), DataField("whitelist")]
public EntityWhitelist? Whitelist;
/// <summary>
/// Is this destination visible but available to be warped to?
/// </summary>
[ViewVariables(VVAccess.ReadWrite), DataField("enabled")]
public bool Enabled = true;
}

View File

@@ -1,37 +0,0 @@
using Robust.Shared.Map;
namespace Content.Server.Shuttles.Components;
/// <summary>
/// Added to a component when it is queued or is travelling through hyperspace
/// </summary>
[RegisterComponent]
public sealed class HyperspaceComponent : Component
{
[ViewVariables]
public HyperspaceState State = HyperspaceState.Starting;
[ViewVariables(VVAccess.ReadWrite)]
public float StartupTime = 0f;
[ViewVariables(VVAccess.ReadWrite)]
public float TravelTime = 0f;
[ViewVariables(VVAccess.ReadWrite)]
public float Accumulator = 0f;
/// <summary>
/// Target Uid to dock with at the end of hyperspace.
/// </summary>
[ViewVariables(VVAccess.ReadWrite), DataField("targetUid")]
public EntityUid? TargetUid;
[ViewVariables(VVAccess.ReadWrite), DataField("targetCoordinates")]
public EntityCoordinates TargetCoordinates;
}
public enum HyperspaceState : byte
{
Starting,
Travelling,
}

View File

@@ -5,12 +5,6 @@ namespace Content.Server.Shuttles.Components
[RegisterComponent]
public sealed class ShuttleComponent : Component
{
/// <summary>
/// Should controls be enabled or disabled on this shuttle.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public bool CanPilot = true;
[ViewVariables]
public bool Enabled = true;

View File

@@ -5,12 +5,6 @@ namespace Content.Server.Shuttles.Components
[RegisterComponent]
public sealed class ShuttleConsoleComponent : SharedShuttleConsoleComponent
{
/// <summary>
/// Set by shuttlesystem if the grid should no longer be pilotable.
/// </summary>
[ViewVariables]
public bool CanPilot = true;
[ViewVariables]
public readonly List<PilotComponent> SubscribedPilots = new();

View File

@@ -9,6 +9,7 @@ using Robust.Shared.Map;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Collision.Shapes;
using Robust.Shared.Physics.Dynamics;
using Robust.Shared.Physics.Dynamics.Joints;
using Robust.Shared.Utility;
namespace Content.Server.Shuttles.Systems
@@ -156,9 +157,11 @@ namespace Content.Server.Shuttles.Systems
dockB.DockedWith = null;
dockB.DockJoint = null;
dockB.DockJointId = null;
dockA.DockJoint = null;
dockA.DockedWith = null;
dockA.DockJointId = null;
// If these grids are ever null then need to look at fixing ordering for unanchored events elsewhere.
var gridAUid = EntityManager.GetComponent<TransformComponent>(dockA.Owner).GridUid;
@@ -279,6 +282,11 @@ namespace Content.Server.Shuttles.Systems
/// </summary>
public void Dock(DockingComponent dockA, DockingComponent dockB)
{
if (dockB.Owner.GetHashCode() < dockA.Owner.GetHashCode())
{
(dockA, dockB) = (dockB, dockA);
}
_sawmill.Debug($"Docking between {dockA.Owner} and {dockB.Owner}");
// https://gamedev.stackexchange.com/questions/98772/b2distancejoint-with-frequency-equal-to-0-vs-b2weldjoint
@@ -302,8 +310,18 @@ namespace Content.Server.Shuttles.Systems
// These need playing around with
// Could also potentially have collideconnected false and stiffness 0 but it was a bit more suss???
WeldJoint joint;
var joint = _jointSystem.GetOrCreateWeldJoint(gridA, gridB, DockingJoint + dockA.Owner);
// 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 + dockA.Owner);
}
var gridAXform = EntityManager.GetComponent<TransformComponent>(gridA);
var gridBXform = EntityManager.GetComponent<TransformComponent>(gridB);
@@ -320,8 +338,12 @@ namespace Content.Server.Shuttles.Systems
dockA.DockedWith = dockB.Owner;
dockB.DockedWith = dockA.Owner;
dockA.DockJoint = joint;
dockA.DockJointId = joint.ID;
dockB.DockJoint = joint;
dockB.DockJointId = joint.ID;
if (TryComp<AirlockComponent>(dockA.Owner, out var airlockA))
{
@@ -439,10 +461,17 @@ namespace Content.Server.Shuttles.Systems
_doorSystem.TryClose(doorB.Owner, doorB);
}
var recentlyDocked = EnsureComp<RecentlyDockedComponent>(dock.Owner);
recentlyDocked.LastDocked = dock.DockedWith.Value;
recentlyDocked = EnsureComp<RecentlyDockedComponent>(dock.DockedWith.Value);
recentlyDocked.LastDocked = dock.DockedWith.Value;
if (!Deleted(dock.Owner))
{
var recentlyDocked = EnsureComp<RecentlyDockedComponent>(dock.Owner);
recentlyDocked.LastDocked = dock.DockedWith.Value;
}
if (!Deleted(dock.DockedWith.Value))
{
var recentlyDocked = EnsureComp<RecentlyDockedComponent>(dock.DockedWith.Value);
recentlyDocked.LastDocked = dock.DockedWith.Value;
}
Cleanup(dock);
}

View File

@@ -6,7 +6,6 @@ using Content.Server.Shuttles.Events;
using Content.Server.UserInterface;
using Content.Shared.ActionBlocker;
using Content.Shared.Alert;
using Content.Shared.Light;
using Content.Shared.Popups;
using Content.Shared.Shuttles.BUIStates;
using Content.Shared.Shuttles.Components;
@@ -15,15 +14,19 @@ using Content.Shared.Shuttles.Systems;
using Content.Shared.Tag;
using Robust.Server.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.Map;
using Robust.Shared.Player;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Content.Server.Shuttles.Systems
{
public sealed class ShuttleConsoleSystem : SharedShuttleConsoleSystem
{
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly ActionBlockerSystem _blocker = default!;
[Dependency] private readonly AlertsSystem _alertsSystem = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly ShuttleSystem _shuttle = default!;
[Dependency] private readonly TagSystem _tags = default!;
[Dependency] private readonly UserInterfaceSystem _ui = default!;
@@ -36,7 +39,9 @@ namespace Content.Server.Shuttles.Systems
SubscribeLocalEvent<ShuttleConsoleComponent, AnchorStateChangedEvent>(OnConsoleAnchorChange);
SubscribeLocalEvent<ShuttleConsoleComponent, ActivatableUIOpenAttemptEvent>(OnConsoleUIOpenAttempt);
SubscribeLocalEvent<ShuttleConsoleComponent, ShuttleModeRequestMessage>(OnModeRequest);
SubscribeLocalEvent<ShuttleConsoleComponent, ShuttleConsoleDestinationMessage>(OnDestinationMessage);
SubscribeLocalEvent<ShuttleConsoleComponent, BoundUIClosedEvent>(OnConsoleUIClose);
SubscribeLocalEvent<DockEvent>(OnDock);
SubscribeLocalEvent<UndockEvent>(OnUndock);
@@ -44,6 +49,46 @@ namespace Content.Server.Shuttles.Systems
SubscribeLocalEvent<PilotComponent, ComponentGetState>(OnGetState);
}
private void OnDestinationMessage(EntityUid uid, ShuttleConsoleComponent component, ShuttleConsoleDestinationMessage args)
{
if (!TryComp<FTLDestinationComponent>(args.Destination, out var dest)) return;
if (!dest.Enabled) return;
EntityUid? entity = component.Owner;
var getShuttleEv = new ConsoleShuttleEvent
{
Console = uid,
};
RaiseLocalEvent(entity.Value, ref getShuttleEv);
entity = getShuttleEv.Console;
if (entity == null || dest.Whitelist?.IsValid(entity.Value, EntityManager) == false) return;
if (!TryComp<TransformComponent>(entity, out var xform) ||
!TryComp<ShuttleComponent>(xform.GridUid, out var shuttle)) return;
if (HasComp<FTLComponent>(xform.GridUid))
{
if (args.Session.AttachedEntity != null)
_popup.PopupCursor(Loc.GetString("shuttle-console-in-ftl"), Filter.Entities(args.Session.AttachedEntity.Value));
return;
}
if (!_shuttle.CanFTL(shuttle.Owner, out var reason))
{
if (args.Session.AttachedEntity != null)
_popup.PopupCursor(reason, Filter.Entities(args.Session.AttachedEntity.Value));
return;
}
_shuttle.FTLTravel(shuttle, args.Destination, hyperspaceTime: _shuttle.TransitTime);
}
private void OnDock(DockEvent ev)
{
RefreshShuttleConsoles();
@@ -54,12 +99,17 @@ namespace Content.Server.Shuttles.Systems
RefreshShuttleConsoles();
}
public void RefreshShuttleConsoles(EntityUid uid)
{
// TODO: Should really call this per shuttle in some instances.
RefreshShuttleConsoles();
}
/// <summary>
/// Refreshes all of the data for shuttle consoles.
/// </summary>
public void RefreshShuttleConsoles()
{
// TODO: Should really call this per shuttle in some instances.
var docks = GetAllDocks();
foreach (var comp in EntityQuery<ShuttleConsoleComponent>(true))
@@ -87,7 +137,7 @@ namespace Content.Server.Shuttles.Systems
private void OnConsoleUIOpenAttempt(EntityUid uid, ShuttleConsoleComponent component, ActivatableUIOpenAttemptEvent args)
{
if (!component.CanPilot || !TryPilot(args.User, uid))
if (!TryPilot(args.User, uid))
args.Cancel();
}
@@ -174,7 +224,7 @@ namespace Content.Server.Shuttles.Systems
if (!this.IsPowered(consoleComponent.Owner, EntityManager) ||
!Resolve(consoleComponent.Owner, ref consoleXform) ||
!consoleXform.Anchored ||
consoleXform.GridID != Transform(shuttleComponent.Owner).GridID)
consoleXform.GridUid != Transform(shuttleComponent.Owner).GridUid)
{
return;
}
@@ -228,7 +278,7 @@ namespace Content.Server.Shuttles.Systems
Console = entity,
};
RaiseLocalEvent(entity.Value, ref getShuttleEv, false);
RaiseLocalEvent(entity.Value, ref getShuttleEv);
entity = getShuttleEv.Console;
TryComp<TransformComponent>(entity, out var consoleXform);
@@ -236,14 +286,64 @@ namespace Content.Server.Shuttles.Systems
var range = radar?.MaxRange ?? 0f;
TryComp<ShuttleComponent>(consoleXform?.GridUid, out var shuttle);
component.CanPilot = shuttle is { CanPilot: true };
var mode = shuttle?.Mode ?? ShuttleMode.Cruise;
var destinations = new List<(EntityUid, string, bool)>();
var ftlState = FTLState.Available;
var ftlTime = TimeSpan.Zero;
if (TryComp<FTLComponent>(shuttle?.Owner, 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))
{
var metaQuery = GetEntityQuery<MetaDataComponent>();
// Can't go anywhere when in FTL.
var locked = shuttleFtl != null || Paused(shuttle!.Owner);
// Can't cache it because it may have a whitelist for the particular console.
// Include paused as we still want to show centcomm.
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 meta = metaQuery.GetComponent(comp.Owner);
var name = meta.EntityName;
if (string.IsNullOrEmpty(name))
name = Loc.GetString("shuttle-console-unknown");
var canTravel = !locked &&
comp.Enabled &&
!Paused(comp.Owner, meta) &&
(!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)
{
canTravel = false;
}
destinations.Add((comp.Owner, name, canTravel));
}
}
docks ??= GetAllDocks();
_ui.GetUiOrNull(component.Owner, ShuttleConsoleUiKey.Key)
?.SetState(new ShuttleConsoleBoundInterfaceState(
ftlState,
ftlTime,
mode,
destinations,
range,
consoleXform?.Coordinates,
consoleXform?.LocalRotation,

View File

@@ -52,7 +52,7 @@ public sealed partial class ShuttleSystem
/// <summary>
/// <see cref="CCVars.EmergencyShuttleTransitTime"/>
/// </summary>
private float _transitTime;
public float TransitTime { get; private set; }
/// <summary>
/// <see cref="CCVars.EmergencyShuttleAuthorizeTime"/>
@@ -69,6 +69,11 @@ public sealed partial class ShuttleSystem
/// </summary>
private bool _launchedShuttles;
/// <summary>
/// Have we announced the launch?
/// </summary>
private bool _announced;
private void InitializeEmergencyConsole()
{
_configManager.OnValueChanged(CCVars.EmergencyShuttleTransitTime, SetTransitTime, true);
@@ -86,7 +91,7 @@ public sealed partial class ShuttleSystem
private void SetTransitTime(float obj)
{
_transitTime = obj;
TransitTime = obj;
}
private void ShutdownEmergencyConsole()
@@ -106,6 +111,14 @@ public sealed partial class ShuttleSystem
_consoleAccumulator -= frameTime;
// No early launch but we're under the timer.
if (!_launchedShuttles && _consoleAccumulator <= _authorizeTime)
{
if (!EarlyLaunchAuthorized)
AnnounceLaunch();
}
// Imminent departure
if (!_launchedShuttles && _consoleAccumulator <= DefaultStartupTime)
{
_launchedShuttles = true;
@@ -119,27 +132,33 @@ public sealed partial class ShuttleSystem
if (Deleted(_centcomm))
{
// TODO: Need to get non-overlapping positions.
Hyperspace(shuttle,
FTLTravel(shuttle,
new EntityCoordinates(
_mapManager.GetMapEntityId(_centcommMap.Value),
Vector2.One * 1000f), _consoleAccumulator, _transitTime);
Vector2.One * 1000f), _consoleAccumulator, TransitTime);
}
else
{
Hyperspace(shuttle,
_centcomm.Value, _consoleAccumulator, _transitTime);
FTLTravel(shuttle,
_centcomm.Value, _consoleAccumulator, TransitTime);
}
}
}
}
// Departed
if (_consoleAccumulator <= 0f)
{
_launchedShuttles = true;
_chatSystem.DispatchGlobalAnnouncement(Loc.GetString("emergency-shuttle-left", ("transitTime", $"{_transitTime:0}")));
_chatSystem.DispatchGlobalAnnouncement(Loc.GetString("emergency-shuttle-left", ("transitTime", $"{TransitTime:0}")));
_roundEndCancelToken = new CancellationTokenSource();
Timer.Spawn((int) (_transitTime * 1000) + _bufferTime.Milliseconds, () => _roundEnd.EndRound(), _roundEndCancelToken.Token);
Timer.Spawn((int) (TransitTime * 1000) + _bufferTime.Milliseconds, () => _roundEnd.EndRound(), _roundEndCancelToken.Token);
// Guarantees that emergency shuttle arrives first before anyone else can FTL.
if (_centcomm != null)
AddFTLDestination(_centcomm.Value, true);
}
}
@@ -213,6 +232,7 @@ public sealed partial class ShuttleSystem
private void CleanupEmergencyConsole()
{
_announced = false;
_roundEndCancelToken = null;
_launchedShuttles = false;
_consoleAccumulator = 0f;
@@ -259,20 +279,28 @@ public sealed partial class ShuttleSystem
/// </summary>
public bool EarlyLaunch()
{
if (EarlyLaunchAuthorized || !EmergencyShuttleArrived) return false;
if (EarlyLaunchAuthorized || !EmergencyShuttleArrived || _consoleAccumulator <= _authorizeTime) return false;
_logger.Add(LogType.EmergencyShuttle, LogImpact.Extreme, $"Emergency shuttle launch authorized");
_consoleAccumulator = MathF.Max(1f, MathF.Min(_consoleAccumulator, _authorizeTime));
_consoleAccumulator =_authorizeTime;
EarlyLaunchAuthorized = true;
RaiseLocalEvent(new EmergencyShuttleAuthorizedEvent());
AnnounceLaunch();
UpdateAllEmergencyConsoles();
return true;
}
private void AnnounceLaunch()
{
if (_announced) return;
_announced = true;
_chatSystem.DispatchGlobalAnnouncement(
Loc.GetString("emergency-shuttle-launch-time", ("consoleAccumulator", $"{_consoleAccumulator:0}")),
playDefaultSound: false,
colorOverride: DangerColor);
SoundSystem.Play("/Audio/Misc/notice1.ogg", Filter.Broadcast());
UpdateAllEmergencyConsoles();
return true;
}
public bool DelayEmergencyRoundEnd()

View File

@@ -132,7 +132,6 @@ public sealed partial class ShuttleSystem
var shuttleAABB = Comp<IMapGridComponent>(component.Owner).Grid.LocalAABB;
var validDockConfigs = new List<DockingConfig>();
SetPilotable(component, false);
if (shuttleDocks.Count > 0)
{
@@ -165,7 +164,7 @@ public sealed partial class ShuttleSystem
if (_mapManager.FindGridsIntersecting(targetGridXform.MapID,
dockedBounds).Any(o => o.GridEntityId != targetGrid))
{
break;
continue;
}
// Alright well the spawn is valid now to check how many we can connect
@@ -255,7 +254,7 @@ public sealed partial class ShuttleSystem
return;
}
if (TryHyperspaceDock(shuttle, targetGrid.Value))
if (TryFTLDock(shuttle, targetGrid.Value))
{
var xformQuery = GetEntityQuery<TransformComponent>();
@@ -417,6 +416,9 @@ public sealed partial class ShuttleSystem
{
var (_, centcomm) = _loader.LoadBlueprint(_centcommMap.Value, "/Maps/centcomm.yml");
_centcomm = centcomm;
if (_centcomm != null)
AddFTLDestination(_centcomm.Value, false);
}
else
{

View File

@@ -3,7 +3,9 @@ using Content.Server.Buckle.Components;
using Content.Server.Doors.Components;
using Content.Server.Doors.Systems;
using Content.Server.Shuttles.Components;
using Content.Server.Station.Systems;
using Content.Server.Stunnable;
using Content.Shared.Shuttles.Systems;
using Content.Shared.Sound;
using Content.Shared.StatusEffect;
using Robust.Shared.Audio;
@@ -21,12 +23,23 @@ public sealed partial class ShuttleSystem
*/
[Dependency] private readonly DoorSystem _doors = default!;
[Dependency] private readonly ShuttleConsoleSystem _console = default!;
[Dependency] private readonly StunSystem _stuns = default!;
[Dependency] private readonly ThrusterSystem _thruster = default!;
private MapId? _hyperSpaceMap;
private const float DefaultStartupTime = 5.5f;
private const float DefaultTravelTime = 30f;
private const float DefaultArrivalTime = 5f;
private const float FTLCooldown = 30f;
private const float ShuttleFTLRange = 100f;
/// <summary>
/// Minimum mass a grid needs to be to block a shuttle recall.
/// </summary>
private const float ShuttleFTLMassThreshold = 300f;
// I'm too lazy to make CVars.
@@ -44,62 +57,144 @@ public sealed partial class ShuttleSystem
/// </summary>
private const float Buffer = 5f;
private void InitializeFTL()
{
SubscribeLocalEvent<StationGridAddedEvent>(OnStationGridAdd);
SubscribeLocalEvent<FTLDestinationComponent, EntityPausedEvent>(OnDestinationPause);
}
private void OnDestinationPause(EntityUid uid, FTLDestinationComponent component, EntityPausedEvent args)
{
_console.RefreshShuttleConsoles();
}
private void OnStationGridAdd(StationGridAddedEvent ev)
{
if (TryComp<PhysicsComponent>(ev.GridId, out var body) && body.Mass > 500f)
{
AddFTLDestination(ev.GridId, true);
}
}
public bool CanFTL(EntityUid? uid, [NotNullWhen(false)] out string? reason, TransformComponent? xform = null)
{
reason = null;
if (!TryComp<IMapGridComponent>(uid, out var grid) ||
!Resolve(uid.Value, ref xform)) return true;
var bounds = grid.Grid.WorldAABB.Enlarged(ShuttleFTLRange);
var bodyQuery = GetEntityQuery<PhysicsComponent>();
foreach (var other in _mapManager.FindGridsIntersecting(xform.MapID, bounds))
{
if (grid.GridIndex == other.Index ||
!bodyQuery.TryGetComponent(other.GridEntityId, out var body) ||
body.Mass < ShuttleFTLMassThreshold) continue;
reason = Loc.GetString("shuttle-console-proximity");
return false;
}
return true;
}
/// <summary>
/// Adds a target for hyperspace to every shuttle console.
/// </summary>
public FTLDestinationComponent AddFTLDestination(EntityUid uid, bool enabled)
{
if (TryComp<FTLDestinationComponent>(uid, out var destination) && destination.Enabled == enabled) return destination;
destination = EnsureComp<FTLDestinationComponent>(uid);
if (HasComp<FTLComponent>(uid))
{
enabled = false;
}
destination.Enabled = enabled;
_console.RefreshShuttleConsoles();
return destination;
}
public void RemoveFTLDestination(EntityUid uid)
{
if (!RemComp<FTLDestinationComponent>(uid)) return;
_console.RefreshShuttleConsoles();
}
/// <summary>
/// Moves a shuttle from its current position to the target one. Goes through the hyperspace map while the timer is running.
/// </summary>
public void Hyperspace(ShuttleComponent component,
public void FTLTravel(ShuttleComponent component,
EntityCoordinates coordinates,
float startupTime = DefaultStartupTime,
float hyperspaceTime = DefaultTravelTime)
{
if (!TrySetupHyperspace(component.Owner, out var hyperspace))
if (!TrySetupFTL(component, out var hyperspace))
return;
hyperspace.StartupTime = startupTime;
hyperspace.TravelTime = hyperspaceTime;
hyperspace.Accumulator = hyperspace.StartupTime;
hyperspace.TargetCoordinates = coordinates;
hyperspace.Dock = false;
_console.RefreshShuttleConsoles();
}
/// <summary>
/// Moves a shuttle from its current position to docked on the target one. Goes through the hyperspace map while the timer is running.
/// </summary>
public void Hyperspace(ShuttleComponent component,
public void FTLTravel(ShuttleComponent component,
EntityUid target,
float startupTime = DefaultStartupTime,
float hyperspaceTime = DefaultTravelTime)
float hyperspaceTime = DefaultTravelTime,
bool dock = false)
{
if (!TrySetupHyperspace(component.Owner, out var hyperspace))
if (!TrySetupFTL(component, out var hyperspace))
return;
hyperspace.State = FTLState.Starting;
hyperspace.StartupTime = startupTime;
hyperspace.TravelTime = hyperspaceTime;
hyperspace.Accumulator = hyperspace.StartupTime;
hyperspace.TargetUid = target;
hyperspace.Dock = dock;
_console.RefreshShuttleConsoles();
}
private bool TrySetupHyperspace(EntityUid uid, [NotNullWhen(true)] out HyperspaceComponent? component)
private bool TrySetupFTL(ShuttleComponent shuttle, [NotNullWhen(true)] out FTLComponent? component)
{
var uid = shuttle.Owner;
component = null;
if (HasComp<HyperspaceComponent>(uid))
if (HasComp<FTLComponent>(uid))
{
_sawmill.Warning($"Tried queuing {ToPrettyString(uid)} which already has HyperspaceComponent?");
return false;
}
if (TryComp<FTLDestinationComponent>(uid, out var dest))
{
dest.Enabled = false;
}
_thruster.DisableLinearThrusters(shuttle);
_thruster.EnableLinearThrustDirection(shuttle, DirectionFlag.North);
_thruster.SetAngularThrust(shuttle, false);
// TODO: Maybe move this to docking instead?
SetDocks(uid, false);
component = AddComp<HyperspaceComponent>(uid);
component = AddComp<FTLComponent>(uid);
// TODO: Need BroadcastGrid to not be bad.
SoundSystem.Play(_startupSound.GetSound(), Filter.Pvs(component.Owner, GetSoundRange(component.Owner), entityManager: EntityManager), _startupSound.Params);
SoundSystem.Play(_startupSound.GetSound(), Filter.Empty().AddInRange(Transform(uid).MapPosition, GetSoundRange(component.Owner)), _startupSound.Params);
return true;
}
private void UpdateHyperspace(float frameTime)
{
foreach (var comp in EntityQuery<HyperspaceComponent>())
foreach (var comp in EntityQuery<FTLComponent>())
{
comp.Accumulator -= frameTime;
@@ -107,21 +202,22 @@ public sealed partial class ShuttleSystem
var xform = Transform(comp.Owner);
PhysicsComponent? body;
ShuttleComponent? shuttle;
switch (comp.State)
{
// Startup time has elapsed and in hyperspace.
case HyperspaceState.Starting:
case FTLState.Starting:
DoTheDinosaur(xform);
comp.State = HyperspaceState.Travelling;
comp.State = FTLState.Travelling;
SetupHyperspace();
var width = Comp<IMapGridComponent>(comp.Owner).Grid.LocalAABB.Width;
xform.Coordinates = new EntityCoordinates(_mapManager.GetMapEntityId(_hyperSpaceMap!.Value), new Vector2(_index + width / 2f, 0f));
xform.LocalRotation = Angle.Zero;
_index += width + Buffer;
comp.Accumulator += comp.TravelTime;
comp.Accumulator += comp.TravelTime - DefaultArrivalTime;
if (TryComp(comp.Owner, out body))
{
@@ -131,11 +227,32 @@ public sealed partial class ShuttleSystem
body.AngularDamping = 0f;
}
SetDockBolts(comp.Owner, true);
if (comp.TravelSound != null)
{
comp.TravelStream = SoundSystem.Play(comp.TravelSound.GetSound(),
Filter.Pvs(comp.Owner, 4f, entityManager: EntityManager), comp.TravelSound.Params);
}
SetDockBolts(comp.Owner, true);
_console.RefreshShuttleConsoles(comp.Owner);
break;
// Arrive.
case HyperspaceState.Travelling:
// Arriving, play effects
case FTLState.Travelling:
comp.Accumulator += DefaultArrivalTime;
comp.State = FTLState.Arriving;
// TODO: Arrival effects
// For now we'll just use the ss13 bubbles but we can do fancier.
if (TryComp(comp.Owner, out shuttle))
{
_thruster.DisableLinearThrusters(shuttle);
_thruster.EnableLinearThrustDirection(shuttle, DirectionFlag.South);
}
_console.RefreshShuttleConsoles(comp.Owner);
break;
// Arrived
case FTLState.Arriving:
DoTheDinosaur(xform);
SetDockBolts(comp.Owner, false);
SetDocks(comp.Owner, true);
@@ -148,18 +265,45 @@ public sealed partial class ShuttleSystem
body.AngularDamping = ShuttleIdleAngularDamping;
}
if (comp.TargetUid != null && TryComp<ShuttleComponent>(comp.Owner, out var shuttle))
TryComp(comp.Owner, out shuttle);
if (comp.TargetUid != null && shuttle != null)
{
TryHyperspaceDock(shuttle, comp.TargetUid.Value);
if (comp.Dock)
TryFTLDock(shuttle, comp.TargetUid.Value);
else
TryFTLProximity(shuttle, comp.TargetUid.Value);
}
else
{
xform.Coordinates = comp.TargetCoordinates;
}
SoundSystem.Play(_arrivalSound.GetSound(),
Filter.Pvs(comp.Owner, GetSoundRange(comp.Owner), entityManager: EntityManager));
RemComp<HyperspaceComponent>(comp.Owner);
if (shuttle != null)
{
_thruster.DisableLinearThrusters(shuttle);
}
if (comp.TravelStream != null)
{
comp.TravelStream?.Stop();
comp.TravelStream = null;
}
SoundSystem.Play(_arrivalSound.GetSound(), Filter.Empty().AddInRange(Transform(comp.Owner).MapPosition, GetSoundRange(comp.Owner)), _arrivalSound.Params);
if (TryComp<FTLDestinationComponent>(comp.Owner, out var dest))
{
dest.Enabled = true;
}
comp.State = FTLState.Cooldown;
comp.Accumulator += FTLCooldown;
_console.RefreshShuttleConsoles(comp.Owner);
break;
case FTLState.Cooldown:
RemComp<FTLComponent>(comp.Owner);
_console.RefreshShuttleConsoles(comp.Owner);
break;
default:
throw new ArgumentOutOfRangeException();
@@ -248,7 +392,10 @@ public sealed partial class ShuttleSystem
}
}
private bool TryHyperspaceDock(ShuttleComponent component, EntityUid targetUid)
/// <summary>
/// Tries to dock with the target grid, otherwise falls back to proximity.
/// </summary>
public bool TryFTLDock(ShuttleComponent component, EntityUid targetUid)
{
if (!TryComp<TransformComponent>(component.Owner, out var xform) ||
!TryComp<TransformComponent>(targetUid, out var targetXform) ||
@@ -271,6 +418,17 @@ public sealed partial class ShuttleSystem
return true;
}
TryFTLProximity(component, targetUid, xform, targetXform);
return false;
}
/// <summary>
/// Tries to arrive nearby without overlapping with other grids.
/// </summary>
public bool TryFTLProximity(ShuttleComponent component, EntityUid targetUid, TransformComponent? xform = null, TransformComponent? targetXform = null)
{
if (!Resolve(targetUid, ref targetXform) || targetXform.MapUid == null || !Resolve(component.Owner, ref xform)) return false;
var shuttleAABB = Comp<IMapGridComponent>(component.Owner).Grid.WorldAABB;
Box2? aabb = null;
@@ -284,7 +442,7 @@ public sealed partial class ShuttleSystem
aabb ??= new Box2();
var minRadius = MathF.Max(aabb.Value.Width, aabb.Value.Height) + MathF.Max(shuttleAABB.Width, shuttleAABB.Height);
var spawnPos = aabb.Value.Center + _random.NextVector2(minRadius, minRadius + 10f);
var spawnPos = aabb.Value.Center + _random.NextVector2(minRadius, minRadius + 256f);
if (TryComp<PhysicsComponent>(component.Owner, out var shuttleBody))
{

View File

@@ -1,8 +1,7 @@
using Content.Server.RoundEnd;
using Content.Server.Shuttles.Components;
using Content.Shared.CCVar;
using Content.Shared.GameTicking;
using Content.Shared.Shuttles.Components;
using Content.Shared.Shuttles.Systems;
using JetBrains.Annotations;
using Robust.Server.GameObjects;
using Robust.Shared.Configuration;
@@ -12,7 +11,7 @@ using Robust.Shared.Physics;
namespace Content.Server.Shuttles.Systems
{
[UsedImplicitly]
public sealed partial class ShuttleSystem : EntitySystem
public sealed partial class ShuttleSystem : SharedShuttleSystem
{
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly FixtureSystem _fixtures = default!;
@@ -38,6 +37,7 @@ namespace Content.Server.Shuttles.Systems
InitializeEmergencyConsole();
InitializeEscape();
InitializeFTL();
SubscribeLocalEvent<ShuttleComponent, ComponentAdd>(OnShuttleAdd);
SubscribeLocalEvent<ShuttleComponent, ComponentStartup>(OnShuttleStartup);
@@ -138,24 +138,6 @@ namespace Content.Server.Shuttles.Systems
}
}
/// <summary>
/// Enables or disables a shuttle's piloting controls.
/// </summary>
public void SetPilotable(ShuttleComponent component, bool value)
{
if (component.CanPilot == value) return;
component.CanPilot = value;
foreach (var comp in EntityQuery<ShuttleConsoleComponent>(true))
{
comp.CanPilot = value;
// I'm gonna pray if the UI is force closed and we block UI opens that BUI handles it.
if (!value)
_uiSystem.GetUiOrNull(comp.Owner, ShuttleConsoleUiKey.Key)?.CloseAll();
}
}
public void Toggle(ShuttleComponent component)
{
if (!EntityManager.TryGetComponent(component.Owner, out PhysicsComponent? physicsComponent)) return;
@@ -176,7 +158,6 @@ namespace Content.Server.Shuttles.Systems
{
component.BodyType = BodyType.Dynamic;
component.BodyStatus = BodyStatus.InAir;
//component.FixedRotation = false; TODO WHEN ROTATING SHUTTLES FIXED.
component.FixedRotation = false;
component.LinearDamping = ShuttleIdleLinearDamping;
component.AngularDamping = ShuttleIdleAngularDamping;

View File

@@ -17,11 +17,12 @@ public sealed class SpaceGarbageSystem : EntitySystem
private void OnCollide(EntityUid uid, SpaceGarbageComponent component, StartCollideEvent args)
{
if (args.OtherFixture.Body.BodyType != BodyType.Static) return;
var ourXform = Transform(args.OurFixture.Body.Owner);
var otherXform = Transform(args.OtherFixture.Body.Owner);
if (ourXform.GridUid == otherXform.GridUid ||
args.OtherFixture.Body.BodyType != BodyType.Static) return;
if (ourXform.GridUid == otherXform.GridUid) return;
QueueDel(uid);
}