Compare commits
39 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e3784bc237 | ||
|
|
acc2fd5a55 | ||
|
|
179116cc22 | ||
|
|
d07d294b91 | ||
|
|
f6e950cbf7 | ||
|
|
4410380abc | ||
|
|
ddd080f5f8 | ||
|
|
925ec94daf | ||
|
|
9e868145ff | ||
|
|
1e9966e946 | ||
|
|
a3dab14c6c | ||
|
|
586cf26f0f | ||
|
|
9b76fc7d74 | ||
|
|
24b3223403 | ||
|
|
a477e2f64f | ||
|
|
dc2f707f12 | ||
|
|
888f5714dc | ||
|
|
4793eaa494 | ||
|
|
9f0a8ca631 | ||
|
|
bdc027444f | ||
|
|
54834dbb07 | ||
|
|
8941193a0f | ||
|
|
993b4315bb | ||
|
|
c3d4e811db | ||
|
|
acfe951a0c | ||
|
|
c0794feada | ||
|
|
f6598c255b | ||
|
|
f570a9de6c | ||
|
|
4591833d81 | ||
|
|
a99c5c3488 | ||
|
|
6df2a1a571 | ||
|
|
ab44d02630 | ||
|
|
dafe921ac4 | ||
|
|
445a0d544f | ||
|
|
9864bae192 | ||
|
|
184bd103b0 | ||
|
|
a88eb38484 | ||
|
|
d88fe1c1c4 | ||
|
|
81fb2c02a2 |
58
Content.Client/Icarus/IcarusTerminalBoundUserInterface.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
using Content.Shared.Icarus;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
|
||||
namespace Content.Client.Icarus;
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed class IcarusTerminalBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
private IcarusTerminalWindow? _window;
|
||||
|
||||
public IcarusTerminalBoundUserInterface([NotNull] ClientUserInterfaceComponent owner, [NotNull] object uiKey) : base(owner, uiKey)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
_window = new IcarusTerminalWindow();
|
||||
_window.OnClose += Close;
|
||||
_window.OpenCentered();
|
||||
|
||||
_window.FireButtonPressed += OnFireButtonPressed;
|
||||
}
|
||||
|
||||
private void OnFireButtonPressed()
|
||||
{
|
||||
if (_window == null)
|
||||
return;
|
||||
|
||||
SendMessage(new IcarusTerminalFireMessage());
|
||||
}
|
||||
|
||||
protected override void UpdateState(BoundUserInterfaceState state)
|
||||
{
|
||||
base.UpdateState(state);
|
||||
|
||||
if (_window == null)
|
||||
return;
|
||||
|
||||
if (state is not IcarusTerminalUiState cast)
|
||||
return;
|
||||
|
||||
_window.UpdateState(cast);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
if (!disposing)
|
||||
return;
|
||||
|
||||
if (_window != null)
|
||||
_window.OnClose -= Close;
|
||||
|
||||
_window?.Dispose();
|
||||
}
|
||||
}
|
||||
21
Content.Client/Icarus/IcarusTerminalWindow.xaml
Normal file
@@ -0,0 +1,21 @@
|
||||
<DefaultWindow xmlns="https://spacestation14.io"
|
||||
Title="{Loc 'icarus-ui-window-title'}"
|
||||
MinSize="300 120">
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<Button Name="FireButton"
|
||||
Text="{Loc 'icarus-ui-fire-button'}"
|
||||
StyleClasses="Caution"
|
||||
MinHeight="50"
|
||||
Disabled="True" />
|
||||
<BoxContainer Name="TimerBox" Orientation="Horizontal" Visible="False">
|
||||
<Label Text="{Loc 'icarus-ui-timer-label'}" />
|
||||
<Label Text=" " />
|
||||
<Label Name="TimerValue" Text="-" />
|
||||
</BoxContainer>
|
||||
<BoxContainer Name="CooldownBox" Orientation="Horizontal" Visible="False">
|
||||
<Label Text="{Loc 'icarus-ui-cooldown-label'}" />
|
||||
<Label Text=" " />
|
||||
<Label Name="CooldownValue" Text="-" />
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</DefaultWindow>
|
||||
36
Content.Client/Icarus/IcarusTerminalWindow.xaml.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using Content.Shared.Icarus;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
namespace Content.Client.Icarus;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class IcarusTerminalWindow : DefaultWindow
|
||||
{
|
||||
public event Action? FireButtonPressed;
|
||||
|
||||
public IcarusTerminalWindow()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
FireButton.OnPressed += _ => FireButtonPressed?.Invoke();
|
||||
}
|
||||
|
||||
public void UpdateState(IcarusTerminalUiState state)
|
||||
{
|
||||
FireButton.Disabled = state.Status != IcarusTerminalStatus.FIRE_READY;
|
||||
TimerBox.Visible = state.Status == IcarusTerminalStatus.FIRE_PREPARING;
|
||||
CooldownBox.Visible = state.Status == IcarusTerminalStatus.COOLDOWN;
|
||||
|
||||
switch (state.Status)
|
||||
{
|
||||
case IcarusTerminalStatus.FIRE_PREPARING:
|
||||
TimerValue.Text = state.RemainingTime.ToString();
|
||||
break;
|
||||
case IcarusTerminalStatus.COOLDOWN:
|
||||
CooldownValue.Text = state.CooldownTime.ToString();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
45
Content.Server/Icarus/Commands/SpawnIcarusCommand.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
using Content.Server.Administration;
|
||||
using Content.Shared.Administration;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
namespace Content.Server.Icarus.Commands;
|
||||
|
||||
[UsedImplicitly]
|
||||
[AdminCommand(AdminFlags.Fun)]
|
||||
public sealed class SpawnIcarusCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "spawnicarus";
|
||||
public string Description => "Spawn Icarus beam and direct to specified grid center.";
|
||||
public string Help => "spawnicarus <gridId>";
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length != 1)
|
||||
{
|
||||
shell.WriteError("Incorrect number of arguments. " + Help);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!int.TryParse(args[0], out var id))
|
||||
{
|
||||
shell.WriteLine($"{args[0]} is not a valid integer.");
|
||||
return;
|
||||
}
|
||||
|
||||
var gridId = new GridId(int.Parse(args[0]));
|
||||
var mapManager = IoCManager.Resolve<IMapManager>();
|
||||
|
||||
if (mapManager.TryGetGrid(gridId, out var grid))
|
||||
{
|
||||
var icarusSystem = EntitySystem.Get<IcarusTerminalSystem>();
|
||||
var coords = icarusSystem.FireBeam(grid.WorldAABB);
|
||||
shell.WriteLine($"Icarus was spawned: {coords.ToString()}");
|
||||
}
|
||||
else
|
||||
{
|
||||
shell.WriteError($"No grid exists with id {id}");
|
||||
}
|
||||
}
|
||||
}
|
||||
32
Content.Server/Icarus/IcarusBeamComponent.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
namespace Content.Server.Icarus;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed class IcarusBeamComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Beam moving speed.
|
||||
/// </summary>
|
||||
[DataField("speed")]
|
||||
public float Speed = 1f;
|
||||
|
||||
/// <summary>
|
||||
/// The beam will be automatically cleaned up after this time.
|
||||
/// </summary>
|
||||
[DataField("lifetime")]
|
||||
public TimeSpan Lifetime = TimeSpan.FromSeconds(240);
|
||||
|
||||
/// <summary>
|
||||
/// With this set to true, beam will automatically set the tiles under them to space.
|
||||
/// </summary>
|
||||
[DataField("destroyTiles")]
|
||||
public bool DestroyTiles = true;
|
||||
|
||||
[DataField("destroyRadius")]
|
||||
public float DestroyRadius = 2f;
|
||||
|
||||
[DataField("flameRadius")]
|
||||
public float FlameRadius = 4f;
|
||||
|
||||
[DataField("accumulator")]
|
||||
public float Accumulator = 0f;
|
||||
}
|
||||
137
Content.Server/Icarus/IcarusBeamSystem.cs
Normal file
@@ -0,0 +1,137 @@
|
||||
using Content.Server.Atmos.Components;
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Server.Ghost.Components;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
namespace Content.Server.Icarus;
|
||||
|
||||
public sealed class IcarusBeamSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IMapManager _map = default!;
|
||||
[Dependency] private readonly EntityLookupSystem _lookup = default!;
|
||||
[Dependency] private readonly FlammableSystem _flammable = default!;
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
var query = EntityQuery<IcarusBeamComponent, TransformComponent>(true);
|
||||
foreach (var (beam, trans) in query)
|
||||
{
|
||||
AccumulateLifetime(beam, frameTime);
|
||||
DestroyEntities(beam, trans);
|
||||
BurnEntities(beam, trans);
|
||||
|
||||
if (!beam.DestroyTiles)
|
||||
continue;
|
||||
|
||||
DestroyTiles(beam, trans);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<IcarusBeamComponent, ComponentInit>(OnComponentInit);
|
||||
}
|
||||
|
||||
private void OnComponentInit(EntityUid uid, IcarusBeamComponent component, ComponentInit args)
|
||||
{
|
||||
if (TryComp(uid, out PhysicsComponent? phys))
|
||||
{
|
||||
phys.LinearDamping = 0f;
|
||||
phys.Friction = 0f;
|
||||
phys.BodyStatus = BodyStatus.InAir;
|
||||
}
|
||||
}
|
||||
|
||||
public void LaunchInDirection(EntityUid uid, Vector2 dir, IcarusBeamComponent? beam = null)
|
||||
{
|
||||
if (!Resolve(uid, ref beam))
|
||||
return;
|
||||
|
||||
if (TryComp(beam.Owner, out PhysicsComponent? phys))
|
||||
phys.ApplyLinearImpulse(dir * beam.Speed);
|
||||
}
|
||||
|
||||
private void AccumulateLifetime(IcarusBeamComponent beam, float frameTime)
|
||||
{
|
||||
beam.Accumulator += frameTime;
|
||||
if (beam.Accumulator > beam.Lifetime.TotalSeconds)
|
||||
{
|
||||
QueueDel(beam.Owner);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Destroy any grid tiles in beam radius.
|
||||
/// </summary>
|
||||
private void DestroyTiles(IcarusBeamComponent component, TransformComponent trans)
|
||||
{
|
||||
var radius = component.DestroyRadius;
|
||||
var worldPos = trans.WorldPosition;
|
||||
|
||||
var circle = new Circle(worldPos, radius);
|
||||
var box = new Box2(worldPos - radius, worldPos + radius);
|
||||
|
||||
foreach (var grid in _map.FindGridsIntersecting(trans.MapID, box))
|
||||
{
|
||||
// Bundle these together so we can use the faster helper to set tiles.
|
||||
var toDestroy = new List<(Vector2i, Tile)>();
|
||||
|
||||
foreach (var tile in grid.GetTilesIntersecting(circle))
|
||||
{
|
||||
if (tile.Tile.IsEmpty)
|
||||
continue;
|
||||
|
||||
toDestroy.Add((tile.GridIndices, Tile.Empty));
|
||||
}
|
||||
|
||||
grid.SetTiles(toDestroy);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle deleting entities in beam radius.
|
||||
/// </summary>
|
||||
private void DestroyEntities(IcarusBeamComponent component, TransformComponent trans)
|
||||
{
|
||||
var radius = component.DestroyRadius - 0.5f;
|
||||
foreach (var entity in _lookup.GetEntitiesInRange(trans.MapID, trans.WorldPosition, radius))
|
||||
{
|
||||
if (!CanDestroy(component, entity))
|
||||
continue;
|
||||
|
||||
QueueDel(entity);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle igniting flammable entities in beam radius.
|
||||
/// </summary>
|
||||
private void BurnEntities(IcarusBeamComponent component, TransformComponent trans)
|
||||
{
|
||||
var radius = component.FlameRadius * 2;
|
||||
foreach (var entity in _lookup.GetEntitiesInRange(trans.MapID, trans.WorldPosition, radius))
|
||||
{
|
||||
if (!CanDestroy(component, entity))
|
||||
continue;
|
||||
|
||||
if (!TryComp<FlammableComponent>(entity, out var flammable))
|
||||
continue;
|
||||
|
||||
flammable.FireStacks += 1;
|
||||
if (!flammable.OnFire)
|
||||
_flammable.Ignite(entity);
|
||||
}
|
||||
}
|
||||
|
||||
private bool CanDestroy(IcarusBeamComponent component, EntityUid entity)
|
||||
{
|
||||
return entity != component.Owner &&
|
||||
!EntityManager.HasComponent<IMapGridComponent>(entity) &&
|
||||
!EntityManager.HasComponent<GhostComponent>(entity);
|
||||
}
|
||||
}
|
||||
219
Content.Server/Icarus/IcarusTerminalSystem.cs
Normal file
@@ -0,0 +1,219 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Chat.Systems;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.Station.Components;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Shared.Containers.ItemSlots;
|
||||
using Content.Shared.Icarus;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.Icarus;
|
||||
|
||||
/// <summary>
|
||||
/// Handle Icarus activation terminal
|
||||
/// </summary>
|
||||
public sealed class IcarusTerminalSystem : EntitySystem
|
||||
{
|
||||
private const string IcarusBeamPrototypeId = "IcarusBeam";
|
||||
|
||||
[Dependency] private readonly ChatSystem _chatSystem = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IRobustRandom _robustRandom = default!;
|
||||
[Dependency] private readonly GameTicker _gameTicker = default!;
|
||||
[Dependency] private readonly StationSystem _stationSystem = default!;
|
||||
[Dependency] private readonly IcarusBeamSystem _icarusSystem = default!;
|
||||
[Dependency] private readonly UserInterfaceSystem _userInterfaceSystem = default!;
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
var query = EntityQuery<IcarusTerminalComponent>();
|
||||
foreach (var terminal in query)
|
||||
{
|
||||
switch (terminal.Status)
|
||||
{
|
||||
case IcarusTerminalStatus.FIRE_PREPARING:
|
||||
TickTimer(terminal, frameTime);
|
||||
break;
|
||||
case IcarusTerminalStatus.COOLDOWN:
|
||||
TickCooldown(terminal, frameTime);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<IcarusTerminalComponent, ComponentInit>(OnInit);
|
||||
SubscribeLocalEvent<IcarusTerminalComponent, ItemSlotChangedEvent>(OnItemSlotChanged);
|
||||
|
||||
// UI events
|
||||
SubscribeLocalEvent<IcarusTerminalComponent, IcarusTerminalFireMessage>(OnFireButtonPressed);
|
||||
}
|
||||
|
||||
private void OnInit(EntityUid uid, IcarusTerminalComponent component, ComponentInit args)
|
||||
{
|
||||
component.RemainingTime = component.Timer;
|
||||
UpdateStatus(component);
|
||||
UpdateUserInterface(component);
|
||||
}
|
||||
|
||||
private void OnItemSlotChanged(EntityUid uid, IcarusTerminalComponent component, ref ItemSlotChangedEvent args)
|
||||
{
|
||||
UpdateStatus(component);
|
||||
UpdateUserInterface(component);
|
||||
}
|
||||
|
||||
private void OnFireButtonPressed(EntityUid uid, IcarusTerminalComponent component, IcarusTerminalFireMessage args)
|
||||
{
|
||||
Fire(component);
|
||||
}
|
||||
|
||||
private void Fire(IcarusTerminalComponent component)
|
||||
{
|
||||
if (component.Status == IcarusTerminalStatus.FIRE_PREPARING)
|
||||
return;
|
||||
|
||||
component.RemainingTime = component.Timer;
|
||||
component.Status = IcarusTerminalStatus.FIRE_PREPARING;
|
||||
|
||||
_chatSystem.DispatchStationAnnouncement(component.Owner,
|
||||
Loc.GetString("icarus-fire-announcement", ("seconds", component.Timer)),
|
||||
Loc.GetString("icarus-announce-sender"),
|
||||
false,
|
||||
Color.Red);
|
||||
SoundSystem.Play(component.AlertSound.GetSound(), Filter.Broadcast());
|
||||
}
|
||||
|
||||
private void UpdateStatus(IcarusTerminalComponent component)
|
||||
{
|
||||
switch (component.Status)
|
||||
{
|
||||
case IcarusTerminalStatus.AWAIT_DISKS:
|
||||
if (IsAccessGranted(component.Owner))
|
||||
Authorize(component);
|
||||
break;
|
||||
case IcarusTerminalStatus.FIRE_READY:
|
||||
{
|
||||
if (!IsAccessGranted(component.Owner))
|
||||
{
|
||||
component.Status = IcarusTerminalStatus.AWAIT_DISKS;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateUserInterface(IcarusTerminalComponent component)
|
||||
{
|
||||
_userInterfaceSystem.TrySetUiState(component.Owner, IcarusTerminalUiKey.Key, new IcarusTerminalUiState(
|
||||
component.Status,
|
||||
(int) component.RemainingTime,
|
||||
(int) component.CooldownTime)
|
||||
);
|
||||
}
|
||||
|
||||
private bool IsAccessGranted(EntityUid uid)
|
||||
{
|
||||
return Comp<ItemSlotsComponent>(uid).Slots.Values.All(v => v.HasItem);
|
||||
}
|
||||
|
||||
private bool CanFire(EntityUid uid, IcarusTerminalComponent component)
|
||||
{
|
||||
return IsAccessGranted(uid) &&
|
||||
component.Status == IcarusTerminalStatus.FIRE_READY;
|
||||
}
|
||||
|
||||
private void Authorize(IcarusTerminalComponent component)
|
||||
{
|
||||
component.Status = IcarusTerminalStatus.FIRE_READY;
|
||||
|
||||
SoundSystem.Play(component.AccessGrantedSound.GetSound(), Filter.Pvs(component.Owner), component.Owner);
|
||||
|
||||
if (!component.AuthorizationNotified)
|
||||
{
|
||||
_chatSystem.DispatchStationAnnouncement(component.Owner, Loc.GetString("icarus-authorized-announcement"),
|
||||
playDefaultSound: false); // TODO: Just pass custom sound path after PR accepting
|
||||
SoundSystem.Play("/Audio/Misc/notice1.ogg",
|
||||
Filter.Broadcast());
|
||||
component.AuthorizationNotified = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void TickCooldown(IcarusTerminalComponent component, float frameTime)
|
||||
{
|
||||
component.CooldownTime -= frameTime;
|
||||
if (component.CooldownTime <= 0)
|
||||
{
|
||||
component.CooldownTime = 0;
|
||||
component.Status = IcarusTerminalStatus.AWAIT_DISKS;
|
||||
UpdateStatus(component);
|
||||
}
|
||||
|
||||
UpdateUserInterface(component);
|
||||
}
|
||||
|
||||
private void TickTimer(IcarusTerminalComponent component, float frameTime)
|
||||
{
|
||||
component.RemainingTime -= frameTime;
|
||||
if (component.RemainingTime <= 0)
|
||||
{
|
||||
component.RemainingTime = 0;
|
||||
ActivateBeamOnStation(component);
|
||||
}
|
||||
|
||||
UpdateUserInterface(component);
|
||||
}
|
||||
|
||||
private void ActivateBeamOnStation(IcarusTerminalComponent component)
|
||||
{
|
||||
component.Status = IcarusTerminalStatus.COOLDOWN;
|
||||
component.CooldownTime = component.Cooldown;
|
||||
|
||||
SoundSystem.Play(component.FireSound.GetSound(), Filter.Broadcast());
|
||||
FireBeam(GetStationArea());
|
||||
}
|
||||
|
||||
public MapCoordinates FireBeam(Box2 area)
|
||||
{
|
||||
TryGetBeamSpawnLocation(area, out var coords, out var offset);
|
||||
Logger.DebugS("icarus", $"Try spawn beam on coords: {coords.ToString()}");
|
||||
var entUid = Spawn(IcarusBeamPrototypeId, coords);
|
||||
_icarusSystem.LaunchInDirection(entUid, -offset.Normalized);
|
||||
return coords;
|
||||
}
|
||||
|
||||
private void TryGetBeamSpawnLocation(Box2 area, out MapCoordinates coords,
|
||||
out Vector2 offset)
|
||||
{
|
||||
coords = MapCoordinates.Nullspace;
|
||||
offset = Vector2.Zero;
|
||||
|
||||
var center = area.Center;
|
||||
var distance = (area.TopRight - center).Length;
|
||||
var angle = new Angle(_robustRandom.NextFloat() * MathF.Tau);
|
||||
|
||||
offset = angle.RotateVec(new Vector2(distance, 0));
|
||||
coords = new MapCoordinates(center + offset, _gameTicker.DefaultMap);
|
||||
}
|
||||
|
||||
private Box2 GetStationArea()
|
||||
{
|
||||
var areas = _stationSystem.Stations.SelectMany(x =>
|
||||
Comp<StationDataComponent>(x).Grids.Select(x => _mapManager.GetGridComp(x).Grid.WorldAABB)).ToArray();
|
||||
var stationArea = areas[0];
|
||||
|
||||
for (var i = 1; i < areas.Length; i++)
|
||||
{
|
||||
stationArea.Union(areas[i]);
|
||||
}
|
||||
|
||||
return stationArea;
|
||||
}
|
||||
}
|
||||
7
Content.Shared/Icarus/IcarusKeyComponent.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace Content.Shared.Icarus;
|
||||
|
||||
/// <summary>
|
||||
/// Used for Icarus terminal activation
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class IcarusKeyComponent : Component {}
|
||||
64
Content.Shared/Icarus/IcarusTerminalComponent.cs
Normal file
@@ -0,0 +1,64 @@
|
||||
using Content.Shared.Containers.ItemSlots;
|
||||
using Content.Shared.Sound;
|
||||
|
||||
namespace Content.Shared.Icarus;
|
||||
|
||||
/// <summary>
|
||||
/// Used for Icarus terminal activation
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class IcarusTerminalComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Default fire timer value in seconds.
|
||||
/// </summary>
|
||||
[DataField("timer")]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public int Timer = 25;
|
||||
|
||||
/// <summary>
|
||||
/// How long until the beam can arm again after fire.
|
||||
/// </summary>
|
||||
[DataField("cooldown")]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public int Cooldown = 360;
|
||||
|
||||
/// <summary>
|
||||
/// Current status of a terminal.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public IcarusTerminalStatus Status = IcarusTerminalStatus.AWAIT_DISKS;
|
||||
|
||||
/// <summary>
|
||||
/// Time until beam will be spawned in seconds.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public float RemainingTime;
|
||||
|
||||
/// <summary>
|
||||
/// Time until beam cooldown will expire in seconds.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public float CooldownTime;
|
||||
|
||||
[DataField("alertSound")]
|
||||
public SoundSpecifier AlertSound = new SoundPathSpecifier("/Audio/Corvax/AssaultOperatives/icarus_alarm.ogg");
|
||||
|
||||
[DataField("accessGrantedSound")]
|
||||
public SoundSpecifier AccessGrantedSound = new SoundPathSpecifier("/Audio/Machines/Nuke/confirm_beep.ogg");
|
||||
|
||||
[DataField("fireSound")]
|
||||
public SoundSpecifier FireSound = new SoundPathSpecifier("/Audio/Corvax/AssaultOperatives/sunbeam_fire.ogg");
|
||||
|
||||
/// <summary>
|
||||
/// Check if already notified about system authorization
|
||||
/// </summary>
|
||||
public bool AuthorizationNotified = false;
|
||||
|
||||
protected override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
Owner.EnsureComponentWarn<ItemSlotsComponent>();
|
||||
}
|
||||
}
|
||||
38
Content.Shared/Icarus/SharedIcarus.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using Content.Shared.Nuke;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Icarus;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum IcarusTerminalUiKey
|
||||
{
|
||||
Key,
|
||||
}
|
||||
|
||||
public enum IcarusTerminalStatus : byte
|
||||
{
|
||||
AWAIT_DISKS,
|
||||
FIRE_READY,
|
||||
FIRE_PREPARING,
|
||||
COOLDOWN
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class IcarusTerminalUiState : BoundUserInterfaceState
|
||||
{
|
||||
public IcarusTerminalStatus Status { get; }
|
||||
public int RemainingTime { get; }
|
||||
public int CooldownTime { get; }
|
||||
|
||||
public IcarusTerminalUiState(IcarusTerminalStatus status, int remainingTime, int cooldownTime)
|
||||
{
|
||||
Status = status;
|
||||
RemainingTime = remainingTime;
|
||||
CooldownTime = cooldownTime;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class IcarusTerminalFireMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
}
|
||||
BIN
Resources/Audio/Corvax/AssaultOperatives/goldeneyealarm.ogg
Normal file
BIN
Resources/Audio/Corvax/AssaultOperatives/icarus_alarm.ogg
Normal file
BIN
Resources/Audio/Corvax/AssaultOperatives/sunbeam_fire.ogg
Normal file
BIN
Resources/Audio/Corvax/AssaultOperatives/sunbeam_loop.ogg
Normal file
@@ -0,0 +1,16 @@
|
||||
# Announce
|
||||
icarus-announce-sender = Icarus Defence Network
|
||||
icarus-authorized-announcement = UNAUTHORISED KEYCARD UPLOAD DETECTED. ALL KEYCARDS UPLOADED!
|
||||
icarus-fire-announcement = /// ICARUS DEFENCE NETWORK BREACHED ///
|
||||
Unauthorised Icarus Defence Network access detected.
|
||||
ICARUS online.
|
||||
Targeting system override detected...
|
||||
New target: /NTSS14/
|
||||
ICARUS firing protocols activated.
|
||||
ETA to fire: { $seconds } seconds.
|
||||
|
||||
# UI
|
||||
icarus-ui-window-title = Icarus Terminal
|
||||
icarus-ui-fire-button = Fire
|
||||
icarus-ui-timer-label = Time until the shot:
|
||||
icarus-ui-cooldown-label = Cooldown:
|
||||
@@ -0,0 +1,20 @@
|
||||
# Announce
|
||||
icarus-announce-sender = Оборонная сеть "Икарус"
|
||||
icarus-authorized-announcement = ОБНАРУЖЕНА НЕСАНКЦИОНИРОВАННАЯ ЗАГРУЗКА КЛЮЧ-КАРТ. ВСЕ КЛЮЧИ ЗАГРУЖЕНЫ!
|
||||
icarus-fire-announcement = /// ВЗЛОМ ЗАЩИЩЕННОЙ СЕТИ "ИКАРУС" ///
|
||||
Обнаружен несанкционированный доступ к оборонной сеть "Икарус"
|
||||
ИКАРУС онлайн.
|
||||
Обнаружено переопределение системы нацеливания...
|
||||
Новая цель: /NTSS14/
|
||||
Активированы протоколы стрельбы ИКАРУС.
|
||||
РАСЧЕТНОЕ ВРЕМЯ выстера: { $seconds } { $seconds ->
|
||||
[one] секунду
|
||||
[few] секунды
|
||||
*[other] секунд
|
||||
}.
|
||||
|
||||
# UI
|
||||
icarus-ui-window-title = Терминал Icarus
|
||||
icarus-ui-fire-button = Огонь
|
||||
icarus-ui-timer-label = Время до выстрела:
|
||||
icarus-ui-cooldown-label = Перезарядка:
|
||||
41
Resources/Prototypes/Corvax/AssaultOperatives/beam.yml
Normal file
@@ -0,0 +1,41 @@
|
||||
- type: entity
|
||||
id: IcarusBeam
|
||||
name: icarus beam
|
||||
description: A beam of light from the sun.
|
||||
components:
|
||||
- type: Clickable
|
||||
- type: MovementIgnoreGravity
|
||||
- type: Sprite
|
||||
sprite: Corvax/AssaultOperatives/sunray.rsi
|
||||
drawdepth: Effects
|
||||
noRot: true
|
||||
scale: 5, 5
|
||||
layers:
|
||||
- state: sunray_splash
|
||||
- state: sunray
|
||||
offset: 0, 1
|
||||
- state: sunray_muzzle
|
||||
offset: 0, 2
|
||||
- type: IcarusBeam
|
||||
- type: AmbientSound
|
||||
range: 14
|
||||
sound:
|
||||
path: /Audio/Corvax/AssaultOperatives/sunbeam_loop.ogg
|
||||
- type: Physics
|
||||
bodyType: Dynamic
|
||||
linearDamping: 0
|
||||
- type: PointLight
|
||||
radius: 12
|
||||
color: yellow
|
||||
energy: 10.0
|
||||
- type: Fixtures
|
||||
fixtures:
|
||||
- shape:
|
||||
!type:PhysShapeCircle
|
||||
radius: 2
|
||||
mass: 1
|
||||
hard: false
|
||||
mask:
|
||||
- AllMask
|
||||
layer:
|
||||
- AllMask
|
||||
11
Resources/Prototypes/Corvax/AssaultOperatives/cards.yml
Normal file
@@ -0,0 +1,11 @@
|
||||
- type: entity
|
||||
parent: BaseItem
|
||||
id: IcarusKey
|
||||
name: icarus authentication keycard
|
||||
description: A high profile authentication keycard to Nanotrasen's Icarus secured network.
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Corvax/AssaultOperatives/goldeneye.rsi
|
||||
layers:
|
||||
- state: goldeneye_key
|
||||
- type: IcarusKey
|
||||
50
Resources/Prototypes/Corvax/AssaultOperatives/terminal.yml
Normal file
@@ -0,0 +1,50 @@
|
||||
- type: entity
|
||||
parent: BaseComputer
|
||||
id: ComputerIcarus
|
||||
name: icarus terminal
|
||||
description: An ominous terminal with some ports and keypads, the screen is scrolling with illegible nonsense. It has a strange marking on the side, a red ring with a gold circle within.
|
||||
components:
|
||||
- type: Appearance
|
||||
visuals:
|
||||
- type: ComputerVisualizer
|
||||
key: goldeneye_key
|
||||
screen: goldeneye
|
||||
- type: Computer
|
||||
board: CommsComputerCircuitboard # TODO: Create uniq item
|
||||
- type: PointLight
|
||||
radius: 1.5
|
||||
energy: 1.6
|
||||
color: "#b5a13c"
|
||||
- type: ItemSlots
|
||||
slots:
|
||||
firstKeySlot:
|
||||
ejectSound: /Audio/Machines/id_swipe.ogg
|
||||
insertSound: /Audio/Machines/Nuke/general_beep.ogg
|
||||
ejectOnBreak: true
|
||||
swap: false
|
||||
whitelist:
|
||||
components:
|
||||
- IcarusKey
|
||||
secondKeySlot:
|
||||
ejectSound: /Audio/Machines/id_swipe.ogg
|
||||
insertSound: /Audio/Machines/Nuke/general_beep.ogg
|
||||
ejectOnBreak: true
|
||||
swap: false
|
||||
whitelist:
|
||||
components:
|
||||
- IcarusKey
|
||||
thirdKeySlot:
|
||||
ejectSound: /Audio/Machines/id_swipe.ogg
|
||||
insertSound: /Audio/Machines/Nuke/general_beep.ogg
|
||||
ejectOnBreak: true
|
||||
swap: false
|
||||
whitelist:
|
||||
components:
|
||||
- IcarusKey
|
||||
- type: IcarusTerminal
|
||||
- type: UserInterface
|
||||
interfaces:
|
||||
- key: enum.IcarusTerminalUiKey.Key
|
||||
type: IcarusTerminalBoundUserInterface
|
||||
- type: ActivatableUI
|
||||
key: enum.IcarusTerminalUiKey.Key
|
||||
|
After Width: | Height: | Size: 795 B |
|
After Width: | Height: | Size: 705 B |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 7.9 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
@@ -0,0 +1,72 @@
|
||||
{
|
||||
"version": 1,
|
||||
"license": "CC-BY-SA-3.0",
|
||||
"copyright": "Taken from skyrat-tg at commit https://github.com/Skyrat-SS13/Skyrat-tg/commit/e2a68d3b15f0daff453ff14a1745f68eeb76b765",
|
||||
"size": {
|
||||
"x": 32,
|
||||
"y": 32
|
||||
},
|
||||
"states": [
|
||||
{
|
||||
"name": "interrogator_off",
|
||||
"delays": [
|
||||
[
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "interrogator_on",
|
||||
"delays": [
|
||||
[
|
||||
0.1,
|
||||
0.08,
|
||||
0.08,
|
||||
0.08,
|
||||
0.08,
|
||||
0.08,
|
||||
0.08,
|
||||
0.08,
|
||||
0.08,
|
||||
0.08,
|
||||
0.08,
|
||||
0.08,
|
||||
0.08,
|
||||
0.08,
|
||||
0.08,
|
||||
0.08,
|
||||
0.08,
|
||||
0.08,
|
||||
0.08,
|
||||
0.08,
|
||||
0.08,
|
||||
0.08,
|
||||
0.08
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "interrogator_open"
|
||||
},
|
||||
{
|
||||
"name": "interrogator_closed",
|
||||
"delays": [
|
||||
[
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "goldeneye_key"
|
||||
},
|
||||
{
|
||||
"name": "goldeneye_terminal"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"version": 1,
|
||||
"license": "CC-BY-SA-3.0",
|
||||
"copyright": "Taken from skyrat-tg at commit https://github.com/Skyrat-SS13/Skyrat-tg/commit/e2a68d3b15f0daff453ff14a1745f68eeb76b765",
|
||||
"size": {
|
||||
"x": 32,
|
||||
"y": 32
|
||||
},
|
||||
"states": [
|
||||
{
|
||||
"name": "sunray_splash",
|
||||
"delays": [
|
||||
[
|
||||
0.15,
|
||||
0.15,
|
||||
0.15
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "sunray",
|
||||
"delays": [
|
||||
[
|
||||
0.15,
|
||||
0.15,
|
||||
0.15
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "sunray_muzzle",
|
||||
"delays": [
|
||||
[
|
||||
0.15,
|
||||
0.15,
|
||||
0.15
|
||||
]
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 548 B |
|
After Width: | Height: | Size: 421 B |
|
After Width: | Height: | Size: 407 B |
@@ -1562,6 +1562,15 @@
|
||||
},
|
||||
{
|
||||
"name": "detective_television"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "goldeneye"
|
||||
},
|
||||
{
|
||||
"name": "goldeneye_key"
|
||||
},
|
||||
{
|
||||
"name": "goldeneye_key_off"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||