Radiation pulse ECS (#10641)

This commit is contained in:
Alex Evgrashin
2022-08-31 12:24:21 +02:00
committed by GitHub
parent ccb240ccca
commit cad6c760ad
18 changed files with 146 additions and 258 deletions

View File

@@ -1,37 +0,0 @@
using Content.Shared.Radiation;
namespace Content.Client.Radiation
{
[RegisterComponent]
[ComponentReference(typeof(SharedRadiationPulseComponent))]
public sealed class RadiationPulseComponent : SharedRadiationPulseComponent
{
private bool _draw;
private bool _decay;
private float _range;
private TimeSpan _startTime;
private TimeSpan _endTime;
public override float Range => _range;
public override TimeSpan StartTime => _startTime;
public override TimeSpan EndTime => _endTime;
public override bool Draw => _draw;
public override bool Decay => _decay;
public override void HandleComponentState(ComponentState? curState, ComponentState? nextState)
{
base.HandleComponentState(curState, nextState);
if (curState is not RadiationPulseState state)
{
return;
}
_range = state.Range;
_draw = state.Draw;
_decay = state.Decay;
_startTime = state.StartTime;
_endTime = state.EndTime;
}
}
}

View File

@@ -1,4 +1,5 @@
using System.Linq; using System.Linq;
using Content.Shared.Radiation.Components;
using Robust.Client.Graphics; using Robust.Client.Graphics;
using Robust.Shared.Enums; using Robust.Shared.Enums;
using Robust.Shared.Map; using Robust.Shared.Map;
@@ -18,8 +19,6 @@ namespace Content.Client.Radiation
public override OverlaySpace Space => OverlaySpace.WorldSpace; public override OverlaySpace Space => OverlaySpace.WorldSpace;
public override bool RequestScreenTexture => true; public override bool RequestScreenTexture => true;
private TimeSpan _lastTick = default;
private readonly ShaderInstance _baseShader; private readonly ShaderInstance _baseShader;
private readonly Dictionary<EntityUid, (ShaderInstance shd, RadiationShaderInstance instance)> _pulses = new(); private readonly Dictionary<EntityUid, (ShaderInstance shd, RadiationShaderInstance instance)> _pulses = new();
@@ -52,7 +51,7 @@ namespace Content.Client.Radiation
shd?.SetParameter("renderScale", viewport.RenderScale); shd?.SetParameter("renderScale", viewport.RenderScale);
shd?.SetParameter("positionInput", tempCoords); shd?.SetParameter("positionInput", tempCoords);
shd?.SetParameter("range", instance.Range); shd?.SetParameter("range", instance.Range);
var life = (_lastTick - instance.Start) / (instance.End - instance.Start); var life = (_gameTiming.RealTime - instance.Start).TotalSeconds / instance.Duration;
shd?.SetParameter("life", (float) life); shd?.SetParameter("life", (float) life);
// There's probably a very good reason not to do this. // There's probably a very good reason not to do this.
@@ -75,8 +74,6 @@ namespace Content.Client.Radiation
return; return;
} }
_lastTick = _gameTiming.CurTime;
var currentEyeLoc = currentEye.Position; var currentEyeLoc = currentEye.Position;
var pulses = _entityManager.EntityQuery<RadiationPulseComponent>(); var pulses = _entityManager.EntityQuery<RadiationPulseComponent>();
@@ -92,9 +89,9 @@ namespace Content.Client.Radiation
_baseShader.Duplicate(), _baseShader.Duplicate(),
new RadiationShaderInstance( new RadiationShaderInstance(
_entityManager.GetComponent<TransformComponent>(pulseEntity).MapPosition.Position, _entityManager.GetComponent<TransformComponent>(pulseEntity).MapPosition.Position,
pulse.Range, pulse.VisualRange,
pulse.StartTime, pulse.StartTime,
pulse.EndTime pulse.VisualDuration
) )
) )
); );
@@ -110,7 +107,7 @@ namespace Content.Client.Radiation
{ {
var shaderInstance = _pulses[pulseEntity]; var shaderInstance = _pulses[pulseEntity];
shaderInstance.instance.CurrentMapCoords = _entityManager.GetComponent<TransformComponent>(pulseEntity).MapPosition.Position; shaderInstance.instance.CurrentMapCoords = _entityManager.GetComponent<TransformComponent>(pulseEntity).MapPosition.Position;
shaderInstance.instance.Range = pulse.Range; shaderInstance.instance.Range = pulse.VisualRange;
} else { } else {
_pulses[pulseEntity].shd.Dispose(); _pulses[pulseEntity].shd.Dispose();
_pulses.Remove(pulseEntity); _pulses.Remove(pulseEntity);
@@ -124,12 +121,12 @@ namespace Content.Client.Radiation
return _entityManager.GetComponent<TransformComponent>(pulseEntity).MapID == currentEyeLoc.MapId && _entityManager.GetComponent<TransformComponent>(pulseEntity).Coordinates.InRange(_entityManager, EntityCoordinates.FromMap(_entityManager, _entityManager.GetComponent<TransformComponent>(pulseEntity).ParentUid, currentEyeLoc), MaxDist); return _entityManager.GetComponent<TransformComponent>(pulseEntity).MapID == currentEyeLoc.MapId && _entityManager.GetComponent<TransformComponent>(pulseEntity).Coordinates.InRange(_entityManager, EntityCoordinates.FromMap(_entityManager, _entityManager.GetComponent<TransformComponent>(pulseEntity).ParentUid, currentEyeLoc), MaxDist);
} }
private sealed record RadiationShaderInstance(Vector2 CurrentMapCoords, float Range, TimeSpan Start, TimeSpan End) private sealed record RadiationShaderInstance(Vector2 CurrentMapCoords, float Range, TimeSpan Start, float Duration)
{ {
public Vector2 CurrentMapCoords = CurrentMapCoords; public Vector2 CurrentMapCoords = CurrentMapCoords;
public float Range = Range; public float Range = Range;
public TimeSpan Start = Start; public TimeSpan Start = Start;
public TimeSpan End = End; public float Duration = Duration;
}; };
} }
} }

