Move Icarus to dir & use timing instead of accum

This commit is contained in:
Morbo
2023-01-08 00:27:39 +03:00
parent 9c6a393617
commit 11c5ecf880
14 changed files with 60 additions and 485 deletions
@@ -1,15 +1,13 @@
using Content.Shared.Icarus;
using JetBrains.Annotations;
using Content.Shared.Corvax.Icarus;
using Robust.Client.GameObjects;
namespace Content.Client.Icarus;
namespace Content.Client.Corvax.Icarus;
[UsedImplicitly]
public sealed class IcarusTerminalBoundUserInterface : BoundUserInterface
{
private IcarusTerminalWindow? _window;
public IcarusTerminalBoundUserInterface([NotNull] ClientUserInterfaceComponent owner, [NotNull] object uiKey) : base(owner, uiKey)
public IcarusTerminalBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey)
{
}
@@ -1,9 +1,9 @@
using Content.Shared.Icarus;
using Content.Shared.Corvax.Icarus;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
namespace Content.Client.Icarus;
namespace Content.Client.Corvax.Icarus;
[GenerateTypedNameReferences]
public sealed partial class IcarusTerminalWindow : DefaultWindow
@@ -21,25 +21,30 @@ public sealed class SpawnIcarusCommand : IConsoleCommand
shell.WriteError("Incorrect number of arguments. " + Help);
return;
}
if (!int.TryParse(args[0], out var id))
if (!EntityUid.TryParse(args[0], out var uid))
{
shell.WriteLine($"{args[0]} is not a valid integer.");
shell.WriteError("Not a valid entity ID.");
return;
}
var gridId = new GridId(int.Parse(args[0]));
var mapManager = IoCManager.Resolve<IMapManager>();
if (mapManager.TryGetGrid(gridId, out var grid))
var entityManager = IoCManager.Resolve<IEntityManager>();
if (!entityManager.EntityExists(uid))
{
var icarusSystem = EntitySystem.Get<IcarusTerminalSystem>();
var coords = icarusSystem.FireBeam(grid.WorldAABB);
shell.WriteError("That grid does not exist.");
return;
}
var mapManager = IoCManager.Resolve<IMapManager>();
if (mapManager.TryGetGrid(uid, out var grid))
{
var icarusSystem = IoCManager.Resolve<IEntityManager>().System<IcarusTerminalSystem>();
var coords = icarusSystem.FireBeam(grid.LocalAABB);
shell.WriteLine($"Icarus was spawned: {coords.ToString()}");
}
else
{
shell.WriteError($"No grid exists with id {id}");
shell.WriteError($"No grid exists with ID {uid}");
}
}
}
@@ -27,6 +27,5 @@ public sealed class IcarusBeamComponent : Component
[DataField("flameRadius")]
public float FlameRadius = 4f;
[DataField("accumulator")]
public float Accumulator = 0f;
public TimeSpan LifetimeEnd;
}
@@ -2,31 +2,36 @@
using Content.Server.Atmos.EntitySystems;
using Content.Server.Ghost.Components;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Systems;
using Robust.Shared.Timing;
namespace Content.Server.Corvax.Icarus;
public sealed class IcarusBeamSystem : EntitySystem
{
[Dependency] private readonly IMapManager _map = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly EntityLookupSystem _lookup = default!;
[Dependency] private readonly FlammableSystem _flammable = default!;
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
public override void Update(float frameTime)
{
base.Update(frameTime);
var query = EntityQuery<IcarusBeamComponent, TransformComponent>(true);
foreach (var (beam, trans) in query)
foreach (var (comp, xform) in query)
{
AccumulateLifetime(beam, frameTime);
DestroyEntities(beam, trans);
BurnEntities(beam, trans);
DestroyEntities(comp, xform);
BurnEntities(comp, xform);
if (!beam.DestroyTiles)
continue;
if (comp.DestroyTiles)
DestroyTiles(comp, xform);
DestroyTiles(beam, trans);
if (_timing.CurTime > comp.LifetimeEnd)
QueueDel(comp.Owner);
}
}
@@ -38,6 +43,7 @@ public sealed class IcarusBeamSystem : EntitySystem
private void OnComponentInit(EntityUid uid, IcarusBeamComponent component, ComponentInit args)
{
component.LifetimeEnd = _timing.CurTime + component.Lifetime;
if (TryComp(uid, out PhysicsComponent? phys))
{
phys.LinearDamping = 0f;
@@ -46,22 +52,13 @@ public sealed class IcarusBeamSystem : EntitySystem
}
}
public void LaunchInDirection(EntityUid uid, Vector2 dir, IcarusBeamComponent? beam = null)
public void LaunchInDirection(EntityUid uid, Vector2 dir, IcarusBeamComponent? comp = null)
{
if (!Resolve(uid, ref beam))
if (!Resolve(uid, ref comp))
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);
}
if (TryComp(comp.Owner, out PhysicsComponent? phys))
_physics.ApplyLinearImpulse(phys, dir * comp.Speed);
}
/// <summary>
@@ -130,7 +127,7 @@ public sealed class IcarusBeamSystem : EntitySystem
private bool CanDestroy(IcarusBeamComponent component, EntityUid entity)
{
return entity != component.Owner &&
!EntityManager.HasComponent<IMapGridComponent>(entity) &&
!EntityManager.HasComponent<MapGridComponent>(entity) &&
!EntityManager.HasComponent<GhostComponent>(entity);
}
}
@@ -4,12 +4,15 @@ using Content.Server.GameTicking;
using Content.Server.Station.Components;
using Content.Server.Station.Systems;
using Content.Shared.Containers.ItemSlots;
using Content.Shared.Icarus;
using Content.Shared.Corvax.Icarus;
using Robust.Server.GameObjects;
using Robust.Shared.Audio;
using Robust.Shared.Containers;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Player;
using Robust.Shared.Random;
using Robust.Shared.Utility;
namespace Content.Server.Corvax.Icarus;
@@ -51,7 +54,8 @@ public sealed class IcarusTerminalSystem : EntitySystem
{
base.Initialize();
SubscribeLocalEvent<IcarusTerminalComponent, ComponentInit>(OnInit);
SubscribeLocalEvent<IcarusTerminalComponent, ItemSlotChangedEvent>(OnItemSlotChanged);
SubscribeLocalEvent<IcarusTerminalComponent, EntInsertedIntoContainerMessage>(OnItemSlotChanged);
SubscribeLocalEvent<IcarusTerminalComponent, EntRemovedFromContainerMessage>(OnItemSlotChanged);
// UI events
SubscribeLocalEvent<IcarusTerminalComponent, IcarusTerminalFireMessage>(OnFireButtonPressed);
@@ -64,7 +68,7 @@ public sealed class IcarusTerminalSystem : EntitySystem
UpdateUserInterface(component);
}
private void OnItemSlotChanged(EntityUid uid, IcarusTerminalComponent component, ref ItemSlotChangedEvent args)
private void OnItemSlotChanged(EntityUid uid, IcarusTerminalComponent component, ContainerModifiedMessage args)
{
UpdateStatus(component);
UpdateUserInterface(component);
@@ -87,7 +91,7 @@ public sealed class IcarusTerminalSystem : EntitySystem
Loc.GetString("icarus-fire-announcement", ("seconds", component.Timer)),
Loc.GetString("icarus-announce-sender"),
false,
Color.Red);
colorOverride: Color.Red);
SoundSystem.Play(component.AlertSound.GetSound(), Filter.Broadcast());
}
@@ -203,16 +207,20 @@ public sealed class IcarusTerminalSystem : EntitySystem
coords = new MapCoordinates(center + offset, _gameTicker.DefaultMap);
}
/// <summary>
/// Determine box of all stations and all of they grids. (copy-paste from pirate gamerule)
/// </summary>
/// <returns>Box of all station grids</returns>
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];
var xformQuery = GetEntityQuery<TransformComponent>();
var areas = _stationSystem.Stations.SelectMany(s =>
Comp<StationDataComponent>(s).Grids.Select(g =>
xformQuery.GetComponent(g).WorldMatrix.TransformBox(Comp<MapGridComponent>(g).LocalAABB))).ToArray();
var stationArea = areas[0];
for (var i = 1; i < areas.Length; i++)
{
stationArea.Union(areas[i]);
}
return stationArea;
}
@@ -1,45 +0,0 @@
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}");
}
}
}
@@ -1,32 +0,0 @@
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
View File
@@ -1,137 +0,0 @@
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);
}
}
@@ -1,219 +0,0 @@
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;
}
}
@@ -1,4 +1,4 @@
namespace Content.Shared.Icarus;
namespace Content.Shared.Corvax.Icarus;
/// <summary>
/// Used for Icarus terminal activation
@@ -1,7 +1,8 @@
using Content.Shared.Containers.ItemSlots;
using Content.Shared.Sound;
using Robust.Shared.Audio;
namespace Content.Shared.Icarus;
namespace Content.Shared.Corvax.Icarus;
/// <summary>
/// Used for Icarus terminal activation
@@ -1,7 +1,7 @@
using Content.Shared.Nuke;
using Robust.Shared.Serialization;
namespace Content.Shared.Icarus;
namespace Content.Shared.Corvax.Icarus;
[Serializable, NetSerializable]
public enum IcarusTerminalUiKey