Evac shuttle (#8931)
Co-authored-by: metalgearsloth <metalgearsloth@gmail.com>
This commit is contained in:
@@ -24,7 +24,8 @@ namespace Content.Client.Computer
|
||||
{
|
||||
base.InitializeEntity(entity);
|
||||
|
||||
var sprite = IoCManager.Resolve<IEntityManager>().GetComponent<ISpriteComponent>(entity);
|
||||
if (!IoCManager.Resolve<IEntityManager>().TryGetComponent<SpriteComponent>(entity, out var sprite)) return;
|
||||
|
||||
sprite.LayerSetState(Layers.Screen, ScreenState);
|
||||
|
||||
if (!string.IsNullOrEmpty(KeyboardState))
|
||||
@@ -38,7 +39,7 @@ namespace Content.Client.Computer
|
||||
{
|
||||
base.OnChangeData(component);
|
||||
|
||||
var sprite = IoCManager.Resolve<IEntityManager>().GetComponent<ISpriteComponent>(component.Owner);
|
||||
if (!IoCManager.Resolve<IEntityManager>().TryGetComponent<SpriteComponent>(component.Owner, out var sprite)) return;
|
||||
|
||||
if (!component.TryGetData(ComputerVisuals.Powered, out bool powered))
|
||||
{
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
using Content.Client.Computer;
|
||||
using Content.Client.Shuttles.UI;
|
||||
using Content.Shared.Shuttles.BUIStates;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
|
||||
namespace Content.Client.Shuttles.BUI;
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed class EmergencyConsoleBoundUserInterface : ComputerBoundUserInterface<EmergencyConsoleWindow, EmergencyConsoleBoundUserInterfaceState>
|
||||
{
|
||||
public EmergencyConsoleBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey) {}
|
||||
}
|
||||
@@ -19,6 +19,15 @@ public sealed class RadarConsoleBoundUserInterface : BoundUserInterface
|
||||
_window?.OpenCentered();
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
if (disposing)
|
||||
{
|
||||
_window?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void UpdateState(BoundUserInterfaceState state)
|
||||
{
|
||||
base.UpdateState(state);
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
using Content.Client.Shuttles.Systems;
|
||||
using Robust.Shared.Console;
|
||||
|
||||
namespace Content.Client.Shuttles.Commands;
|
||||
|
||||
public sealed class ShowEmergencyShuttleCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "showemergencyshuttle";
|
||||
public string Description => "Shows the expected position of the emergency shuttle";
|
||||
public string Help => $"{Command}";
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var tstalker = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<ShuttleSystem>();
|
||||
tstalker.EnableShuttlePosition ^= true;
|
||||
shell.WriteLine($"Set emergency shuttle debug to {tstalker.EnableShuttlePosition}");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
using Content.Shared.Shuttles.Events;
|
||||
using Content.Shared.Shuttles.Systems;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Enums;
|
||||
|
||||
namespace Content.Client.Shuttles.Systems;
|
||||
|
||||
public sealed partial class ShuttleSystem : SharedShuttleSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// Should we show the expected emergency shuttle position.
|
||||
/// </summary>
|
||||
public bool EnableShuttlePosition
|
||||
{
|
||||
get => _enableShuttlePosition;
|
||||
set
|
||||
{
|
||||
if (_enableShuttlePosition == value) return;
|
||||
|
||||
_enableShuttlePosition = value;
|
||||
var overlayManager = IoCManager.Resolve<IOverlayManager>();
|
||||
|
||||
if (_enableShuttlePosition)
|
||||
{
|
||||
_overlay = new EmergencyShuttleOverlay(EntityManager);
|
||||
overlayManager.AddOverlay(_overlay);
|
||||
RaiseNetworkEvent(new EmergencyShuttleRequestPositionMessage());
|
||||
}
|
||||
else
|
||||
{
|
||||
overlayManager.RemoveOverlay(_overlay!);
|
||||
_overlay = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool _enableShuttlePosition;
|
||||
private EmergencyShuttleOverlay? _overlay;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeNetworkEvent<EmergencyShuttlePositionMessage>(OnShuttlePosMessage);
|
||||
}
|
||||
|
||||
private void OnShuttlePosMessage(EmergencyShuttlePositionMessage ev)
|
||||
{
|
||||
if (_overlay == null) return;
|
||||
|
||||
_overlay.StationUid = ev.StationUid;
|
||||
_overlay.Position = ev.Position;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shows the expected position of the emergency shuttle. Nothing more.
|
||||
/// </summary>
|
||||
public sealed class EmergencyShuttleOverlay : Overlay
|
||||
{
|
||||
private IEntityManager _entManager;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
||||
|
||||
public EntityUid? StationUid;
|
||||
public Box2? Position;
|
||||
|
||||
public EmergencyShuttleOverlay(IEntityManager entManager)
|
||||
{
|
||||
_entManager = entManager;
|
||||
}
|
||||
|
||||
protected override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
if (Position == null || !_entManager.TryGetComponent<TransformComponent>(StationUid, out var xform)) return;
|
||||
|
||||
args.WorldHandle.SetTransform(xform.WorldMatrix);
|
||||
args.WorldHandle.DrawRect(Position.Value, Color.Red.WithAlpha(100));
|
||||
args.WorldHandle.SetTransform(Matrix3.Identity);
|
||||
}
|
||||
}
|
||||
41
Content.Client/Shuttles/UI/EmergencyConsoleWindow.xaml
Normal file
41
Content.Client/Shuttles/UI/EmergencyConsoleWindow.xaml
Normal file
@@ -0,0 +1,41 @@
|
||||
<userInterface:FancyWindow xmlns="https://spacestation14.io"
|
||||
xmlns:userInterface="clr-namespace:Content.Client.UserInterface"
|
||||
Title="Emergency Shuttle Console"
|
||||
MinSize="400 400">
|
||||
<BoxContainer Orientation="Vertical"
|
||||
Margin="5">
|
||||
<Label Name="Countdown"
|
||||
HorizontalAlignment="Center"
|
||||
Text="00:00"/>
|
||||
<BoxContainer Name="EngineStatusContainer"
|
||||
HorizontalAlignment="Center">
|
||||
<Label HorizontalAlignment="Center"
|
||||
Text="{Loc 'emergency-shuttle-ui-engines'}"/>
|
||||
<Label Name="EngineStatus"
|
||||
HorizontalAlignment="Center"
|
||||
FontColorOverride="#FFA500"
|
||||
Text="{Loc 'emergency-shuttle-ui-idle'}"/>
|
||||
</BoxContainer>
|
||||
<BoxContainer Name="LaunchContainer">
|
||||
<Label Text="{Loc 'emergency-shuttle-ui-early-authorize'}"/>
|
||||
<Button Name="RepealAllButton" Text="{Loc 'emergency-shuttle-ui-repeal-all'}"/>
|
||||
</BoxContainer>
|
||||
<!-- Spacer -->
|
||||
<BoxContainer Name="AuthorizeContainer">
|
||||
<Button Name="AuthorizeButton"
|
||||
Text="{Loc 'emergency-shuttle-ui-authorize'}"/>
|
||||
<Button Name="RepealButton" Text="{Loc 'emergency-shuttle-ui-repeal'}"/>
|
||||
</BoxContainer>
|
||||
<BoxContainer Name="AuthorizationsTextContainer">
|
||||
<Label Text="{Loc 'emergency-shuttle-ui-authorizations'}"/>
|
||||
<Label Name="AuthorizationCount"
|
||||
Text="{Loc 'emergency-shuttle-ui-remaining'}"
|
||||
HorizontalAlignment="Right"
|
||||
Align="Right"
|
||||
HorizontalExpand="True"/>
|
||||
</BoxContainer>
|
||||
<!-- Spacer -->
|
||||
<BoxContainer Name="AuthorizationsContainer" Orientation="Vertical">
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</userInterface:FancyWindow>
|
||||
93
Content.Client/Shuttles/UI/EmergencyConsoleWindow.xaml.cs
Normal file
93
Content.Client/Shuttles/UI/EmergencyConsoleWindow.xaml.cs
Normal file
@@ -0,0 +1,93 @@
|
||||
using Content.Client.Computer;
|
||||
using Content.Client.UserInterface;
|
||||
using Content.Shared.Shuttles.BUIStates;
|
||||
using Content.Shared.Shuttles.Events;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client.Shuttles.UI;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class EmergencyConsoleWindow : FancyWindow,
|
||||
IComputerWindow<EmergencyConsoleBoundUserInterfaceState>
|
||||
{
|
||||
private readonly IGameTiming _timing;
|
||||
private TimeSpan? _earlyLaunchTime;
|
||||
|
||||
public EmergencyConsoleWindow()
|
||||
{
|
||||
_timing = IoCManager.Resolve<IGameTiming>();
|
||||
RobustXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
public void SetupComputerWindow(ComputerBoundUserInterfaceBase cb)
|
||||
{
|
||||
RepealAllButton.OnPressed += args =>
|
||||
{
|
||||
OnRepealAllPressed(cb, args);
|
||||
};
|
||||
AuthorizeButton.OnPressed += args =>
|
||||
{
|
||||
OnAuthorizePressed(cb, args);
|
||||
};
|
||||
RepealButton.OnPressed += args =>
|
||||
{
|
||||
OnRepealPressed(cb, args);
|
||||
};
|
||||
}
|
||||
|
||||
private void OnRepealAllPressed(ComputerBoundUserInterfaceBase cb, BaseButton.ButtonEventArgs obj)
|
||||
{
|
||||
cb.SendMessage(new EmergencyShuttleRepealAllMessage());
|
||||
}
|
||||
|
||||
private void OnRepealPressed(ComputerBoundUserInterfaceBase cb, BaseButton.ButtonEventArgs obj)
|
||||
{
|
||||
cb.SendMessage(new EmergencyShuttleRepealMessage());
|
||||
}
|
||||
|
||||
private void OnAuthorizePressed(ComputerBoundUserInterfaceBase cb, BaseButton.ButtonEventArgs obj)
|
||||
{
|
||||
cb.SendMessage(new EmergencyShuttleAuthorizeMessage());
|
||||
}
|
||||
|
||||
public void UpdateState(EmergencyConsoleBoundUserInterfaceState scc)
|
||||
{
|
||||
// TODO: Loc and cvar for this.
|
||||
_earlyLaunchTime = scc.EarlyLaunchTime;
|
||||
|
||||
AuthorizationsContainer.DisposeAllChildren();
|
||||
var remainingAuths = scc.AuthorizationsRequired - scc.Authorizations.Count;
|
||||
AuthorizationCount.Text = Loc.GetString("emergency-shuttle-ui-remaining", ("remaining", remainingAuths));
|
||||
|
||||
foreach (var auth in scc.Authorizations)
|
||||
{
|
||||
AuthorizationsContainer.AddChild(new Label
|
||||
{
|
||||
Text = auth,
|
||||
FontColorOverride = Color.Lime,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Draw(DrawingHandleScreen handle)
|
||||
{
|
||||
base.Draw(handle);
|
||||
if (_earlyLaunchTime == null)
|
||||
{
|
||||
Countdown.Text = "00:10";
|
||||
}
|
||||
else
|
||||
{
|
||||
var remaining = _earlyLaunchTime.Value - _timing.CurTime;
|
||||
|
||||
if (remaining < TimeSpan.Zero)
|
||||
remaining = TimeSpan.Zero;
|
||||
|
||||
Countdown.Text = $"{remaining.Minutes:00}:{remaining.Seconds:00}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -45,7 +45,7 @@ public sealed class RadarControl : Control
|
||||
/// </summary>
|
||||
private Dictionary<EntityUid, Control> _iffControls = new();
|
||||
|
||||
private Dictionary<EntityUid, Dictionary<EntityUid, DockingInterfaceState>> _docks = new();
|
||||
private Dictionary<EntityUid, List<DockingInterfaceState>> _docks = new();
|
||||
|
||||
public bool ShowIFF { get; set; } = true;
|
||||
public bool ShowDocks { get; set; } = true;
|
||||
@@ -88,7 +88,7 @@ public sealed class RadarControl : Control
|
||||
{
|
||||
var coordinates = state.Coordinates;
|
||||
var grid = _docks.GetOrNew(coordinates.EntityId);
|
||||
grid.Add(state.Entity, state);
|
||||
grid.Add(state);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -288,8 +288,9 @@ public sealed class RadarControl : Control
|
||||
|
||||
if (_docks.TryGetValue(uid, out var docks))
|
||||
{
|
||||
foreach (var (ent, state) in docks)
|
||||
foreach (var state in docks)
|
||||
{
|
||||
var ent = state.Entity;
|
||||
var position = state.Coordinates.Position;
|
||||
var uiPosition = matrix.Transform(position);
|
||||
|
||||
|
||||
@@ -103,7 +103,7 @@ namespace Content.IntegrationTests.Tests
|
||||
|
||||
async Task WaitForEvent()
|
||||
{
|
||||
var timeout = Task.Delay(TimeSpan.FromSeconds(10));
|
||||
var timeout = Task.Delay(TimeSpan.FromSeconds(60));
|
||||
var currentCount = Thread.VolatileRead(ref eventCount);
|
||||
while (currentCount == Thread.VolatileRead(ref eventCount) && !timeout.IsCompleted)
|
||||
{
|
||||
|
||||
@@ -8,14 +8,11 @@ using Robust.Shared.Prototypes;
|
||||
namespace Content.Server.AI.Components
|
||||
{
|
||||
[RegisterComponent]
|
||||
[ComponentReference(typeof(IMobMoverComponent))]
|
||||
[Virtual]
|
||||
public class AiControllerComponent : Component, IMobMoverComponent, IMoverComponent
|
||||
public class AiControllerComponent : Component
|
||||
{
|
||||
[DataField("logic")] private float _visionRadius = 8.0f;
|
||||
|
||||
public bool CanMove { get; set; } = true;
|
||||
|
||||
// TODO: Need to ECS a lot more of the AI first before we can ECS this
|
||||
/// <summary>
|
||||
/// Whether the AI is actively iterated.
|
||||
@@ -46,59 +43,6 @@ namespace Content.Server.AI.Components
|
||||
set => _visionRadius = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
// This component requires a physics component.
|
||||
Owner.EnsureComponent<PhysicsComponent>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Movement speed (m/s) that the entity walks, after modifiers
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public float CurrentWalkSpeed
|
||||
{
|
||||
get
|
||||
{
|
||||
if (IoCManager.Resolve<IEntityManager>().TryGetComponent(Owner, out MovementSpeedModifierComponent? component))
|
||||
{
|
||||
return component.CurrentWalkSpeed;
|
||||
}
|
||||
|
||||
return MovementSpeedModifierComponent.DefaultBaseWalkSpeed;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Movement speed (m/s) that the entity walks, after modifiers
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public float CurrentSprintSpeed
|
||||
{
|
||||
get
|
||||
{
|
||||
if (IoCManager.Resolve<IEntityManager>().TryGetComponent(Owner, out MovementSpeedModifierComponent? component))
|
||||
{
|
||||
return component.CurrentSprintSpeed;
|
||||
}
|
||||
|
||||
return MovementSpeedModifierComponent.DefaultBaseSprintSpeed;
|
||||
}
|
||||
}
|
||||
|
||||
public Angle LastGridAngle { get => Angle.Zero; set {} }
|
||||
|
||||
/// <inheritdoc />
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public float PushStrength { get; set; } = IMobMoverComponent.PushStrengthDefault;
|
||||
|
||||
/// <inheritdoc />
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public float GrabRange { get; set; } = IMobMoverComponent.GrabRangeDefault;
|
||||
|
||||
/// <summary>
|
||||
/// Is the entity Sprinting (running)?
|
||||
/// </summary>
|
||||
@@ -111,17 +55,6 @@ namespace Content.Server.AI.Components
|
||||
[ViewVariables]
|
||||
public Vector2 VelocityDir { get; set; }
|
||||
|
||||
(Vector2 walking, Vector2 sprinting) IMoverComponent.VelocityDir =>
|
||||
Sprinting ? (Vector2.Zero, VelocityDir) : (VelocityDir, Vector2.Zero);
|
||||
|
||||
public EntityCoordinates LastPosition { get; set; }
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public float StepSoundDistance { get; set; }
|
||||
|
||||
public void SetVelocityDirection(Direction direction, ushort subTick, bool enabled) { }
|
||||
public void SetSprinting(ushort subTick, bool walking) { }
|
||||
|
||||
public virtual void Update(float frameTime) {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,8 +8,10 @@ using Content.Server.CPUJob.JobQueues;
|
||||
using Content.Shared.Access.Systems;
|
||||
using Content.Shared.Doors.Components;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Movement.Components;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.AI.Steering
|
||||
@@ -17,6 +19,7 @@ namespace Content.Server.AI.Steering
|
||||
public sealed class AiSteeringSystem : EntitySystem
|
||||
{
|
||||
// http://www.red3d.com/cwr/papers/1999/gdc99steer.html for a steering overview
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly PathfindingSystem _pathfindingSystem = default!;
|
||||
[Dependency] private readonly AccessReaderSystem _accessReader = default!;
|
||||
@@ -120,9 +123,9 @@ namespace Content.Server.AI.Steering
|
||||
/// <exception cref="InvalidOperationException"></exception>
|
||||
public void Unregister(EntityUid entity)
|
||||
{
|
||||
if (EntityManager.TryGetComponent(entity, out AiControllerComponent? controller))
|
||||
if (EntityManager.TryGetComponent(entity, out SharedPlayerInputMoverComponent? controller))
|
||||
{
|
||||
controller.VelocityDir = Vector2.Zero;
|
||||
controller.CurTickSprintMovement = Vector2.Zero;
|
||||
}
|
||||
|
||||
if (_pathfindingRequests.TryGetValue(entity, out var request))
|
||||
@@ -228,6 +231,13 @@ namespace Content.Server.AI.Steering
|
||||
_listIndex = (_listIndex + 1) % _agentLists.Count;
|
||||
}
|
||||
|
||||
private void SetDirection(SharedPlayerInputMoverComponent component, Vector2 value)
|
||||
{
|
||||
component.CurTickSprintMovement = value;
|
||||
component._lastInputTick = _timing.CurTick;
|
||||
component._lastInputSubTick = ushort.MaxValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Go through each steerer and combine their vectors
|
||||
/// </summary>
|
||||
@@ -240,7 +250,7 @@ namespace Content.Server.AI.Steering
|
||||
{
|
||||
// Main optimisation to be done below is the redundant calls and adding more variables
|
||||
if (Deleted(entity) ||
|
||||
!EntityManager.TryGetComponent(entity, out AiControllerComponent? controller) ||
|
||||
!EntityManager.TryGetComponent(entity, out SharedPlayerInputMoverComponent? controller) ||
|
||||
!controller.CanMove ||
|
||||
!TryComp(entity, out TransformComponent? xform) ||
|
||||
xform.GridUid == null)
|
||||
@@ -252,13 +262,13 @@ namespace Content.Server.AI.Steering
|
||||
|
||||
if (entitySteering != null && (!EntityManager.EntityExists(entitySteering.Target) ? EntityLifeStage.Deleted : EntityManager.GetComponent<MetaDataComponent>(entitySteering.Target).EntityLifeStage) >= EntityLifeStage.Deleted)
|
||||
{
|
||||
controller.VelocityDir = Vector2.Zero;
|
||||
controller.CurTickSprintMovement = Vector2.Zero;
|
||||
return SteeringStatus.NoPath;
|
||||
}
|
||||
|
||||
if (_mapManager.IsGridPaused(xform.GridUid.Value))
|
||||
{
|
||||
controller.VelocityDir = Vector2.Zero;
|
||||
SetDirection(controller, Vector2.Zero);
|
||||
return SteeringStatus.Pending;
|
||||
}
|
||||
|
||||
@@ -266,7 +276,7 @@ namespace Content.Server.AI.Steering
|
||||
// Check if we can even arrive -> Currently only samegrid movement supported
|
||||
if (xform.GridUid != steeringRequest.TargetGrid.GetGridUid(EntityManager))
|
||||
{
|
||||
controller.VelocityDir = Vector2.Zero;
|
||||
SetDirection(controller, Vector2.Zero);
|
||||
return SteeringStatus.NoPath;
|
||||
}
|
||||
|
||||
@@ -280,7 +290,7 @@ namespace Content.Server.AI.Steering
|
||||
_interactionSystem.InRangeUnobstructed(entity, steeringRequest.TargetMap, steeringRequest.ArrivalDistance, popup: true))
|
||||
{
|
||||
// TODO: Need cruder LOS checks for ranged weaps
|
||||
controller.VelocityDir = Vector2.Zero;
|
||||
SetDirection(controller, Vector2.Zero);
|
||||
return SteeringStatus.Arrived;
|
||||
}
|
||||
|
||||
@@ -291,7 +301,7 @@ namespace Content.Server.AI.Steering
|
||||
// If we're really close don't swiggity swoogity back and forth and just wait for the interaction check maybe?
|
||||
if (steeringRequest.TimeUntilInteractionCheck > 0.0f && targetDistance <= 0.1f)
|
||||
{
|
||||
controller.VelocityDir = Vector2.Zero;
|
||||
SetDirection(controller, Vector2.Zero);
|
||||
return SteeringStatus.Moving;
|
||||
}
|
||||
|
||||
@@ -305,7 +315,7 @@ namespace Content.Server.AI.Steering
|
||||
break;
|
||||
// Currently nothing should be cancelling these except external factors
|
||||
case TaskCanceledException _:
|
||||
controller.VelocityDir = Vector2.Zero;
|
||||
SetDirection(controller, Vector2.Zero);
|
||||
return SteeringStatus.NoPath;
|
||||
default:
|
||||
throw pathRequest.Job.Exception;
|
||||
@@ -314,7 +324,7 @@ namespace Content.Server.AI.Steering
|
||||
var path = _pathfindingRequests[entity].Job.Result;
|
||||
if (path == null || path.Count == 0)
|
||||
{
|
||||
controller.VelocityDir = Vector2.Zero;
|
||||
SetDirection(controller, Vector2.Zero);
|
||||
return SteeringStatus.NoPath;
|
||||
}
|
||||
|
||||
@@ -335,7 +345,7 @@ namespace Content.Server.AI.Steering
|
||||
// If the route's empty we could be close and may not need a re-path so we won't check if it is
|
||||
if (!_paths.ContainsKey(entity) && !_pathfindingRequests.ContainsKey(entity) && targetDistance > 1.5f)
|
||||
{
|
||||
controller.VelocityDir = Vector2.Zero;
|
||||
SetDirection(controller, Vector2.Zero);
|
||||
RequestPath(entity, steeringRequest);
|
||||
return SteeringStatus.Pending;
|
||||
}
|
||||
@@ -365,14 +375,14 @@ namespace Content.Server.AI.Steering
|
||||
var nextGrid = NextGrid(entity, steeringRequest);
|
||||
if (!nextGrid.HasValue)
|
||||
{
|
||||
controller.VelocityDir = Vector2.Zero;
|
||||
SetDirection(controller, Vector2.Zero);
|
||||
return SteeringStatus.NoPath;
|
||||
}
|
||||
|
||||
// Validate that we can even get to the next grid (could probably just check if we can use nextTile if we're not near the target grid)
|
||||
if (!_pathfindingSystem.CanTraverse(entity, nextGrid.Value))
|
||||
{
|
||||
controller.VelocityDir = Vector2.Zero;
|
||||
SetDirection(controller, Vector2.Zero);
|
||||
return SteeringStatus.NoPath;
|
||||
}
|
||||
|
||||
@@ -392,7 +402,7 @@ namespace Content.Server.AI.Steering
|
||||
|
||||
// Move towards it
|
||||
DebugTools.Assert(movementVector != new Vector2(float.NaN, float.NaN));
|
||||
controller.VelocityDir = movementVector.Normalized;
|
||||
SetDirection(controller, movementVector.Normalized);
|
||||
return SteeringStatus.Moving;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace Content.Server.AI.Utility.AiLogic
|
||||
// TODO: Need to split out the IMover stuff for NPC to a generic one that can be used for hoomans as well.
|
||||
[RegisterComponent]
|
||||
[ComponentProtoName("UtilityAI")]
|
||||
[ComponentReference(typeof(AiControllerComponent)), ComponentReference(typeof(IMoverComponent))]
|
||||
[ComponentReference(typeof(AiControllerComponent))]
|
||||
public sealed class UtilityAi : AiControllerComponent
|
||||
{
|
||||
// TODO: Look at having ParallelOperators (probably no more than that as then you'd have a full-blown BT)
|
||||
|
||||
@@ -57,5 +57,10 @@ public sealed class AlertLevelDetail
|
||||
/// The color that this alert level will show in-game in chat.
|
||||
/// </summary>
|
||||
[DataField("color")] public Color Color { get; } = Color.White;
|
||||
|
||||
/// <summary>
|
||||
/// How long it takes for the shuttle to arrive when called.
|
||||
/// </summary>
|
||||
[DataField("shuttleTime")] public TimeSpan ShuttleTime { get; } = TimeSpan.FromMinutes(5);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ using Content.Server.Chat;
|
||||
using Content.Server.Chat.Systems;
|
||||
using Content.Server.Popups;
|
||||
using Content.Server.RoundEnd;
|
||||
using Content.Server.Shuttles.Systems;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Shared.Access.Components;
|
||||
using Content.Shared.Access.Systems;
|
||||
@@ -15,13 +16,14 @@ namespace Content.Server.Communications
|
||||
{
|
||||
public sealed class CommunicationsConsoleSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly RoundEndSystem _roundEndSystem = default!;
|
||||
[Dependency] private readonly AlertLevelSystem _alertLevelSystem = default!;
|
||||
[Dependency] private readonly StationSystem _stationSystem = default!;
|
||||
[Dependency] private readonly IdCardSystem _idCardSystem = default!;
|
||||
[Dependency] private readonly AccessReaderSystem _accessReaderSystem = default!;
|
||||
[Dependency] private readonly PopupSystem _popupSystem = default!;
|
||||
[Dependency] private readonly AlertLevelSystem _alertLevelSystem = default!;
|
||||
[Dependency] private readonly ChatSystem _chatSystem = default!;
|
||||
[Dependency] private readonly IdCardSystem _idCardSystem = default!;
|
||||
[Dependency] private readonly PopupSystem _popupSystem = default!;
|
||||
[Dependency] private readonly RoundEndSystem _roundEndSystem = default!;
|
||||
[Dependency] private readonly ShuttleSystem _shuttle = default!;
|
||||
[Dependency] private readonly StationSystem _stationSystem = default!;
|
||||
|
||||
private const int MaxMessageLength = 256;
|
||||
|
||||
@@ -29,7 +31,7 @@ namespace Content.Server.Communications
|
||||
{
|
||||
// All events that refresh the BUI
|
||||
SubscribeLocalEvent<AlertLevelChangedEvent>(OnAlertLevelChanged);
|
||||
SubscribeLocalEvent<CommunicationsConsoleComponent, ComponentInit>((_, comp, _) => UpdateBoundUserInterface(comp));
|
||||
SubscribeLocalEvent<CommunicationsConsoleComponent, ComponentInit>((_, comp, _) => UpdateCommsConsoleInterface(comp));
|
||||
SubscribeLocalEvent<RoundEndSystemChangedEvent>(_ => OnGenericBroadcastEvent());
|
||||
SubscribeLocalEvent<AlertLevelDelayFinishedEvent>(_ => OnGenericBroadcastEvent());
|
||||
|
||||
@@ -48,7 +50,7 @@ namespace Content.Server.Communications
|
||||
if (comp.AlreadyRefreshed) continue;
|
||||
if (comp.AnnouncementCooldownRemaining <= 0f)
|
||||
{
|
||||
UpdateBoundUserInterface(comp);
|
||||
UpdateCommsConsoleInterface(comp);
|
||||
comp.AlreadyRefreshed = true;
|
||||
continue;
|
||||
}
|
||||
@@ -65,7 +67,7 @@ namespace Content.Server.Communications
|
||||
{
|
||||
foreach (var comp in EntityQuery<CommunicationsConsoleComponent>())
|
||||
{
|
||||
UpdateBoundUserInterface(comp);
|
||||
UpdateCommsConsoleInterface(comp);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,17 +77,32 @@ namespace Content.Server.Communications
|
||||
/// <param name="args">Alert level changed event arguments</param>
|
||||
private void OnAlertLevelChanged(AlertLevelChangedEvent args)
|
||||
{
|
||||
foreach (var comp in EntityQuery<CommunicationsConsoleComponent>())
|
||||
foreach (var comp in EntityQuery<CommunicationsConsoleComponent>(true))
|
||||
{
|
||||
var entStation = _stationSystem.GetOwningStation(comp.Owner);
|
||||
if (args.Station == entStation)
|
||||
{
|
||||
UpdateBoundUserInterface(comp);
|
||||
UpdateCommsConsoleInterface(comp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateBoundUserInterface(CommunicationsConsoleComponent comp)
|
||||
/// <summary>
|
||||
/// Updates the UI for all comms consoles.
|
||||
/// </summary>
|
||||
public void UpdateCommsConsoleInterface()
|
||||
{
|
||||
foreach (var comp in EntityQuery<CommunicationsConsoleComponent>())
|
||||
{
|
||||
UpdateCommsConsoleInterface(comp);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the UI for a particular comms console.
|
||||
/// </summary>
|
||||
/// <param name="comp"></param>
|
||||
public void UpdateCommsConsoleInterface(CommunicationsConsoleComponent comp)
|
||||
{
|
||||
var uid = comp.Owner;
|
||||
|
||||
@@ -144,6 +161,8 @@ namespace Content.Server.Communications
|
||||
|
||||
private bool CanCall(CommunicationsConsoleComponent comp)
|
||||
{
|
||||
if (_shuttle.EmergencyShuttleArrived) return false;
|
||||
|
||||
return comp.CanCallShuttle && _roundEndSystem.CanCall();
|
||||
}
|
||||
|
||||
@@ -189,7 +208,7 @@ namespace Content.Server.Communications
|
||||
|
||||
comp.AnnouncementCooldownRemaining = comp.DelayBetweenAnnouncements;
|
||||
comp.AlreadyRefreshed = false;
|
||||
UpdateBoundUserInterface(comp);
|
||||
UpdateCommsConsoleInterface(comp);
|
||||
|
||||
// allow admemes with vv
|
||||
Loc.TryGetString(comp.AnnouncementDisplayName, out var title);
|
||||
@@ -206,7 +225,7 @@ namespace Content.Server.Communications
|
||||
|
||||
private void OnCallShuttleMessage(EntityUid uid, CommunicationsConsoleComponent comp, CommunicationsConsoleCallEmergencyShuttleMessage message)
|
||||
{
|
||||
if (!comp.CanCallShuttle) return;
|
||||
if (!CanCall(comp)) return;
|
||||
if (message.Session.AttachedEntity is not {Valid: true} mob) return;
|
||||
if (!CanUse(mob, uid))
|
||||
{
|
||||
@@ -218,7 +237,7 @@ namespace Content.Server.Communications
|
||||
|
||||
private void OnRecallShuttleMessage(EntityUid uid, CommunicationsConsoleComponent comp, CommunicationsConsoleRecallEmergencyShuttleMessage message)
|
||||
{
|
||||
if (!comp.CanCallShuttle) return;
|
||||
if (!CanCall(comp)) return;
|
||||
if (message.Session.AttachedEntity is not {Valid: true} mob) return;
|
||||
if (!CanUse(mob, uid))
|
||||
{
|
||||
|
||||
@@ -154,7 +154,7 @@ namespace Content.Server.Doors.Components
|
||||
|
||||
BoltsDown = newBolts;
|
||||
|
||||
SoundSystem.Play(newBolts ? BoltDownSound.GetSound() : BoltUpSound.GetSound(), Filter.Broadcast(), Owner);
|
||||
SoundSystem.Play(newBolts ? BoltDownSound.GetSound() : BoltUpSound.GetSound(), Filter.Pvs(Owner), Owner);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,9 +78,10 @@ namespace Content.Server.Physics.Controllers
|
||||
_excludedMobs.Add(mover.Owner);
|
||||
|
||||
var gridId = xform.GridUid;
|
||||
// This tries to see if the grid is a shuttle
|
||||
// This tries to see if the grid is a shuttle and if the console should work.
|
||||
if (!_mapManager.TryGetGrid(gridId, out var grid) ||
|
||||
!EntityManager.TryGetComponent(grid.GridEntityId, out ShuttleComponent? shuttleComponent)) continue;
|
||||
!EntityManager.TryGetComponent(grid.GridEntityId, out ShuttleComponent? shuttleComponent) ||
|
||||
!shuttleComponent.Enabled) continue;
|
||||
|
||||
if (!newPilots.TryGetValue(shuttleComponent, out var pilots))
|
||||
{
|
||||
|
||||
@@ -1,29 +1,42 @@
|
||||
using System.Threading;
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Server.AlertLevel;
|
||||
using Content.Server.Chat;
|
||||
using Content.Server.Chat.Managers;
|
||||
using Content.Server.Chat.Systems;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.Shuttles.Systems;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.GameTicking;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
using Timer = Robust.Shared.Timing.Timer;
|
||||
|
||||
namespace Content.Server.RoundEnd
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles ending rounds normally and also via requesting it (e.g. via comms console)
|
||||
/// If you request a round end then an escape shuttle will be used.
|
||||
/// </summary>
|
||||
public sealed class RoundEndSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
|
||||
[Dependency] private readonly IChatManager _chatManager = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly IPrototypeManager _protoManager = default!;
|
||||
[Dependency] private readonly ChatSystem _chatSystem = default!;
|
||||
[Dependency] private readonly GameTicker _gameTicker = default!;
|
||||
|
||||
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
|
||||
|
||||
[Dependency] private readonly ShuttleSystem _shuttle = default!;
|
||||
[Dependency] private readonly StationSystem _stationSystem = default!;
|
||||
|
||||
public TimeSpan DefaultCooldownDuration { get; set; } = TimeSpan.FromSeconds(30);
|
||||
|
||||
/// <summary>
|
||||
/// Countdown to use where there is no station alert countdown to be found.
|
||||
/// </summary>
|
||||
public TimeSpan DefaultCountdownDuration { get; set; } = TimeSpan.FromMinutes(4);
|
||||
public TimeSpan DefaultRestartRoundDuration { get; set; } = TimeSpan.FromMinutes(1);
|
||||
|
||||
@@ -62,7 +75,20 @@ namespace Content.Server.RoundEnd
|
||||
|
||||
public void RequestRoundEnd(EntityUid? requester = null, bool checkCooldown = true)
|
||||
{
|
||||
RequestRoundEnd(DefaultCountdownDuration, requester, checkCooldown);
|
||||
var duration = DefaultCountdownDuration;
|
||||
|
||||
if (requester != null)
|
||||
{
|
||||
var stationUid = _stationSystem.GetOwningStation(requester.Value);
|
||||
if (TryComp<AlertLevelComponent>(stationUid, out var alertLevel))
|
||||
{
|
||||
duration = _protoManager
|
||||
.Index<AlertLevelPrototype>(AlertLevelSystem.DefaultAlertLevelSet)
|
||||
.Levels[alertLevel.CurrentLevel].ShuttleTime;
|
||||
}
|
||||
}
|
||||
|
||||
RequestRoundEnd(duration, requester, checkCooldown);
|
||||
}
|
||||
|
||||
public void RequestRoundEnd(TimeSpan countdownTime, EntityUid? requester = null, bool checkCooldown = true)
|
||||
@@ -83,12 +109,32 @@ namespace Content.Server.RoundEnd
|
||||
_adminLogger.Add(LogType.ShuttleCalled, LogImpact.High, $"Shuttle called");
|
||||
}
|
||||
|
||||
_chatSystem.DispatchGlobalStationAnnouncement(Loc.GetString("round-end-system-shuttle-called-announcement",("minutes", countdownTime.Minutes)), Loc.GetString("Station"), false, Color.Gold);
|
||||
// I originally had these set up here but somehow time gets passed as 0 to Loc so IDEK.
|
||||
int time;
|
||||
string units;
|
||||
|
||||
if (countdownTime.TotalSeconds < 60)
|
||||
{
|
||||
time = countdownTime.Seconds;
|
||||
units = "seconds";
|
||||
}
|
||||
else
|
||||
{
|
||||
time = countdownTime.Minutes;
|
||||
units = "minutes";
|
||||
}
|
||||
|
||||
_chatSystem.DispatchGlobalStationAnnouncement(Loc.GetString("round-end-system-shuttle-called-announcement",
|
||||
("time", time),
|
||||
("units", units)),
|
||||
Loc.GetString("Station"),
|
||||
false,
|
||||
Color.Gold);
|
||||
|
||||
SoundSystem.Play("/Audio/Announcements/shuttlecalled.ogg", Filter.Broadcast());
|
||||
|
||||
ExpectedCountdownEnd = _gameTiming.CurTime + countdownTime;
|
||||
Timer.Spawn(countdownTime, EndRound, _countdownTokenSource.Token);
|
||||
Timer.Spawn(countdownTime, _shuttle.CallEmergencyShuttle, _countdownTokenSource.Token);
|
||||
|
||||
ActivateCooldown();
|
||||
RaiseLocalEvent(RoundEndSystemChangedEvent.Default);
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
using Content.Server.Administration;
|
||||
using Content.Server.Shuttles.Systems;
|
||||
using Content.Shared.Administration;
|
||||
using Robust.Shared.Console;
|
||||
|
||||
namespace Content.Server.Shuttles.Commands;
|
||||
|
||||
/// <summary>
|
||||
/// Delays the round from ending via the shuttle call. Can still be ended via other means.
|
||||
/// </summary>
|
||||
[AdminCommand(AdminFlags.Fun)]
|
||||
public sealed class DelayRoundEndCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "delayroundend";
|
||||
public string Description => Loc.GetString("emergency-shuttle-command-round-desc");
|
||||
public string Help => $"{Command}";
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var system = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<ShuttleSystem>();
|
||||
if (system.DelayEmergencyRoundEnd())
|
||||
{
|
||||
shell.WriteLine(Loc.GetString("emergency-shuttle-command-round-yes"));
|
||||
}
|
||||
else
|
||||
{
|
||||
shell.WriteLine(Loc.GetString("emergency-shuttle-command-round-no"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using Content.Server.Administration;
|
||||
using Content.Server.Shuttles.Systems;
|
||||
using Content.Shared.Administration;
|
||||
using Robust.Shared.Console;
|
||||
|
||||
namespace Content.Server.Shuttles.Commands;
|
||||
|
||||
/// <summary>
|
||||
/// Calls in the emergency shuttle.
|
||||
/// </summary>
|
||||
[AdminCommand(AdminFlags.Fun)]
|
||||
public sealed class DockEmergencyShuttleCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "dockemergencyshuttle";
|
||||
public string Description => Loc.GetString("emergency-shuttle-command-dock-desc");
|
||||
public string Help => $"{Command}";
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var system = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<ShuttleSystem>();
|
||||
system.CallEmergencyShuttle();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using Content.Server.Administration;
|
||||
using Content.Server.Shuttles.Systems;
|
||||
using Content.Shared.Administration;
|
||||
using Robust.Shared.Console;
|
||||
|
||||
namespace Content.Server.Shuttles.Commands;
|
||||
|
||||
/// <summary>
|
||||
/// Early launches in the emergency shuttle.
|
||||
/// </summary>
|
||||
[AdminCommand(AdminFlags.Fun)]
|
||||
public sealed class LaunchEmergencyShuttleCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "launchemergencyshuttle";
|
||||
public string Description => Loc.GetString("emergency-shuttle-command-launch-desc");
|
||||
public string Help => $"{Command}";
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var system = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<ShuttleSystem>();
|
||||
system.EarlyLaunch();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Content.Server.Shuttles.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Given priority when considering where to dock an emergency shuttle.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class EmergencyDockComponent : Component {}
|
||||
@@ -0,0 +1,16 @@
|
||||
namespace Content.Server.Shuttles.Components;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed class EmergencyShuttleConsoleComponent : Component
|
||||
{
|
||||
// TODO: Okay doing it by string is kinda suss but also ID card tracking doesn't seem to be robust enough
|
||||
|
||||
/// <summary>
|
||||
/// ID cards that have been used to authorize an early launch.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("authorized")]
|
||||
public HashSet<string> AuthorizedEntities = new();
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("authorizationsRequired")]
|
||||
public int AuthorizationsRequired = 3;
|
||||
}
|
||||
31
Content.Server/Shuttles/Components/HyperspaceComponent.cs
Normal file
31
Content.Server/Shuttles/Components/HyperspaceComponent.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
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;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("targetCoordinates")]
|
||||
public EntityCoordinates TargetCoordinates;
|
||||
}
|
||||
|
||||
public enum HyperspaceState : byte
|
||||
{
|
||||
Starting,
|
||||
Travelling,
|
||||
}
|
||||
@@ -5,6 +5,12 @@ 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;
|
||||
|
||||
|
||||
@@ -5,6 +5,12 @@ 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();
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
using Content.Server.Shuttles.Components;
|
||||
|
||||
namespace Content.Server.Shuttles.Events;
|
||||
|
||||
/// <summary>
|
||||
/// Raised when <see cref="EmergencyShuttleConsoleComponent"/> has authorized successfully.
|
||||
/// </summary>
|
||||
public sealed class EmergencyShuttleAuthorizedEvent {}
|
||||
@@ -401,14 +401,10 @@ namespace Content.Server.Shuttles.Systems
|
||||
Dock(dockA, dockB);
|
||||
}
|
||||
|
||||
private void Undock(DockingComponent dock)
|
||||
public void Undock(DockingComponent dock)
|
||||
{
|
||||
if (dock.DockedWith == null)
|
||||
{
|
||||
DebugTools.Assert(false);
|
||||
_sawmill.Error($"Tried to undock {(dock).Owner} but not docked with anything?");
|
||||
return;
|
||||
}
|
||||
|
||||
if (TryComp(dock.Owner, out DoorComponent? doorA))
|
||||
{
|
||||
|
||||
@@ -15,12 +15,14 @@ using Content.Shared.Shuttles.Systems;
|
||||
using Content.Shared.Tag;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Shuttles.Systems
|
||||
{
|
||||
public sealed class ShuttleConsoleSystem : SharedShuttleConsoleSystem
|
||||
{
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly ActionBlockerSystem _blocker = default!;
|
||||
[Dependency] private readonly AlertsSystem _alertsSystem = default!;
|
||||
[Dependency] private readonly TagSystem _tags = default!;
|
||||
@@ -86,7 +88,7 @@ namespace Content.Server.Shuttles.Systems
|
||||
|
||||
private void OnConsoleUIOpenAttempt(EntityUid uid, ShuttleConsoleComponent component, ActivatableUIOpenAttemptEvent args)
|
||||
{
|
||||
if (!TryPilot(args.User, uid))
|
||||
if (!component.CanPilot || !TryPilot(args.User, uid))
|
||||
args.Cancel();
|
||||
}
|
||||
|
||||
@@ -233,6 +235,7 @@ 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;
|
||||
|
||||
docks ??= GetAllDocks();
|
||||
|
||||
@@ -0,0 +1,263 @@
|
||||
using System.Threading;
|
||||
using Content.Server.Popups;
|
||||
using Content.Server.RoundEnd;
|
||||
using Content.Server.Shuttles.Components;
|
||||
using Content.Server.Shuttles.Events;
|
||||
using Content.Server.Station.Components;
|
||||
using Content.Shared.Access.Systems;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Shuttles.BUIStates;
|
||||
using Content.Shared.Shuttles.Events;
|
||||
using Content.Shared.Shuttles.Systems;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Timing;
|
||||
using Timer = Robust.Shared.Timing.Timer;
|
||||
|
||||
namespace Content.Server.Shuttles.Systems;
|
||||
|
||||
public sealed partial class ShuttleSystem
|
||||
{
|
||||
/*
|
||||
* Handles the emergency shuttle's console and early launching.
|
||||
*/
|
||||
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly AccessReaderSystem _reader = default!;
|
||||
[Dependency] private readonly PopupSystem _popup = default!;
|
||||
[Dependency] private readonly RoundEndSystem _roundEnd = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Has the emergency shuttle arrived?
|
||||
/// </summary>
|
||||
public bool EmergencyShuttleArrived { get; private set; }
|
||||
|
||||
public bool EarlyLaunchAuthorized { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// How much time remaining until the shuttle consoles for emergency shuttles are unlocked?
|
||||
/// </summary>
|
||||
private float _consoleAccumulator;
|
||||
|
||||
/// <summary>
|
||||
/// How long after the transit is over to end the round.
|
||||
/// </summary>
|
||||
private readonly TimeSpan _bufferTime = TimeSpan.FromSeconds(3);
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="CCVars.EmergencyShuttleTransitTime"/>
|
||||
/// </summary>
|
||||
private float _transitTime;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="CCVars.EmergencyShuttleAuthorizeTime"/>
|
||||
/// </summary>
|
||||
private float _authorizeTime;
|
||||
|
||||
private CancellationTokenSource? _roundEndCancelToken;
|
||||
|
||||
private const string EmergencyRepealAllAccess = "EmergencyShuttleRepealAll";
|
||||
|
||||
/// <summary>
|
||||
/// Have the emergency shuttles been authorised to launch at Centcomm?
|
||||
/// </summary>
|
||||
private bool _launchedShuttles;
|
||||
|
||||
private void InitializeEmergencyConsole()
|
||||
{
|
||||
_configManager.OnValueChanged(CCVars.EmergencyShuttleTransitTime, SetTransitTime, true);
|
||||
_configManager.OnValueChanged(CCVars.EmergencyShuttleAuthorizeTime, SetAuthorizeTime, true);
|
||||
SubscribeLocalEvent<EmergencyShuttleConsoleComponent, ComponentStartup>(OnEmergencyStartup);
|
||||
SubscribeLocalEvent<EmergencyShuttleConsoleComponent, EmergencyShuttleAuthorizeMessage>(OnEmergencyAuthorize);
|
||||
SubscribeLocalEvent<EmergencyShuttleConsoleComponent, EmergencyShuttleRepealMessage>(OnEmergencyRepeal);
|
||||
SubscribeLocalEvent<EmergencyShuttleConsoleComponent, EmergencyShuttleRepealAllMessage>(OnEmergencyRepealAll);
|
||||
}
|
||||
|
||||
private void SetAuthorizeTime(float obj)
|
||||
{
|
||||
_authorizeTime = obj;
|
||||
}
|
||||
|
||||
private void SetTransitTime(float obj)
|
||||
{
|
||||
_transitTime = obj;
|
||||
}
|
||||
|
||||
private void ShutdownEmergencyConsole()
|
||||
{
|
||||
_configManager.UnsubValueChanged(CCVars.EmergencyShuttleAuthorizeTime, SetAuthorizeTime);
|
||||
_configManager.UnsubValueChanged(CCVars.EmergencyShuttleTransitTime, SetTransitTime);
|
||||
}
|
||||
|
||||
private void OnEmergencyStartup(EntityUid uid, EmergencyShuttleConsoleComponent component, ComponentStartup args)
|
||||
{
|
||||
UpdateConsoleState(uid, component);
|
||||
}
|
||||
|
||||
private void UpdateEmergencyConsole(float frameTime)
|
||||
{
|
||||
if (_consoleAccumulator <= 0f) return;
|
||||
|
||||
_consoleAccumulator -= frameTime;
|
||||
|
||||
if (!_launchedShuttles && _consoleAccumulator <= DefaultStartupTime)
|
||||
{
|
||||
_launchedShuttles = true;
|
||||
|
||||
if (_centcommMap != null)
|
||||
{
|
||||
foreach (var comp in EntityQuery<StationDataComponent>(true))
|
||||
{
|
||||
if (!TryComp<ShuttleComponent>(comp.EmergencyShuttle, out var shuttle)) continue;
|
||||
|
||||
// TODO: Add support so Hyperspace will just dock it to Centcomm.
|
||||
|
||||
Hyperspace(shuttle,
|
||||
new EntityCoordinates(
|
||||
_mapManager.GetMapEntityId(_centcommMap.Value),
|
||||
Vector2.One * 1000f), _consoleAccumulator, _transitTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_consoleAccumulator <= 0f)
|
||||
{
|
||||
_launchedShuttles = true;
|
||||
_chatSystem.DispatchGlobalStationAnnouncement(Loc.GetString("emergency-shuttle-left", ("transitTime", $"{_transitTime:0}")));
|
||||
|
||||
_roundEndCancelToken = new CancellationTokenSource();
|
||||
Timer.Spawn((int) (_transitTime * 1000) + _bufferTime.Milliseconds, () => _roundEnd.EndRound(), _roundEndCancelToken.Token);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnEmergencyRepealAll(EntityUid uid, EmergencyShuttleConsoleComponent component, EmergencyShuttleRepealAllMessage args)
|
||||
{
|
||||
var player = args.Session.AttachedEntity;
|
||||
if (player == null) return;
|
||||
|
||||
if (!_reader.FindAccessTags(player.Value).Contains(EmergencyRepealAllAccess))
|
||||
{
|
||||
_popup.PopupCursor(Loc.GetString("emergency-shuttle-console-denied"), Filter.Entities(player.Value));
|
||||
return;
|
||||
}
|
||||
|
||||
if (component.AuthorizedEntities.Count == 0) return;
|
||||
|
||||
_logger.Add(LogType.EmergencyShuttle, LogImpact.High, $"Emergency shuttle early launch REPEAL ALL by {args.Session:user}");
|
||||
component.AuthorizedEntities.Clear();
|
||||
UpdateAllEmergencyConsoles();
|
||||
}
|
||||
|
||||
private void OnEmergencyRepeal(EntityUid uid, EmergencyShuttleConsoleComponent component, EmergencyShuttleRepealMessage args)
|
||||
{
|
||||
var player = args.Session.AttachedEntity;
|
||||
if (player == null) return;
|
||||
|
||||
if (!_reader.IsAllowed(player.Value, uid))
|
||||
{
|
||||
_popup.PopupCursor("Access denied", Filter.Entities(player.Value));
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: This is fucking bad
|
||||
if (!component.AuthorizedEntities.Remove(MetaData(player.Value).EntityName)) return;
|
||||
|
||||
_logger.Add(LogType.EmergencyShuttle, LogImpact.High, $"Emergency shuttle early launch REPEAL by {args.Session:user}");
|
||||
var remaining = component.AuthorizationsRequired - component.AuthorizedEntities.Count;
|
||||
_chatSystem.DispatchGlobalStationAnnouncement(Loc.GetString("emergency-shuttle-console-auth-revoked", ("remaining", remaining)));
|
||||
CheckForLaunch(component);
|
||||
UpdateAllEmergencyConsoles();
|
||||
}
|
||||
|
||||
private void OnEmergencyAuthorize(EntityUid uid, EmergencyShuttleConsoleComponent component, EmergencyShuttleAuthorizeMessage args)
|
||||
{
|
||||
var player = args.Session.AttachedEntity;
|
||||
if (player == null) return;
|
||||
|
||||
if (!_reader.IsAllowed(player.Value, uid))
|
||||
{
|
||||
_popup.PopupCursor(Loc.GetString("emergency-shuttle-console-denied"), Filter.Entities(player.Value));
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: This is fucking bad
|
||||
if (!component.AuthorizedEntities.Add(MetaData(player.Value).EntityName)) return;
|
||||
|
||||
_logger.Add(LogType.EmergencyShuttle, LogImpact.High, $"Emergency shuttle early launch AUTH by {args.Session:user}");
|
||||
var remaining = component.AuthorizationsRequired - component.AuthorizedEntities.Count;
|
||||
|
||||
if (remaining > 0)
|
||||
_chatSystem.DispatchGlobalStationAnnouncement(Loc.GetString("emergency-shuttle-console-auth-left", ("remaining", remaining)), playDefaultSound: false);
|
||||
|
||||
SoundSystem.Play("/Audio/Misc/notice1.ogg", Filter.Broadcast());
|
||||
CheckForLaunch(component);
|
||||
UpdateAllEmergencyConsoles();
|
||||
}
|
||||
|
||||
private void CleanupEmergencyConsole()
|
||||
{
|
||||
_roundEndCancelToken = null;
|
||||
_launchedShuttles = false;
|
||||
_consoleAccumulator = 0f;
|
||||
EarlyLaunchAuthorized = false;
|
||||
EmergencyShuttleArrived = false;
|
||||
}
|
||||
|
||||
private void UpdateAllEmergencyConsoles()
|
||||
{
|
||||
foreach (var comp in EntityQuery<EmergencyShuttleConsoleComponent>(true))
|
||||
{
|
||||
UpdateConsoleState(comp.Owner, comp);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateConsoleState(EntityUid uid, EmergencyShuttleConsoleComponent component)
|
||||
{
|
||||
var auths = new List<string>();
|
||||
|
||||
foreach (var auth in component.AuthorizedEntities)
|
||||
{
|
||||
auths.Add(auth);
|
||||
}
|
||||
|
||||
_uiSystem.GetUiOrNull(uid, EmergencyConsoleUiKey.Key)?.SetState(new EmergencyConsoleBoundUserInterfaceState()
|
||||
{
|
||||
EarlyLaunchTime = EarlyLaunchAuthorized ? _timing.CurTime + TimeSpan.FromSeconds(_consoleAccumulator) : null,
|
||||
Authorizations = auths,
|
||||
AuthorizationsRequired = component.AuthorizationsRequired,
|
||||
});
|
||||
}
|
||||
|
||||
private void CheckForLaunch(EmergencyShuttleConsoleComponent component)
|
||||
{
|
||||
if (component.AuthorizedEntities.Count < component.AuthorizationsRequired || EarlyLaunchAuthorized)
|
||||
return;
|
||||
|
||||
EarlyLaunch();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to early launch the emergency shuttle if not already done.
|
||||
/// </summary>
|
||||
public void EarlyLaunch()
|
||||
{
|
||||
if (EarlyLaunchAuthorized || !EmergencyShuttleArrived) return;
|
||||
|
||||
_logger.Add(LogType.EmergencyShuttle, LogImpact.Extreme, $"Emergency shuttle launch authorized");
|
||||
_consoleAccumulator = MathF.Max(1f, MathF.Min(_consoleAccumulator, _authorizeTime));
|
||||
EarlyLaunchAuthorized = true;
|
||||
RaiseLocalEvent(new EmergencyShuttleAuthorizedEvent());
|
||||
_chatSystem.DispatchGlobalStationAnnouncement(Loc.GetString("emergency-shuttle-launch-time", ("consoleAccumulator", $"{_consoleAccumulator:0}")), playDefaultSound: false);
|
||||
UpdateAllEmergencyConsoles();
|
||||
}
|
||||
|
||||
public bool DelayEmergencyRoundEnd()
|
||||
{
|
||||
if (_roundEndCancelToken == null) return false;
|
||||
_roundEndCancelToken = null;
|
||||
_roundEndCancelToken?.Cancel();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,465 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Server.Administration.Managers;
|
||||
using Content.Server.Chat.Systems;
|
||||
using Content.Server.Communications;
|
||||
using Content.Server.GameTicking.Events;
|
||||
using Content.Server.Shuttles.Components;
|
||||
using Content.Server.Station.Components;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Shuttles.Events;
|
||||
using Robust.Server.Maps;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.Shuttles.Systems;
|
||||
|
||||
public sealed partial class ShuttleSystem
|
||||
{
|
||||
/*
|
||||
* Handles the escape shuttle + Centcomm.
|
||||
*/
|
||||
|
||||
[Dependency] private readonly IAdminLogManager _logger = default!;
|
||||
[Dependency] private readonly IAdminManager _admin = default!;
|
||||
[Dependency] private readonly IConfigurationManager _configManager = default!;
|
||||
[Dependency] private readonly IMapLoader _loader = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly ChatSystem _chatSystem = default!;
|
||||
[Dependency] private readonly CommunicationsConsoleSystem _commsConsole = default!;
|
||||
[Dependency] private readonly DockingSystem _dockSystem = default!;
|
||||
[Dependency] private readonly StationSystem _station = default!;
|
||||
|
||||
private MapId? _centcommMap;
|
||||
|
||||
/// <summary>
|
||||
/// Used for multiple shuttle spawn offsets.
|
||||
/// </summary>
|
||||
private float _shuttleIndex;
|
||||
|
||||
private const float ShuttleSpawnBuffer = 1f;
|
||||
|
||||
private void InitializeEscape()
|
||||
{
|
||||
SubscribeLocalEvent<RoundStartingEvent>(OnRoundStart);
|
||||
SubscribeLocalEvent<StationDataComponent, ComponentStartup>(OnStationStartup);
|
||||
SubscribeNetworkEvent<EmergencyShuttleRequestPositionMessage>(OnShuttleRequestPosition);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If the client is requesting debug info on where an emergency shuttle would dock.
|
||||
/// </summary>
|
||||
private void OnShuttleRequestPosition(EmergencyShuttleRequestPositionMessage msg, EntitySessionEventArgs args)
|
||||
{
|
||||
if (!_admin.IsAdmin((IPlayerSession) args.SenderSession)) return;
|
||||
|
||||
var player = args.SenderSession.AttachedEntity;
|
||||
|
||||
if (player == null ||
|
||||
!TryComp<StationDataComponent>(_station.GetOwningStation(player.Value), out var stationData)) return;
|
||||
|
||||
var config = GetDockingConfig(stationData);
|
||||
|
||||
if (config != null)
|
||||
{
|
||||
RaiseNetworkEvent(new EmergencyShuttlePositionMessage()
|
||||
{
|
||||
StationUid = config.TargetGrid,
|
||||
Position = config.Area,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the emergency shuttle can warp to the specified position.
|
||||
/// </summary>
|
||||
private bool ValidSpawn(IMapGridComponent grid, Box2 area)
|
||||
{
|
||||
return !grid.Grid.GetLocalTilesIntersecting(area).Any();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to find the most valid docking config for the station.
|
||||
/// </summary>
|
||||
private DockingConfig? GetDockingConfig(StationDataComponent dataComponent)
|
||||
{
|
||||
// Find the largest grid associated with the station, then try all combinations of docks on it with
|
||||
// all of them on the shuttle and try to find the most appropriate.
|
||||
if (dataComponent.EmergencyShuttle == null) return null;
|
||||
|
||||
var targetGrid = GetLargestGrid(dataComponent);
|
||||
|
||||
if (targetGrid == null) return null;
|
||||
var gridDocks = GetDocks(targetGrid.Value);
|
||||
|
||||
if (gridDocks.Count <= 0) return null;
|
||||
|
||||
var xformQuery = GetEntityQuery<TransformComponent>();
|
||||
var targetGridGrid = Comp<IMapGridComponent>(targetGrid.Value);
|
||||
var targetGridXform = xformQuery.GetComponent(targetGrid.Value);
|
||||
var targetGridRotation = targetGridXform.WorldRotation.ToVec();
|
||||
|
||||
var shuttleDocks = GetDocks(dataComponent.EmergencyShuttle.Value);
|
||||
var shuttleAABB = Comp<IMapGridComponent>(dataComponent.EmergencyShuttle.Value).Grid.LocalAABB;
|
||||
|
||||
var validDockConfigs = new List<DockingConfig>();
|
||||
|
||||
if (TryComp<ShuttleComponent>(dataComponent.EmergencyShuttle, out var shuttle))
|
||||
{
|
||||
SetPilotable(shuttle, false);
|
||||
}
|
||||
|
||||
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,
|
||||
targetGridRotation,
|
||||
shuttleAABB,
|
||||
targetGridGrid,
|
||||
out var dockedAABB,
|
||||
out var matty,
|
||||
out var targetAngle)) 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),
|
||||
targetGridRotation,
|
||||
shuttleAABB, targetGridGrid,
|
||||
out var otherDockedAABB,
|
||||
out _,
|
||||
out var otherTargetAngle) ||
|
||||
!otherDockedAABB.Equals(dockedAABB) ||
|
||||
!targetAngle.Equals(otherTargetAngle)) continue;
|
||||
|
||||
dockedPorts.Add((other, otherGrid));
|
||||
}
|
||||
}
|
||||
|
||||
var spawnPosition = new EntityCoordinates(targetGrid.Value, matty.Transform(Vector2.Zero));
|
||||
spawnPosition = new EntityCoordinates(targetGridXform.MapUid!.Value, spawnPosition.ToMapPos(EntityManager));
|
||||
var spawnRotation = shuttleDockXform.LocalRotation +
|
||||
gridXform.LocalRotation +
|
||||
targetGridXform.LocalRotation;
|
||||
|
||||
validDockConfigs.Add(new DockingConfig()
|
||||
{
|
||||
Docks = dockedPorts,
|
||||
Area = dockedAABB.Value,
|
||||
Coordinates = spawnPosition,
|
||||
Angle = spawnRotation,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (validDockConfigs.Count <= 0) return null;
|
||||
|
||||
var targetGridAngle = targetGridXform.WorldRotation.Reduced();
|
||||
|
||||
// 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.Value;
|
||||
// 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>
|
||||
/// <param name="stationUid"></param>
|
||||
/// <param name="dryRun">Should we show the debug data and not actually call it.</param>
|
||||
public void CallEmergencyShuttle(EntityUid? stationUid)
|
||||
{
|
||||
if (!TryComp<StationDataComponent>(stationUid, out var stationData) ||
|
||||
!TryComp<TransformComponent>(stationData.EmergencyShuttle, out var xform)) return;
|
||||
|
||||
var config = GetDockingConfig(stationData);
|
||||
|
||||
if (config != null)
|
||||
{
|
||||
// Set position
|
||||
xform.Coordinates = config.Coordinates;
|
||||
xform.WorldRotation = config.Angle;
|
||||
|
||||
// Connect everything
|
||||
foreach (var (dockA, dockB) in config.Docks)
|
||||
{
|
||||
_dockSystem.Dock(dockA, dockB);
|
||||
}
|
||||
|
||||
_logger.Add(LogType.EmergencyShuttle, LogImpact.High, $"Emergency shuttle {ToPrettyString(stationUid.Value)} docked with stations");
|
||||
_chatSystem.DispatchStationAnnouncement(stationUid.Value, Loc.GetString("emergency-shuttle-docked", ("time", $"{_consoleAccumulator:0}")), playDefaultSound: false);
|
||||
// TODO: Need filter extensions or something don't blame me.
|
||||
SoundSystem.Play("/Audio/Announcements/shuttle_dock.ogg", Filter.Broadcast());
|
||||
}
|
||||
else
|
||||
{
|
||||
var shuttleAABB = Comp<IMapGridComponent>(stationData.EmergencyShuttle.Value).Grid.WorldAABB;
|
||||
Box2? aabb = null;
|
||||
|
||||
// Spawn nearby.
|
||||
foreach (var gridUid in stationData.Grids)
|
||||
{
|
||||
var grid = Comp<IMapGridComponent>(gridUid).Grid;
|
||||
var gridAABB = grid.WorldAABB;
|
||||
aabb = aabb?.Union(gridAABB) ?? gridAABB;
|
||||
}
|
||||
|
||||
// UHH GOOD LUCK
|
||||
if (aabb == null)
|
||||
{
|
||||
_logger.Add(LogType.EmergencyShuttle, LogImpact.High, $"Emergency shuttle {ToPrettyString(stationUid.Value)} unable to dock with station {ToPrettyString(stationUid.Value)}");
|
||||
_chatSystem.DispatchStationAnnouncement(stationUid.Value, Loc.GetString("emergency-shuttle-good-luck"), playDefaultSound: false);
|
||||
// TODO: Need filter extensions or something don't blame me.
|
||||
SoundSystem.Play("/Audio/Misc/notice1.ogg", Filter.Broadcast());
|
||||
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
if (TryComp<PhysicsComponent>(stationData.EmergencyShuttle, out var shuttleBody))
|
||||
{
|
||||
shuttleBody.LinearVelocity = Vector2.Zero;
|
||||
shuttleBody.AngularVelocity = 0f;
|
||||
}
|
||||
|
||||
xform.WorldPosition = spawnPos;
|
||||
xform.WorldRotation = _random.NextAngle();
|
||||
|
||||
_logger.Add(LogType.EmergencyShuttle, LogImpact.High, $"Emergency shuttle {ToPrettyString(stationUid.Value)} unable to find a valid docking port for {ToPrettyString(stationUid.Value)}");
|
||||
_chatSystem.DispatchStationAnnouncement(stationUid.Value, Loc.GetString("emergency-shuttle-nearby"), playDefaultSound: false);
|
||||
// TODO: Need filter extensions or something don't blame me.
|
||||
SoundSystem.Play("/Audio/Misc/notice1.ogg", Filter.Broadcast());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if 2 docks can be connected by moving the shuttle directly onto docks.
|
||||
/// </summary>
|
||||
private bool CanDock(
|
||||
DockingComponent shuttleDock,
|
||||
TransformComponent shuttleXform,
|
||||
DockingComponent gridDock,
|
||||
TransformComponent gridXform,
|
||||
Vector2 targetGridRotation,
|
||||
Box2 shuttleAABB,
|
||||
IMapGridComponent grid,
|
||||
[NotNullWhen(true)] out Box2? shuttleDockedAABB,
|
||||
out Matrix3 matty,
|
||||
out Vector2 gridRotation)
|
||||
{
|
||||
gridRotation = Vector2.Zero;
|
||||
matty = Matrix3.Identity;
|
||||
shuttleDockedAABB = null;
|
||||
|
||||
if (shuttleDock.Docked ||
|
||||
gridDock.Docked ||
|
||||
!shuttleXform.Anchored ||
|
||||
!gridXform.Anchored)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// First, get the station dock's position relative to the shuttle, this is where we rotate it around
|
||||
var stationDockPos = shuttleXform.LocalPosition +
|
||||
shuttleXform.LocalRotation.RotateVec(new Vector2(0f, -1f));
|
||||
|
||||
var stationDockMatrix = Matrix3.CreateInverseTransform(stationDockPos, -shuttleXform.LocalRotation);
|
||||
var gridXformMatrix = Matrix3.CreateTransform(gridXform.LocalPosition, gridXform.LocalRotation);
|
||||
Matrix3.Multiply(in stationDockMatrix, in gridXformMatrix, out matty);
|
||||
shuttleDockedAABB = matty.TransformBox(shuttleAABB);
|
||||
|
||||
if (!ValidSpawn(grid, shuttleDockedAABB.Value)) return false;
|
||||
|
||||
gridRotation = matty.Transform(targetGridRotation);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void OnStationStartup(EntityUid uid, StationDataComponent component, ComponentStartup args)
|
||||
{
|
||||
AddEmergencyShuttle(component);
|
||||
}
|
||||
|
||||
private void OnRoundStart(RoundStartingEvent ev)
|
||||
{
|
||||
Setup();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Spawns the emergency shuttle for each station and starts the countdown until controls unlock.
|
||||
/// </summary>
|
||||
public void CallEmergencyShuttle()
|
||||
{
|
||||
if (EmergencyShuttleArrived) return;
|
||||
|
||||
_consoleAccumulator = _configManager.GetCVar(CCVars.EmergencyShuttleDockTime);
|
||||
EmergencyShuttleArrived = true;
|
||||
|
||||
if (_centcommMap != null)
|
||||
_mapManager.SetMapPaused(_centcommMap.Value, false);
|
||||
|
||||
foreach (var comp in EntityQuery<StationDataComponent>(true))
|
||||
{
|
||||
CallEmergencyShuttle(comp.Owner);
|
||||
}
|
||||
|
||||
_commsConsole.UpdateCommsConsoleInterface();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the largest member grid from a station.
|
||||
/// </summary>
|
||||
private EntityUid? GetLargestGrid(StationDataComponent component)
|
||||
{
|
||||
EntityUid? largestGrid = null;
|
||||
Box2 largestBounds = new Box2();
|
||||
|
||||
foreach (var gridUid in component.Grids)
|
||||
{
|
||||
if (!TryComp<IMapGridComponent>(gridUid, out var grid)) continue;
|
||||
|
||||
if (grid.Grid.LocalAABB.Size.LengthSquared < largestBounds.Size.LengthSquared) continue;
|
||||
|
||||
largestBounds = grid.Grid.LocalAABB;
|
||||
largestGrid = gridUid;
|
||||
}
|
||||
|
||||
return largestGrid;
|
||||
}
|
||||
|
||||
private List<DockingComponent> GetDocks(EntityUid uid)
|
||||
{
|
||||
var result = new List<DockingComponent>();
|
||||
|
||||
foreach (var (dock, xform) in EntityQuery<DockingComponent, TransformComponent>(true))
|
||||
{
|
||||
if (xform.ParentUid != uid || !dock.Enabled) continue;
|
||||
|
||||
result.Add(dock);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void Setup()
|
||||
{
|
||||
if (_centcommMap != null && _mapManager.MapExists(_centcommMap.Value)) return;
|
||||
|
||||
_centcommMap = _mapManager.CreateMap();
|
||||
_mapManager.SetMapPaused(_centcommMap.Value, true);
|
||||
|
||||
// Load Centcomm, when we get it!
|
||||
// var (_, centcomm) = _loader.LoadBlueprint(_centcommMap.Value, "/Maps/Salvage/saltern.yml", new MapLoadOptions());
|
||||
// _centcomm = centcomm;
|
||||
|
||||
foreach (var comp in EntityQuery<StationDataComponent>(true))
|
||||
{
|
||||
AddEmergencyShuttle(comp);
|
||||
}
|
||||
}
|
||||
|
||||
private void AddEmergencyShuttle(StationDataComponent component)
|
||||
{
|
||||
if (_centcommMap == null || component.EmergencyShuttle != null) return;
|
||||
|
||||
// Load escape shuttle
|
||||
var (_, shuttle) = _loader.LoadBlueprint(_centcommMap.Value, component.EmergencyShuttlePath.ToString(), new MapLoadOptions()
|
||||
{
|
||||
// Should be far enough... right? I'm too lazy to bounds check centcomm rn.
|
||||
Offset = new Vector2(500f + _shuttleIndex, 0f)
|
||||
});
|
||||
|
||||
if (shuttle == null)
|
||||
{
|
||||
_sawmill.Error($"Unable to spawn emergency shuttle {component.EmergencyShuttlePath} for {ToPrettyString(component.Owner)}");
|
||||
return;
|
||||
}
|
||||
|
||||
_shuttleIndex += _mapManager.GetGrid(shuttle.Value).LocalAABB.Width + ShuttleSpawnBuffer;
|
||||
component.EmergencyShuttle = shuttle;
|
||||
}
|
||||
|
||||
private void CleanupEmergencyShuttle()
|
||||
{
|
||||
_shuttleIndex = 0f;
|
||||
|
||||
if (_centcommMap == null || !_mapManager.MapExists(_centcommMap.Value))
|
||||
{
|
||||
_centcommMap = null;
|
||||
return;
|
||||
}
|
||||
|
||||
_mapManager.DeleteMap(_centcommMap.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stores the data for a valid docking configuration for the emergency shuttle
|
||||
/// </summary>
|
||||
private sealed class DockingConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// The pairs of docks that can connect.
|
||||
/// </summary>
|
||||
public List<(DockingComponent DockA, DockingComponent DockB)> Docks = new();
|
||||
|
||||
/// <summary>
|
||||
/// Area relative to the target grid the emergency shuttle will spawn in on.
|
||||
/// </summary>
|
||||
public Box2 Area;
|
||||
|
||||
/// <summary>
|
||||
/// Target grid for docking.
|
||||
/// </summary>
|
||||
public EntityUid TargetGrid;
|
||||
|
||||
public EntityCoordinates Coordinates;
|
||||
public Angle Angle;
|
||||
}
|
||||
}
|
||||
210
Content.Server/Shuttles/Systems/ShuttleSystem.Hyperspace.cs
Normal file
210
Content.Server/Shuttles/Systems/ShuttleSystem.Hyperspace.cs
Normal file
@@ -0,0 +1,210 @@
|
||||
using Content.Server.Buckle.Components;
|
||||
using Content.Server.Doors.Components;
|
||||
using Content.Server.Shuttles.Components;
|
||||
using Content.Server.Stunnable;
|
||||
using Content.Shared.Sound;
|
||||
using Content.Shared.StatusEffect;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Collections;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Shuttles.Systems;
|
||||
|
||||
public sealed partial class ShuttleSystem
|
||||
{
|
||||
/*
|
||||
* This is a way to move a shuttle from one location to another, via an intermediate map for fanciness.
|
||||
*/
|
||||
|
||||
[Dependency] private readonly StunSystem _stuns = default!;
|
||||
|
||||
private MapId? _hyperSpaceMap;
|
||||
|
||||
private const float DefaultStartupTime = 5.5f;
|
||||
private const float DefaultTravelTime = 30f;
|
||||
|
||||
// I'm too lazy to make CVars.
|
||||
|
||||
private readonly SoundSpecifier _startupSound = new SoundPathSpecifier("/Audio/Effects/Shuttle/hyperspace_begin.ogg");
|
||||
// private SoundSpecifier _travelSound = new SoundPathSpecifier();
|
||||
private readonly SoundSpecifier _arrivalSound = new SoundPathSpecifier("/Audio/Effects/Shuttle/hyperspace_end.ogg");
|
||||
|
||||
private readonly TimeSpan _hyperspaceKnockdownTime = TimeSpan.FromSeconds(5);
|
||||
|
||||
/// Left-side of the station we're allowed to use
|
||||
private float _index;
|
||||
|
||||
/// <summary>
|
||||
/// Space between grids within hyperspace.
|
||||
/// </summary>
|
||||
private const float Buffer = 5f;
|
||||
|
||||
/// <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,
|
||||
EntityCoordinates coordinates,
|
||||
float startupTime = DefaultStartupTime,
|
||||
float hyperspaceTime = DefaultTravelTime)
|
||||
{
|
||||
if (HasComp<HyperspaceComponent>(component.Owner))
|
||||
{
|
||||
_sawmill.Warning($"Tried queuing {ToPrettyString(component.Owner)} which already has HyperspaceComponent?");
|
||||
return;
|
||||
}
|
||||
|
||||
SetDocks(component.Owner, false);
|
||||
var hyperspace = AddComp<HyperspaceComponent>(component.Owner);
|
||||
hyperspace.StartupTime = startupTime;
|
||||
hyperspace.TravelTime = hyperspaceTime;
|
||||
hyperspace.Accumulator = hyperspace.StartupTime;
|
||||
hyperspace.TargetCoordinates = coordinates;
|
||||
// TODO: Need BroadcastGrid to not be bad.
|
||||
SoundSystem.Play(_startupSound.GetSound(), Filter.Pvs(component.Owner, GetSoundRange(component.Owner), entityManager: EntityManager), _startupSound.Params);
|
||||
}
|
||||
|
||||
private void UpdateHyperspace(float frameTime)
|
||||
{
|
||||
foreach (var comp in EntityQuery<HyperspaceComponent>())
|
||||
{
|
||||
comp.Accumulator -= frameTime;
|
||||
|
||||
if (comp.Accumulator > 0f) continue;
|
||||
|
||||
var xform = Transform(comp.Owner);
|
||||
PhysicsComponent? body;
|
||||
|
||||
switch (comp.State)
|
||||
{
|
||||
// Startup time has elapsed and in hyperspace.
|
||||
case HyperspaceState.Starting:
|
||||
DoTheDinosaur(xform);
|
||||
|
||||
comp.State = HyperspaceState.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;
|
||||
|
||||
if (TryComp(comp.Owner, out body))
|
||||
{
|
||||
body.LinearVelocity = new Vector2(0f, 100f);
|
||||
body.AngularVelocity = 0f;
|
||||
body.LinearDamping = 0f;
|
||||
body.AngularDamping = 0f;
|
||||
}
|
||||
|
||||
SetDockBolts(comp.Owner, true);
|
||||
|
||||
break;
|
||||
// Arrive.
|
||||
case HyperspaceState.Travelling:
|
||||
DoTheDinosaur(xform);
|
||||
SetDocks(comp.Owner, true);
|
||||
SetDockBolts(comp.Owner, false);
|
||||
|
||||
if (TryComp(comp.Owner, out body))
|
||||
{
|
||||
body.LinearVelocity = Vector2.Zero;
|
||||
body.AngularVelocity = 0f;
|
||||
body.LinearDamping = ShuttleIdleLinearDamping;
|
||||
body.AngularDamping = ShuttleIdleAngularDamping;
|
||||
}
|
||||
|
||||
xform.Coordinates = comp.TargetCoordinates;
|
||||
SoundSystem.Play(_arrivalSound.GetSound(),
|
||||
Filter.Pvs(comp.Owner, GetSoundRange(comp.Owner), entityManager: EntityManager));
|
||||
RemComp<HyperspaceComponent>(comp.Owner);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SetDocks(EntityUid uid, bool enabled)
|
||||
{
|
||||
foreach (var (dock, xform) in EntityQuery<DockingComponent, TransformComponent>(true))
|
||||
{
|
||||
if (xform.ParentUid != uid || dock.Enabled == enabled) continue;
|
||||
_dockSystem.Undock(dock);
|
||||
dock.Enabled = enabled;
|
||||
}
|
||||
}
|
||||
|
||||
private void SetDockBolts(EntityUid uid, bool enabled)
|
||||
{
|
||||
foreach (var (dock, door, xform) in EntityQuery<DockingComponent, AirlockComponent, TransformComponent>(true))
|
||||
{
|
||||
if (xform.ParentUid != uid ||
|
||||
dock.Enabled == enabled) continue;
|
||||
|
||||
door.SetBoltsWithAudio(enabled);
|
||||
}
|
||||
}
|
||||
|
||||
private float GetSoundRange(EntityUid uid)
|
||||
{
|
||||
if (!_mapManager.TryGetGrid(uid, out var grid)) return 4f;
|
||||
|
||||
return MathF.Max(grid.LocalAABB.Width, grid.LocalAABB.Height) + 12.5f;
|
||||
}
|
||||
|
||||
private void SetupHyperspace()
|
||||
{
|
||||
if (_hyperSpaceMap != null) return;
|
||||
|
||||
_hyperSpaceMap = _mapManager.CreateMap();
|
||||
_sawmill.Info($"Setup hyperspace map at {_hyperSpaceMap.Value}");
|
||||
DebugTools.Assert(!_mapManager.IsMapPaused(_hyperSpaceMap.Value));
|
||||
}
|
||||
|
||||
private void CleanupHyperspace()
|
||||
{
|
||||
_index = 0f;
|
||||
if (_hyperSpaceMap == null || !_mapManager.MapExists(_hyperSpaceMap.Value))
|
||||
{
|
||||
_hyperSpaceMap = null;
|
||||
return;
|
||||
}
|
||||
_mapManager.DeleteMap(_hyperSpaceMap.Value);
|
||||
_hyperSpaceMap = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Puts everyone unbuckled on the floor, paralyzed.
|
||||
/// </summary>
|
||||
private void DoTheDinosaur(TransformComponent xform)
|
||||
{
|
||||
var buckleQuery = GetEntityQuery<BuckleComponent>();
|
||||
var statusQuery = GetEntityQuery<StatusEffectsComponent>();
|
||||
// Get enumeration exceptions from people dropping things if we just paralyze as we go
|
||||
var toKnock = new ValueList<EntityUid>();
|
||||
|
||||
KnockOverKids(xform, buckleQuery, statusQuery, ref toKnock);
|
||||
|
||||
foreach (var child in toKnock)
|
||||
{
|
||||
if (!statusQuery.TryGetComponent(child, out var status)) continue;
|
||||
_stuns.TryParalyze(child, _hyperspaceKnockdownTime, true, status);
|
||||
}
|
||||
}
|
||||
|
||||
private void KnockOverKids(TransformComponent xform, EntityQuery<BuckleComponent> buckleQuery, EntityQuery<StatusEffectsComponent> statusQuery, ref ValueList<EntityUid> toKnock)
|
||||
{
|
||||
// Not recursive because probably not necessary? If we need it to be that's why this method is separate.
|
||||
var childEnumerator = xform.ChildEnumerator;
|
||||
|
||||
while (childEnumerator.MoveNext(out var child))
|
||||
{
|
||||
if (!buckleQuery.TryGetComponent(child.Value, out var buckle) || buckle.Buckled) continue;
|
||||
|
||||
toKnock.Add(child.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,24 @@
|
||||
using Content.Server.RoundEnd;
|
||||
using Content.Server.Shuttles.Components;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.Shuttles.Components;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Physics;
|
||||
|
||||
namespace Content.Server.Shuttles.Systems
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public sealed class ShuttleSystem : EntitySystem
|
||||
public sealed partial class ShuttleSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly FixtureSystem _fixtures = default!;
|
||||
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
|
||||
|
||||
private ISawmill _sawmill = default!;
|
||||
|
||||
public const float TileMassMultiplier = 0.5f;
|
||||
|
||||
@@ -25,10 +34,17 @@ namespace Content.Server.Shuttles.Systems
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
_sawmill = Logger.GetSawmill("shuttles");
|
||||
|
||||
InitializeEmergencyConsole();
|
||||
InitializeEscape();
|
||||
|
||||
SubscribeLocalEvent<ShuttleComponent, ComponentAdd>(OnShuttleAdd);
|
||||
SubscribeLocalEvent<ShuttleComponent, ComponentStartup>(OnShuttleStartup);
|
||||
SubscribeLocalEvent<ShuttleComponent, ComponentShutdown>(OnShuttleShutdown);
|
||||
|
||||
SubscribeLocalEvent<RoundRestartCleanupEvent>(OnRoundRestart);
|
||||
|
||||
SubscribeLocalEvent<GridInitializeEvent>(OnGridInit);
|
||||
SubscribeLocalEvent<GridFixtureChangeEvent>(OnGridFixtureChange);
|
||||
|
||||
@@ -41,6 +57,20 @@ namespace Content.Server.Shuttles.Systems
|
||||
configManager.OnValueChanged(CCVars.ShuttleMaxAngularMomentum, SetShuttleMaxAngularMomentum, true);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
UpdateEmergencyConsole(frameTime);
|
||||
UpdateHyperspace(frameTime);
|
||||
}
|
||||
|
||||
private void OnRoundRestart(RoundRestartCleanupEvent ev)
|
||||
{
|
||||
CleanupEmergencyConsole();
|
||||
CleanupEmergencyShuttle();
|
||||
CleanupHyperspace();
|
||||
}
|
||||
|
||||
private void SetShuttleMaxLinearSpeed(float value) => ShuttleMaxLinearSpeed = value;
|
||||
private void SetShuttleMaxAngularSpeed(float value) => ShuttleMaxAngularSpeed = value;
|
||||
private void SetShuttleMaxAngularAcc(float value) => ShuttleMaxAngularAcc = value;
|
||||
@@ -51,12 +81,12 @@ namespace Content.Server.Shuttles.Systems
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
var configManager = IoCManager.Resolve<IConfigurationManager>();
|
||||
configManager.UnsubValueChanged(CCVars.ShuttleMaxLinearSpeed, SetShuttleMaxLinearSpeed);
|
||||
configManager.UnsubValueChanged(CCVars.ShuttleMaxAngularSpeed, SetShuttleMaxAngularSpeed);
|
||||
configManager.UnsubValueChanged(CCVars.ShuttleIdleLinearDamping, SetShuttleIdleLinearDamping);
|
||||
configManager.UnsubValueChanged(CCVars.ShuttleIdleAngularDamping, SetShuttleIdleAngularDamping);
|
||||
configManager.UnsubValueChanged(CCVars.ShuttleMaxAngularMomentum, SetShuttleMaxAngularMomentum);
|
||||
ShutdownEmergencyConsole();
|
||||
_configManager.UnsubValueChanged(CCVars.ShuttleMaxLinearSpeed, SetShuttleMaxLinearSpeed);
|
||||
_configManager.UnsubValueChanged(CCVars.ShuttleMaxAngularSpeed, SetShuttleMaxAngularSpeed);
|
||||
_configManager.UnsubValueChanged(CCVars.ShuttleIdleLinearDamping, SetShuttleIdleLinearDamping);
|
||||
_configManager.UnsubValueChanged(CCVars.ShuttleIdleAngularDamping, SetShuttleIdleAngularDamping);
|
||||
_configManager.UnsubValueChanged(CCVars.ShuttleMaxAngularMomentum, SetShuttleMaxAngularMomentum);
|
||||
}
|
||||
|
||||
private void OnShuttleAdd(EntityUid uid, ShuttleComponent component, ComponentAdd args)
|
||||
@@ -107,6 +137,24 @@ 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;
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Server.Shuttles.Systems;
|
||||
using Content.Server.Station.Systems;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Station.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Stores core information about a station, namely it's config and associated grids.
|
||||
/// Stores core information about a station, namely its config and associated grids.
|
||||
/// All station entities will have this component.
|
||||
/// </summary>
|
||||
[RegisterComponent, Access(typeof(StationSystem))]
|
||||
@@ -23,4 +25,16 @@ public sealed class StationDataComponent : Component
|
||||
/// </remarks>
|
||||
[DataField("grids")]
|
||||
public readonly HashSet<EntityUid> Grids = new();
|
||||
|
||||
/// <summary>
|
||||
/// The emergency shuttle assigned to this station.
|
||||
/// </summary>
|
||||
[ViewVariables, Access(typeof(ShuttleSystem), Friend = AccessPermissions.ReadWrite)]
|
||||
public EntityUid? EmergencyShuttle;
|
||||
|
||||
/// <summary>
|
||||
/// Emergency shuttle map path for this station.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite), Access(typeof(ShuttleSystem), Friend = AccessPermissions.ReadExecute)]
|
||||
public ResourcePath EmergencyShuttlePath = new("/Maps/Shuttles/emergency_shuttle.yml");
|
||||
}
|
||||
|
||||
@@ -69,6 +69,7 @@ public enum LogType
|
||||
Trigger = 65,
|
||||
Anchor = 66,
|
||||
Unanchor = 67,
|
||||
EmergencyShuttle = 68,
|
||||
// haha so funny
|
||||
Emag = 69,
|
||||
}
|
||||
|
||||
@@ -901,6 +901,28 @@ namespace Content.Shared.CCVar
|
||||
CVarDef.Create("shuttle.idle_angular_damping", 100f, CVar.SERVERONLY);
|
||||
|
||||
|
||||
/*
|
||||
* Emergency
|
||||
*/
|
||||
|
||||
/// <summary>
|
||||
/// How long the emergency shuttle remains docked with the station, in seconds.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<float> EmergencyShuttleDockTime =
|
||||
CVarDef.Create("shuttle.emergency_dock_time", 180f, CVar.SERVERONLY);
|
||||
|
||||
/// <summary>
|
||||
/// How long after the console is authorized for the shuttle to early launch.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<float> EmergencyShuttleAuthorizeTime =
|
||||
CVarDef.Create("shuttle.emergency_authorize_time", 10f, CVar.SERVERONLY);
|
||||
|
||||
/// <summary>
|
||||
/// How long after the console is authorized for the shuttle to early launch.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<float> EmergencyShuttleTransitTime =
|
||||
CVarDef.Create("shuttle.emergency_transit_time", 120f, CVar.SERVERONLY);
|
||||
|
||||
/*
|
||||
* VIEWPORT
|
||||
*/
|
||||
|
||||
@@ -33,10 +33,10 @@ namespace Content.Shared.Movement.Components
|
||||
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
private GameTick _lastInputTick;
|
||||
private ushort _lastInputSubTick;
|
||||
private Vector2 _curTickWalkMovement;
|
||||
private Vector2 _curTickSprintMovement;
|
||||
public GameTick _lastInputTick;
|
||||
public ushort _lastInputSubTick;
|
||||
public Vector2 CurTickWalkMovement;
|
||||
public Vector2 CurTickSprintMovement;
|
||||
|
||||
private MoveButtons _heldMoveButtons = MoveButtons.None;
|
||||
|
||||
@@ -89,8 +89,8 @@ namespace Content.Shared.Movement.Components
|
||||
}
|
||||
else
|
||||
{
|
||||
walk = _curTickWalkMovement;
|
||||
sprint = _curTickSprintMovement;
|
||||
walk = CurTickWalkMovement;
|
||||
sprint = CurTickSprintMovement;
|
||||
remainingFraction = (ushort.MaxValue - _lastInputSubTick) / (float) ushort.MaxValue;
|
||||
}
|
||||
|
||||
@@ -120,7 +120,6 @@ namespace Content.Shared.Movement.Components
|
||||
protected override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
Owner.EnsureComponentWarn<PhysicsComponent>();
|
||||
LastGridAngle = _entityManager.GetComponent<TransformComponent>(Owner).Parent?.WorldRotation ?? new Angle(0);
|
||||
}
|
||||
|
||||
@@ -154,8 +153,8 @@ namespace Content.Shared.Movement.Components
|
||||
|
||||
if (_gameTiming.CurTick > _lastInputTick)
|
||||
{
|
||||
_curTickWalkMovement = Vector2.Zero;
|
||||
_curTickSprintMovement = Vector2.Zero;
|
||||
CurTickWalkMovement = Vector2.Zero;
|
||||
CurTickSprintMovement = Vector2.Zero;
|
||||
_lastInputTick = _gameTiming.CurTick;
|
||||
_lastInputSubTick = 0;
|
||||
}
|
||||
@@ -164,7 +163,7 @@ namespace Content.Shared.Movement.Components
|
||||
{
|
||||
var fraction = (subTick - _lastInputSubTick) / (float) ushort.MaxValue;
|
||||
|
||||
ref var lastMoveAmount = ref Sprinting ? ref _curTickSprintMovement : ref _curTickWalkMovement;
|
||||
ref var lastMoveAmount = ref Sprinting ? ref CurTickSprintMovement : ref CurTickWalkMovement;
|
||||
|
||||
lastMoveAmount += DirVecForButtons(_heldMoveButtons) * fraction;
|
||||
|
||||
|
||||
@@ -59,15 +59,6 @@ namespace Content.Shared.Movement.Components
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
if (!IoCManager.Resolve<IEntityManager>().HasComponent<IMoverComponent>(Owner))
|
||||
{
|
||||
Owner.EnsureComponentWarn<SharedPlayerInputMoverComponent>();
|
||||
}
|
||||
}
|
||||
|
||||
public override ComponentState GetComponentState()
|
||||
{
|
||||
return new PlayerMobMoverComponentState(_grabRange, _pushStrength);
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Shuttles.BUIStates;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class EmergencyConsoleBoundUserInterfaceState : BoundUserInterfaceState
|
||||
{
|
||||
/// <summary>
|
||||
/// null if we're not early launching.
|
||||
/// </summary>
|
||||
public TimeSpan? EarlyLaunchTime;
|
||||
public List<string> Authorizations = new();
|
||||
public int AuthorizationsRequired;
|
||||
|
||||
public TimeSpan? TimeToLaunch;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Shuttles.Events;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class EmergencyShuttleAuthorizeMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Shuttles.Events;
|
||||
|
||||
/// <summary>
|
||||
/// For debugging the expected emergency shuttle position.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class EmergencyShuttlePositionMessage : EntityEventArgs
|
||||
{
|
||||
public EntityUid? StationUid;
|
||||
public Box2? Position;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Shuttles.Events;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class EmergencyShuttleRepealAllMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Shuttles.Events;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class EmergencyShuttleRepealMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Shuttles.Events;
|
||||
|
||||
/// <summary>
|
||||
/// Raised on the client to request the expected position of the emergency shuttle for debugging.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class EmergencyShuttleRequestPositionMessage : EntityEventArgs
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Shuttles.Systems;
|
||||
|
||||
public abstract partial class SharedShuttleSystem
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum EmergencyConsoleUiKey : byte
|
||||
{
|
||||
Key,
|
||||
}
|
||||
6
Content.Shared/Shuttles/Systems/SharedShuttleSystem.cs
Normal file
6
Content.Shared/Shuttles/Systems/SharedShuttleSystem.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace Content.Shared.Shuttles.Systems;
|
||||
|
||||
public abstract partial class SharedShuttleSystem : EntitySystem
|
||||
{
|
||||
|
||||
}
|
||||
@@ -4,3 +4,5 @@ meteors.ogg taken from https://github.com/tgstation/tgstation/blob/95731342b9716
|
||||
outbreak7.ogg taken from /tg/station at commit https://github.com/tgstation/tgstation/commit/40d89d11ea4a5cb81d61dc1018b46f4e7d32c62a used under CC-BY-SA-3.0
|
||||
welcome.ogg taken from /tg/station at commit https://github.com/tgstation/tgstation/commit/40d89d11ea4a5cb81d61dc1018b46f4e7d32c62a used under CC-BY-SA-3.0
|
||||
announce.ogg taken from /tg/station https://github.com/tgstation/tgstation/commit/40d89d11ea4a5cb81d61dc1018b46f4e7d32c62a cut from the beginning of "shuttlerecalled.ogg" used under CC-BY-SA-3.0
|
||||
|
||||
shuttle_dock.ogg taken from /tg/station at https://github.com/tgstation/tgstation/blob/b327cb667c1ca5d1aa14e6368e66cbfa7b343b9c/sound/ai/default/shuttledock.ogg used under CC-BY-SA-3.0
|
||||
BIN
Resources/Audio/Effects/Shuttle/hyperspace_begin.ogg
Normal file
BIN
Resources/Audio/Effects/Shuttle/hyperspace_begin.ogg
Normal file
Binary file not shown.
BIN
Resources/Audio/Effects/Shuttle/hyperspace_end.ogg
Normal file
BIN
Resources/Audio/Effects/Shuttle/hyperspace_end.ogg
Normal file
Binary file not shown.
5
Resources/Audio/Effects/Shuttle/licenses.txt
Normal file
5
Resources/Audio/Effects/Shuttle/licenses.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
The following sounds are taken from TGstation github (licensed under CC by 3.0):
|
||||
|
||||
hyperspace_begin.ogg taken from https://github.com/tgstation/tgstation/tree/71a79f1f75902d2ccab27cbffb971b12b7ab042d/sound/runtime/hyperspace
|
||||
|
||||
hyperspace_end.ogg taken from https://github.com/tgstation/tgstation/tree/71a79f1f75902d2ccab27cbffb971b12b7ab042d/sound/runtime/hyperspace
|
||||
@@ -1,5 +1,5 @@
|
||||
## RoundEndSystem
|
||||
|
||||
round-end-system-shuttle-called-announcement = An emergency shuttle has been sent. ETA: {$minutes} minutes.
|
||||
round-end-system-shuttle-called-announcement = An emergency shuttle has been sent. ETA: {$time} {$units}.
|
||||
round-end-system-shuttle-recalled-announcement = The emergency shuttle has been recalled.
|
||||
round-end-system-round-restart-eta-announcement = Restarting the round in {$minutes} minutes...
|
||||
33
Resources/Locale/en-US/shuttles/emergency.ftl
Normal file
33
Resources/Locale/en-US/shuttles/emergency.ftl
Normal file
@@ -0,0 +1,33 @@
|
||||
# Commands
|
||||
## Delay shuttle round end
|
||||
emergency-shuttle-command-round-desc = Stops the timer that ends the round when the emergency shuttle exits hyperspace.
|
||||
emergency-shuttle-command-round-yes = Round delayed.
|
||||
emergency-shuttle-command-round-no = Unable to delay round end.
|
||||
|
||||
## Dock emergency shuttle
|
||||
emergency-shuttle-command-dock-desc = Calls the emergency shuttle and docks it to the station... if it can.
|
||||
|
||||
## Launch emergency shuttle
|
||||
emergency-shuttle-command-launch-desc = Early launches the emergency shuttle if possible.
|
||||
|
||||
# Emergency shuttle
|
||||
emergency-shuttle-left = The Emergency Shuttle has left the station. Estimate {$transitTime} seconds until the shuttle clears the area.
|
||||
emergency-shuttle-launch-time = The emergency shuttle will launch in {$consoleAccumulator} seconds.
|
||||
emergency-shuttle-docked = The Emergency Shuttle has docked with the station. It will leave in {$time} seconds.
|
||||
emergency-shuttle-good-luck = The Emergency Shuttle is unable to find a station. Good luck.
|
||||
emergency-shuttle-nearby = The Emergency Shuttle is unable to find a valid docking port. It has warped in nearby.
|
||||
|
||||
# Emergency shuttle console popup / announcement
|
||||
emergency-shuttle-console-auth-left = {$remaining} authorizations needed until shuttle is launched early.
|
||||
emergency-shuttle-console-auth-revoked = Early launch authorization revoked, {$remaining} authorizations needed.
|
||||
emergency-shuttle-console-denied = Access denied
|
||||
|
||||
# UI
|
||||
emergency-shuttle-ui-engines = ENGINES:
|
||||
emergency-shuttle-ui-idle = Idle
|
||||
emergency-shuttle-ui-repeal-all = Repeal All
|
||||
emergency-shuttle-ui-early-authorize = Early Launch Authorization
|
||||
emergency-shuttle-ui-authorize = AUTHORIZE
|
||||
emergency-shuttle-ui-repeal = REPEAL
|
||||
emergency-shuttle-ui-authorizations = Authorizations
|
||||
emergency-shuttle-ui-remaining = Remaining: {$remaining}
|
||||
3508
Resources/Maps/Shuttles/emergency_shuttle.yml
Normal file
3508
Resources/Maps/Shuttles/emergency_shuttle.yml
Normal file
File diff suppressed because it is too large
Load Diff
@@ -14,3 +14,9 @@
|
||||
- Command
|
||||
- Captain
|
||||
- HeadOfPersonnel
|
||||
|
||||
- type: accessLevel
|
||||
id: EmergencyShuttleLaunch
|
||||
|
||||
- type: accessLevel
|
||||
id: EmergencyShuttleRepealAll
|
||||
@@ -5,6 +5,7 @@
|
||||
green:
|
||||
announcement: alert-level-green-announcement
|
||||
color: Green
|
||||
shuttleTime: 1200
|
||||
blue:
|
||||
announcement: alert-level-blue-announcement
|
||||
sound: /Audio/Misc/notice1.ogg
|
||||
@@ -13,10 +14,12 @@
|
||||
announcement: alert-level-violet-announcement
|
||||
sound: /Audio/Misc/notice1.ogg
|
||||
color: Violet
|
||||
shuttleTime: 1200
|
||||
yellow:
|
||||
announcement: alert-level-yellow-announcement
|
||||
sound: /Audio/Misc/notice1.ogg
|
||||
color: Yellow
|
||||
shuttleTime: 1200
|
||||
red:
|
||||
announcement: alert-level-red-announcement
|
||||
sound: /Audio/Misc/notice1.ogg
|
||||
@@ -33,9 +36,11 @@
|
||||
sound: /Audio/Misc/delta.ogg
|
||||
disableSelection: true
|
||||
color: DarkRed
|
||||
shuttleTime: 1200
|
||||
epsilon:
|
||||
announcement: alert-level-epsilon-announcement
|
||||
selectable: false
|
||||
sound: /Audio/Misc/epsilon.ogg
|
||||
disableSelection: true
|
||||
color: DarkViolet
|
||||
shuttleTime: 1200
|
||||
|
||||
@@ -137,6 +137,8 @@
|
||||
types:
|
||||
Poison: 2
|
||||
Piercing: 1
|
||||
- type: PlayerInputMover
|
||||
- type: PlayerMobMover
|
||||
- type: UtilityAI
|
||||
behaviorSets:
|
||||
- UnarmedAttackHostiles
|
||||
@@ -652,6 +654,8 @@
|
||||
damage:
|
||||
types:
|
||||
Blunt: 10
|
||||
- type: PlayerInputMover
|
||||
- type: PlayerMobMover
|
||||
- type: UtilityAI
|
||||
behaviorSets:
|
||||
- UnarmedAttackHostiles
|
||||
@@ -1130,6 +1134,8 @@
|
||||
- type: MovementSpeedModifier
|
||||
baseWalkSpeed : 7
|
||||
baseSprintSpeed : 7
|
||||
- type: PlayerInputMover
|
||||
- type: PlayerMobMover
|
||||
- type: UtilityAI
|
||||
behaviorSets:
|
||||
- UnarmedAttackHostiles
|
||||
@@ -1296,6 +1302,8 @@
|
||||
- type: AiFactionTag
|
||||
factions:
|
||||
- Xeno
|
||||
- type: PlayerInputMover
|
||||
- type: PlayerMobMover
|
||||
- type: UtilityAI
|
||||
behaviorSets:
|
||||
- Idle
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
parent: SimpleSpaceMobBase
|
||||
description: It looks friendly. Why don't you give it a hug?
|
||||
components:
|
||||
- type: PlayerInputMover
|
||||
- type: PlayerMobMover
|
||||
- type: UtilityAI
|
||||
behaviorSets:
|
||||
- Idle
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
parent: SimpleSpaceMobBase
|
||||
description: It's a space carp.
|
||||
components:
|
||||
- type: PlayerInputMover
|
||||
- type: PlayerMobMover
|
||||
- type: UtilityAI
|
||||
behaviorSets:
|
||||
- Idle
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
description: A miserable pile of secrets.
|
||||
suffix: AI
|
||||
components:
|
||||
- type: PlayerInputMover
|
||||
- type: PlayerMobMover
|
||||
- type: UtilityAI
|
||||
startingGear: PassengerGear
|
||||
behaviorSets:
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
id: MobCivilian
|
||||
description: A miserable pile of secrets.
|
||||
components:
|
||||
- type: PlayerInputMover
|
||||
- type: PlayerMobMover
|
||||
- type: UtilityAI
|
||||
behaviorSets:
|
||||
- Clothing
|
||||
@@ -22,6 +24,8 @@
|
||||
id: MobSpirate
|
||||
description: Yarr!
|
||||
components:
|
||||
- type: PlayerInputMover
|
||||
- type: PlayerMobMover
|
||||
- type: UtilityAI
|
||||
behaviorSets:
|
||||
- Clothing
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
- type: Tag
|
||||
tags:
|
||||
- FootstepSound
|
||||
- type: PlayerInputMover
|
||||
- type: PlayerMobMover
|
||||
- type: UtilityAI
|
||||
behaviorSets:
|
||||
- UnarmedAttackHostiles
|
||||
|
||||
@@ -82,6 +82,8 @@
|
||||
types:
|
||||
Piercing: 5
|
||||
Slash: 5
|
||||
- type: PlayerInputMover
|
||||
- type: PlayerMobMover
|
||||
- type: UtilityAI
|
||||
behaviorSets:
|
||||
- UnarmedAttackHostiles
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
- type: MovementSpeedModifier
|
||||
baseWalkSpeed : 3.75
|
||||
baseSprintSpeed : 3.75
|
||||
- type: PlayerInputMover
|
||||
- type: PlayerMobMover
|
||||
- type: UtilityAI
|
||||
behaviorSets:
|
||||
- Idle
|
||||
@@ -125,6 +127,8 @@
|
||||
- type: MovementSpeedModifier
|
||||
baseWalkSpeed : 3.5
|
||||
baseSprintSpeed : 3.5
|
||||
- type: PlayerInputMover
|
||||
- type: PlayerMobMover
|
||||
- type: UtilityAI
|
||||
behaviorSets:
|
||||
- Idle
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
Flammable: [Touch]
|
||||
Extinguish: [Touch]
|
||||
Acidic: [Touch, Ingestion]
|
||||
- type: PlayerInputMover
|
||||
- type: PlayerMobMover
|
||||
- type: UtilityAI
|
||||
behaviorSets:
|
||||
# - Clothing
|
||||
@@ -159,6 +161,8 @@
|
||||
parent: SimpleSpaceMobBase
|
||||
suffix: AI
|
||||
components:
|
||||
- type: PlayerInputMover
|
||||
- type: PlayerMobMover
|
||||
- type: UtilityAI
|
||||
behaviorSets:
|
||||
- Idle
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
parent: SimpleSpaceMobBase
|
||||
description: It's a space tick, watch out for its nasty bite. Centcomm reports that 90 percent of cargo leg amputations are due to space tick bites.
|
||||
components:
|
||||
- type: PlayerInputMover
|
||||
- type: PlayerMobMover
|
||||
- type: UtilityAI
|
||||
behaviorSets:
|
||||
- Idle
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
components:
|
||||
- type: CombatMode
|
||||
canDisarm: true
|
||||
- type: PlayerInputMover
|
||||
- type: PlayerMobMover
|
||||
- type: UtilityAI
|
||||
behaviorSets:
|
||||
- Idle
|
||||
@@ -340,6 +342,8 @@
|
||||
attributes:
|
||||
proper: true
|
||||
gender: male
|
||||
- type: PlayerInputMover
|
||||
- type: PlayerMobMover
|
||||
- type: UtilityAI
|
||||
behaviorSets:
|
||||
- Idle
|
||||
|
||||
@@ -42,6 +42,8 @@
|
||||
types:
|
||||
Piercing: 8
|
||||
Slash: 7
|
||||
- type: PlayerInputMover
|
||||
- type: PlayerMobMover
|
||||
- type: UtilityAI
|
||||
behaviorSets:
|
||||
- Idle
|
||||
|
||||
@@ -650,6 +650,15 @@
|
||||
- type: AccessReader
|
||||
access: [["External"]]
|
||||
|
||||
- type: entity
|
||||
parent: AirlockGlassShuttle
|
||||
id: AirlockExternalGlassShuttleEmergencyLocked
|
||||
suffix: External, Emergency, Glass, Docking, Locked
|
||||
components:
|
||||
- type: EmergencyDock
|
||||
- type: AccessReader
|
||||
access: [["External"]]
|
||||
|
||||
#HighSecDoors
|
||||
- type: entity
|
||||
parent: HighSecDoor
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
id: ArcadeBase
|
||||
description: An arcade cabinet.
|
||||
name: arcade
|
||||
parent: ComputerBase
|
||||
parent: BaseComputer
|
||||
components:
|
||||
- type: ApcPowerReceiver
|
||||
powerLoad: 350
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
- type: entity
|
||||
abstract: true
|
||||
parent: ComputerFrame
|
||||
id: ComputerBase
|
||||
id: BaseComputer
|
||||
name: computer
|
||||
placement:
|
||||
mode: SnapgridCenter
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
- type: entity
|
||||
parent: ComputerBase
|
||||
parent: BaseComputer
|
||||
id: ComputerAlert
|
||||
name: alerts computer
|
||||
description: Used to access the station's automated alert system.
|
||||
@@ -13,8 +13,33 @@
|
||||
board: AlertsComputerCircuitboard
|
||||
|
||||
- type: entity
|
||||
parent: ComputerBase
|
||||
id: ComputerShuttleBase
|
||||
parent: BaseComputer
|
||||
id: ComputerEmergencyShuttle
|
||||
name: emergency shuttle console
|
||||
description: Handles authorization to early launch the shuttle.
|
||||
components:
|
||||
- type: AccessReader
|
||||
access:
|
||||
- [ EmergencyShuttleLaunch ]
|
||||
- type: EmergencyShuttleConsole
|
||||
- type: ActivatableUI
|
||||
key: enum.EmergencyConsoleUiKey.Key
|
||||
- type: ActivatableUIRequiresPower
|
||||
- type: UserInterface
|
||||
interfaces:
|
||||
- key: enum.EmergencyConsoleUiKey.Key
|
||||
type: EmergencyConsoleBoundUserInterface
|
||||
- type: ExtensionCableReceiver
|
||||
- type: PointLight
|
||||
radius: 1.5
|
||||
energy: 1.6
|
||||
color: "#43ccb5"
|
||||
- type: Rotatable
|
||||
rotateWhileAnchored: true
|
||||
|
||||
- type: entity
|
||||
parent: BaseComputer
|
||||
id: BaseComputerShuttle
|
||||
name: shuttle console
|
||||
description: Used to pilot a shuttle.
|
||||
abstract: true
|
||||
@@ -37,7 +62,7 @@
|
||||
rotateWhileAnchored: true
|
||||
|
||||
- type: entity
|
||||
parent: ComputerShuttleBase
|
||||
parent: BaseComputerShuttle
|
||||
id: ComputerShuttle
|
||||
name: shuttle console
|
||||
description: Used to pilot a shuttle.
|
||||
@@ -51,7 +76,7 @@
|
||||
board: ShuttleConsoleCircuitboard
|
||||
|
||||
- type: entity
|
||||
parent: ComputerShuttleBase
|
||||
parent: BaseComputerShuttle
|
||||
id: ComputerShuttleSyndie
|
||||
name: syndicate shuttle console
|
||||
description: Used to pilot a syndicate shuttle.
|
||||
@@ -69,7 +94,7 @@
|
||||
color: "#c94242"
|
||||
|
||||
- type: entity
|
||||
parent: ComputerShuttleBase
|
||||
parent: BaseComputerShuttle
|
||||
id: ComputerShuttleCargo
|
||||
name: cargo shuttle console
|
||||
description: Used to pilot the cargo shuttle.
|
||||
@@ -88,7 +113,7 @@
|
||||
color: "#c94242"
|
||||
|
||||
- type: entity
|
||||
parent: ComputerBase
|
||||
parent: BaseComputer
|
||||
id: ComputerPowerMonitoring
|
||||
name: power monitoring computer
|
||||
description: It monitors power levels across the station.
|
||||
@@ -119,7 +144,7 @@
|
||||
type: PowerMonitoringConsoleBoundUserInterface
|
||||
|
||||
- type: entity
|
||||
parent: ComputerBase
|
||||
parent: BaseComputer
|
||||
id: ComputerMedicalRecords
|
||||
name: medical records computer
|
||||
description: This can be used to check medical records.
|
||||
@@ -137,7 +162,7 @@
|
||||
board: MedicalRecordsComputerCircuitboard
|
||||
|
||||
- type: entity
|
||||
parent: ComputerBase
|
||||
parent: BaseComputer
|
||||
id: ComputerCriminalRecords
|
||||
name: criminal records computer
|
||||
description: This can be used to check criminal records.
|
||||
@@ -155,7 +180,7 @@
|
||||
board: CriminalRecordsComputerCircuitboard
|
||||
|
||||
- type: entity
|
||||
parent: ComputerBase
|
||||
parent: BaseComputer
|
||||
id: ComputerCrewMonitoring
|
||||
name: crew monitoring console
|
||||
description: Used to monitor active health sensors built into most of the crew's uniforms.
|
||||
@@ -186,7 +211,7 @@
|
||||
range: 500
|
||||
|
||||
- type: entity
|
||||
parent: ComputerBase
|
||||
parent: BaseComputer
|
||||
id: ComputerResearchAndDevelopment
|
||||
name: R&D computer
|
||||
description: A computer used to interface with R&D tools.
|
||||
@@ -220,7 +245,7 @@
|
||||
color: "#b53ca1"
|
||||
|
||||
- type: entity
|
||||
parent: ComputerBase
|
||||
parent: BaseComputer
|
||||
id: ComputerId
|
||||
name: ID card computer
|
||||
description: Terminal for programming Nanotrasen employee ID cards to access parts of the station.
|
||||
@@ -269,7 +294,7 @@
|
||||
- EmagImmune
|
||||
|
||||
- type: entity
|
||||
parent: ComputerBase
|
||||
parent: BaseComputer
|
||||
id: computerBodyScanner
|
||||
name: body scanner computer
|
||||
description: A body scanner.
|
||||
@@ -297,7 +322,7 @@
|
||||
color: "#1f8c28"
|
||||
|
||||
- type: entity
|
||||
parent: ComputerBase
|
||||
parent: BaseComputer
|
||||
id: ComputerComms
|
||||
name: communications computer
|
||||
description: This can be used for various important functions. Still under development.
|
||||
@@ -351,7 +376,7 @@
|
||||
color: "#f71713"
|
||||
|
||||
- type: entity
|
||||
parent: ComputerBase
|
||||
parent: BaseComputer
|
||||
id: ComputerSolarControl
|
||||
name: solar control computer
|
||||
description: A controller for solar panel arrays.
|
||||
@@ -381,7 +406,7 @@
|
||||
|
||||
|
||||
- type: entity
|
||||
parent: ComputerBase
|
||||
parent: BaseComputer
|
||||
id: ComputerRadar
|
||||
name: mass scanner computer
|
||||
description: A computer for detecting nearby bodies, displaying them by position and mass.
|
||||
@@ -408,7 +433,7 @@
|
||||
|
||||
- type: entity
|
||||
id: ComputerCargoShuttle
|
||||
parent: ComputerBase
|
||||
parent: BaseComputer
|
||||
name: cargo shuttle computer
|
||||
description: Used to order the shuttle.
|
||||
components:
|
||||
@@ -436,7 +461,7 @@
|
||||
|
||||
- type: entity
|
||||
id: ComputerCargoOrders
|
||||
parent: ComputerBase
|
||||
parent: BaseComputer
|
||||
name: cargo request computer
|
||||
description: Used to order supplies and approve requests.
|
||||
components:
|
||||
@@ -463,7 +488,7 @@
|
||||
access: [["Cargo"]]
|
||||
|
||||
- type: entity
|
||||
parent: ComputerBase
|
||||
parent: BaseComputer
|
||||
id: ComputerSurveillanceCameraMonitor
|
||||
name: camera monitor
|
||||
description: A surveillance camera monitor. You're watching them. Maybe.
|
||||
@@ -492,7 +517,7 @@
|
||||
type: SurveillanceCameraMonitorBoundUserInterface
|
||||
|
||||
- type: entity
|
||||
parent: ComputerBase
|
||||
parent: BaseComputer
|
||||
id: ComputerSurveillanceWirelessCameraMonitor
|
||||
name: wireless camera monitor
|
||||
description: A wireless surveillance camera monitor. You're watching them. Maybe.
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
- nodevisfilter
|
||||
- showspread
|
||||
- showambient
|
||||
- showemergencyshuttle
|
||||
|
||||
- Flags: MAPPING
|
||||
Commands:
|
||||
|
||||
Reference in New Issue
Block a user