View File

@@ -1,103 +0,0 @@
using Content.Shared.Radiation;
using Robust.Shared.Audio;
using Robust.Shared.Player;
using Robust.Shared.Random;
using Robust.Shared.Timing;
namespace Content.Server.Radiation
{
[RegisterComponent]
[ComponentReference(typeof(SharedRadiationPulseComponent))]
public sealed class RadiationPulseComponent : SharedRadiationPulseComponent
{
[Dependency] private readonly IEntityManager _entMan = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IRobustRandom _random = default!;
private float _duration;
private float _range = 5f;
private TimeSpan _startTime;
private TimeSpan _endTime;
private bool _draw = true;
private bool _decay = true;
/// <summary>
/// Whether the entity will delete itself after a certain duration defined by
/// <see cref="MinPulseLifespan"/> and <see cref="MaxPulseLifespan"/>
/// </summary>
[DataField("decay")]
public override bool Decay
{
get => _decay;
set
{
_decay = value;
Dirty();
}
}
[DataField("minPulseLifespan")]
public float MinPulseLifespan { get; set; } = 0.8f;
[DataField("maxPulseLifespan")]
public float MaxPulseLifespan { get; set; } = 2.5f;
[DataField("sound")] public SoundSpecifier Sound { get; set; } = new SoundCollectionSpecifier("RadiationPulse");
[DataField("range")]
public override float Range
{
get => _range;
set
{
_range = value;
Dirty();
}
}
[DataField("draw")]
public override bool Draw
{
get => _draw;
set
{
_draw = value;
Dirty();
}
}
public override TimeSpan StartTime => _startTime;
public override TimeSpan EndTime => _endTime;
public void DoPulse()
{
if (Decay)
{
var currentTime = _gameTiming.CurTime;
_startTime = currentTime;
_duration = _random.NextFloat() * (MaxPulseLifespan - MinPulseLifespan) + MinPulseLifespan;
_endTime = currentTime + TimeSpan.FromSeconds(_duration);
}
SoundSystem.Play(Sound.GetSound(), Filter.Pvs(Owner), Owner);
Dirty();
}
public override ComponentState GetComponentState()
{
return new RadiationPulseState(_range, Draw, Decay, _startTime, _endTime);
}
public void Update(float frameTime)
{
if (!Decay || _entMan.Deleted(Owner))
return;
if (_duration <= 0f)
_entMan.QueueDeleteEntity(Owner);
_duration -= frameTime;
}
}
}

View File

@@ -1,38 +0,0 @@
using Content.Server.Radiation.Systems;
using JetBrains.Annotations;
namespace Content.Server.Radiation
{
[UsedImplicitly]
public sealed class RadiationPulseSystem : EntitySystem
{
[Dependency] private readonly RadiationSystem _radiation = default!;
private const float RadiationCooldown = 1.0f;
private float _accumulator;
public override void Update(float frameTime)
{
base.Update(frameTime);
_accumulator += frameTime;
while (_accumulator > RadiationCooldown)
{
_accumulator -= RadiationCooldown;
// All code here runs effectively every RadiationCooldown seconds, so use that as the "frame time".
foreach (var comp in EntityManager.EntityQuery<RadiationPulseComponent>())
{
comp.Update(RadiationCooldown);
var ent = comp.Owner;
if (Deleted(ent)) continue;
var cords = Transform(ent).MapPosition;
_radiation.IrradiateRange(cords, comp.Range, comp.RadsPerSecond, RadiationCooldown);
}
}
}
}
}

View File

@@ -7,6 +7,31 @@ public sealed class RadiationSystem : EntitySystem
{ {
[Dependency] private readonly EntityLookupSystem _lookup = default!; [Dependency] private readonly EntityLookupSystem _lookup = default!;
private const float RadiationCooldown = 1.0f;
private float _accumulator;
public override void Update(float frameTime)
{
base.Update(frameTime);
_accumulator += frameTime;
while (_accumulator > RadiationCooldown)
{
_accumulator -= RadiationCooldown;
// All code here runs effectively every RadiationCooldown seconds, so use that as the "frame time".
foreach (var comp in EntityManager.EntityQuery<RadiationSourceComponent>())
{
var ent = comp.Owner;
if (Deleted(ent))
continue;
var cords = Transform(ent).MapPosition;
IrradiateRange(cords, comp.Range, comp.RadsPerSecond, RadiationCooldown);
}
}
}
public void IrradiateRange(MapCoordinates coordinates, float range, float radsPerSecond, float time) public void IrradiateRange(MapCoordinates coordinates, float range, float radsPerSecond, float time)
{ {
var lookUp = _lookup.GetEntitiesInRange(coordinates, range); var lookUp = _lookup.GetEntitiesInRange(coordinates, range);

View File

@@ -12,6 +12,6 @@ namespace Content.Server.Sound.Components
[ViewVariables(VVAccess.ReadWrite)] [ViewVariables(VVAccess.ReadWrite)]
[DataField("sound", required: true)] [DataField("sound", required: true)]
public SoundSpecifier Sound { get; set; } = default!; public SoundSpecifier? Sound;
} }
} }

View File

@@ -0,0 +1,9 @@
namespace Content.Server.Sound.Components;
/// <summary>
/// Simple sound emitter that emits sound on entity spawn.
/// </summary>
[RegisterComponent]
public sealed class EmitSoundOnSpawnComponent : BaseEmitSoundComponent
{
}

View File

@@ -54,6 +54,7 @@ namespace Content.Server.Sound
public override void Initialize() public override void Initialize()
{ {
base.Initialize(); base.Initialize();
SubscribeLocalEvent<EmitSoundOnSpawnComponent, ComponentInit>(HandleEmitSpawnOnInit);
SubscribeLocalEvent<EmitSoundOnLandComponent, LandEvent>(HandleEmitSoundOnLand); SubscribeLocalEvent<EmitSoundOnLandComponent, LandEvent>(HandleEmitSoundOnLand);
SubscribeLocalEvent<EmitSoundOnUseComponent, UseInHandEvent>(HandleEmitSoundOnUseInHand); SubscribeLocalEvent<EmitSoundOnUseComponent, UseInHandEvent>(HandleEmitSoundOnUseInHand);
SubscribeLocalEvent<EmitSoundOnThrowComponent, ThrownEvent>(HandleEmitSoundOnThrown); SubscribeLocalEvent<EmitSoundOnThrowComponent, ThrownEvent>(HandleEmitSoundOnThrown);
@@ -64,6 +65,11 @@ namespace Content.Server.Sound
SubscribeLocalEvent<EmitSoundOnDropComponent, DroppedEvent>(HandleEmitSoundOnDrop); SubscribeLocalEvent<EmitSoundOnDropComponent, DroppedEvent>(HandleEmitSoundOnDrop);
} }
private void HandleEmitSpawnOnInit(EntityUid uid, EmitSoundOnSpawnComponent component, ComponentInit args)
{
TryEmitSound(component);
}
private void HandleEmitSoundOnTrigger(EntityUid uid, EmitSoundOnTriggerComponent component, TriggerEvent args) private void HandleEmitSoundOnTrigger(EntityUid uid, EmitSoundOnTriggerComponent component, TriggerEvent args)
{ {
TryEmitSound(component); TryEmitSound(component);
@@ -122,6 +128,8 @@ namespace Content.Server.Sound
private void TryEmitSound(BaseEmitSoundComponent component) private void TryEmitSound(BaseEmitSoundComponent component)
{ {
if (component.Sound == null)
return;
_audioSystem.PlayPvs(component.Sound, component.Owner, component.Sound.Params.AddVolume(-2f)); _audioSystem.PlayPvs(component.Sound, component.Owner, component.Sound.Params.AddVolume(-2f));
} }
} }

View File

@@ -1,4 +1,3 @@
using Content.Server.Radiation;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
@@ -12,7 +11,6 @@ public sealed class RadiateArtifactComponent : Component
{ {
/// <summary> /// <summary>
/// Radiation pulse prototype to spawn. /// Radiation pulse prototype to spawn.
/// Should has <see cref="RadiationPulseComponent"/>.
/// </summary> /// </summary>
[DataField("pulsePrototype", customTypeSerializer:typeof(PrototypeIdSerializer<EntityPrototype>))] [DataField("pulsePrototype", customTypeSerializer:typeof(PrototypeIdSerializer<EntityPrototype>))]
public string PulsePrototype = "RadiationPulse"; public string PulsePrototype = "RadiationPulse";

View File

@@ -15,11 +15,6 @@ public sealed class RadiateArtifactSystem : EntitySystem
private void OnActivate(EntityUid uid, RadiateArtifactComponent component, ArtifactActivatedEvent args) private void OnActivate(EntityUid uid, RadiateArtifactComponent component, ArtifactActivatedEvent args)
{ {
var transform = Transform(uid); var transform = Transform(uid);
EntityManager.SpawnEntity(component.PulsePrototype, transform.Coordinates);
var pulseUid = EntityManager.SpawnEntity(component.PulsePrototype, transform.Coordinates);
if (!TryComp(pulseUid, out RadiationPulseComponent? pulse))
return;
pulse.DoPulse();
} }
} }

View File

@@ -0,0 +1,30 @@
using Content.Shared.Radiation.Systems;
using Content.Shared.Spawners.Components;
namespace Content.Shared.Radiation.Components;
/// <summary>
/// Create circle pulse animation of radiation around object.
/// Drawn on client after creation only once per component lifetime.
/// </summary>
[RegisterComponent]
[Access(typeof(RadiationPulseSystem))]
public sealed class RadiationPulseComponent : Component
{
/// <summary>
/// Timestamp when component was assigned to this entity.
/// </summary>
public TimeSpan StartTime;
/// <summary>
/// How long will animation play in seconds.
/// Can be overridden by <see cref="TimedDespawnComponent"/>.
/// </summary>
public float VisualDuration = 2f;
/// <summary>
/// The range of animation.
/// Can be overridden by <see cref="RadiationSourceComponent"/>.
/// </summary>
public float VisualRange = 5f;
}

View File

@@ -0,0 +1,20 @@
/// <summary>
/// Irradiate all objects in range.
/// </summary>
[RegisterComponent]
public sealed class RadiationSourceComponent : Component
{
/// <summary>
/// How many rads per second receive irradiated object.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField("radsPerSecond")]
public float RadsPerSecond = 1;
/// <summary>
/// Radius of radiation source.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField("range")]
public float Range = 5f;
}

View File

@@ -1,47 +0,0 @@
using Robust.Shared.GameStates;
using Robust.Shared.Serialization;
namespace Content.Shared.Radiation
{
[NetworkedComponent()]
public abstract class SharedRadiationPulseComponent : Component
{
[DataField("radsPerSecond")]
public float RadsPerSecond { get; set; } = 1;
/// <summary>
/// Radius of the pulse from its position
/// </summary>
public virtual float Range { get; set; }
public virtual bool Decay { get; set; }
public virtual bool Draw { get; set; }
public virtual TimeSpan StartTime { get; }
public virtual TimeSpan EndTime { get; }
}
/// <summary>
/// For syncing the pulse's lifespan between client and server for the overlay
/// </summary>
[Serializable, NetSerializable]
public sealed class RadiationPulseState : ComponentState
{
// not networking RadsPerSecond because damage is only ever dealt by server-side systems.
public readonly float Range;
public readonly bool Draw;
public readonly bool Decay;
public readonly TimeSpan StartTime;
public readonly TimeSpan EndTime;
public RadiationPulseState(float range, bool draw, bool decay, TimeSpan startTime, TimeSpan endTime)
{
Range = range;
Draw = draw;
Decay = decay;
StartTime = startTime;
EndTime = endTime;
}
}
}

View File

@@ -0,0 +1,32 @@
using Content.Shared.Radiation.Components;
using Content.Shared.Spawners.Components;
using Robust.Shared.Timing;
namespace Content.Shared.Radiation.Systems;
public sealed class RadiationPulseSystem : EntitySystem
{
[Dependency] private readonly IGameTiming _timing = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<RadiationPulseComponent, ComponentStartup>(OnStartup);
}
private void OnStartup(EntityUid uid, RadiationPulseComponent component, ComponentStartup args)
{
component.StartTime = _timing.RealTime;
// try to get despawn time or keep default duration time
if (TryComp<TimedDespawnComponent>(uid, out var despawn))
{
component.VisualDuration = despawn.Lifetime;
}
// try to get radiation range or keep default visual range
if (TryComp<RadiationSourceComponent>(uid, out var radSource))
{
component.VisualRange = radSource.Range;
}
}
}

View File

@@ -105,9 +105,9 @@ namespace Content.Shared.Singularity
singularity.Level = value; singularity.Level = value;
if (EntityManager.TryGetComponent(singularity.Owner, out SharedRadiationPulseComponent? pulse)) if (EntityManager.TryGetComponent(singularity.Owner, out RadiationSourceComponent? source))
{ {
pulse.RadsPerSecond = singularity.RadsPerLevel * value; source.RadsPerSecond = singularity.RadsPerLevel * value;
} }
if (EntityManager.TryGetComponent(singularity.Owner, out AppearanceComponent? appearance)) if (EntityManager.TryGetComponent(singularity.Owner, out AppearanceComponent? appearance))

View File

@@ -4,7 +4,12 @@
noSpawn: true noSpawn: true
description: Looking at this anomaly makes you feel strange, like something is pushing at your eyes. description: Looking at this anomaly makes you feel strange, like something is pushing at your eyes.
components: components:
- type: RadiationPulse - type: RadiationSource
minPulseLifespan: 0.8
maxPulseLifespan: 2.5
radsPerSecond: 5 radsPerSecond: 5
- type: TimedDespawn
lifetime: 2
- type: EmitSoundOnSpawn
sound:
collection:
RadiationPulse
- type: RadiationPulse

View File

@@ -24,13 +24,10 @@
layer: layer:
- AllMask - AllMask
- type: Singularity - type: Singularity
radsPerLevel: 1 # determines RadiationPulse's radiation per second. radsPerLevel: 1
- type: SingularityDistortion - type: SingularityDistortion
- type: RadiationPulse - type: RadiationSource
range: 10 range: 10
decay: false
minPulseLifespan: 1
maxPulseLifespan: 1
- type: Sprite - type: Sprite
sprite: Structures/Power/Generation/Singularity/singularity_1.rsi sprite: Structures/Power/Generation/Singularity/singularity_1.rsi
state: singularity_1 state: singularity_1

View File

@@ -243,8 +243,5 @@
layers: layers:
- state: rtg_damaged - state: rtg_damaged
- state: rtg_glow - state: rtg_glow
- type: RadiationPulse # ideally only when opened. - type: RadiationSource # ideally only when opened.
range: 2 range: 2
decay: false
minPulseLifespan: 1
maxPulseLifespan: 1