This commit is contained in:
Nemanja
2023-01-17 00:05:20 -05:00
committed by GitHub
parent 06f19dafc9
commit 9cd0c11870
85 changed files with 3109 additions and 54 deletions

View File

@@ -0,0 +1,268 @@
using Robust.Shared.Audio;
using Robust.Shared.GameStates;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
namespace Content.Shared.Anomaly.Components;
/// <summary>
/// This is used for tracking the general behavior of anomalies.
/// This doesn't contain the specific implementations for what
/// they do, just the generic behaviors associated with them.
///
/// Anomalies and their related components were designed here: https://hackmd.io/@ss14-design/r1sQbkJOs
/// </summary>
[RegisterComponent, NetworkedComponent]
public sealed class AnomalyComponent : Component
{
/// <summary>
/// How likely an anomaly is to grow more dangerous. Moves both up and down.
/// Ranges from 0 to 1.
/// Values less than 0.5 indicate stability, whereas values greater
/// than 0.5 indicate instability, which causes increases in severity.
/// </summary>
/// <remarks>
/// Note that this doesn't refer to stability as a percentage: This is an arbitrary
/// value that only matters in relation to the <see cref="GrowthThreshold"/> and <see cref="DecayThreshold"/>
/// </remarks>
[ViewVariables(VVAccess.ReadWrite)]
public float Stability = 0f;
/// <summary>
/// How severe the effects of an anomaly are. Moves only upwards.
/// Ranges from 0 to 1.
/// A value of 0 indicates effects of extrememly minimal severity, whereas greater
/// values indicate effects of linearly increasing severity.
/// </summary>
/// <remarks>
/// Wacky-Stability scale lives on in my heart. - emo
/// </remarks>
[ViewVariables(VVAccess.ReadWrite)]
public float Severity = 0f;
#region Health
/// <summary>
/// The internal "health" of an anomaly.
/// Ranges from 0 to 1.
/// When the health of an anomaly reaches 0, it is destroyed without ever
/// reaching a supercritical point.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public float Health = 1f;
/// <summary>
/// If the <see cref="Stability"/> of the anomaly exceeds this value, it
/// becomes too unstable to support itself and starts decreasing in <see cref="Health"/>.
/// </summary>
[DataField("decayhreshold"), ViewVariables(VVAccess.ReadWrite)]
public float DecayThreshold = 0.15f;
/// <summary>
/// The amount of health lost when the stability is below the <see cref="DecayThreshold"/>
/// </summary>
[DataField("healthChangePerSecond"), ViewVariables(VVAccess.ReadWrite)]
public float HealthChangePerSecond = -0.05f;
#endregion
#region Growth
/// <summary>
/// If the <see cref="Stability"/> of the anomaly exceeds this value, it
/// becomes unstable and starts increasing in <see cref="Severity"/>.
/// </summary>
[DataField("growthThreshold"), ViewVariables(VVAccess.ReadWrite)]
public float GrowthThreshold = 0.5f;
/// <summary>
/// A coefficient used for calculating the increase in severity when above the GrowthThreshold
/// </summary>
[DataField("severityGrowthCoefficient"), ViewVariables(VVAccess.ReadWrite)]
public float SeverityGrowthCoefficient = 0.07f;
#endregion
#region Pulse
/// <summary>
/// The time at which the next artifact pulse will occur.
/// </summary>
[DataField("nextPulseTime", customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)]
public TimeSpan NextPulseTime = TimeSpan.MaxValue;
/// <summary>
/// The minimum interval between pulses.
/// </summary>
[DataField("minPulseLength")]
public TimeSpan MinPulseLength = TimeSpan.FromMinutes(1);
/// <summary>
/// The maximum interval between pulses.
/// </summary>
[DataField("maxPulseLength")]
public TimeSpan MaxPulseLength = TimeSpan.FromMinutes(2);
/// <summary>
/// A percentage by which the length of a pulse might vary.
/// </summary>
[DataField("pulseVariation")]
public float PulseVariation = .1f;
/// <summary>
/// The sound played when an anomaly pulses
/// </summary>
[DataField("pulseSound")]
public SoundSpecifier? PulseSound = new SoundCollectionSpecifier("RadiationPulse");
/// <summary>
/// The sound plays when an anomaly goes supercritical
/// </summary>
[DataField("supercriticalSound")]
public SoundSpecifier? SupercriticalSound = new SoundCollectionSpecifier("explosion");
#endregion
/// <summary>
/// The range of initial values for stability
/// </summary>
/// <remarks>
/// +/- 0.2 from perfect stability (0.5)
/// </remarks>
[DataField("initialStabilityRange")]
public (float, float) InitialStabilityRange = (0.4f, 0.6f);
/// <summary>
/// The range of initial values for severity
/// </summary>
/// <remarks>
/// Between 0 and 0.5, which should be all mild effects
/// </remarks>
[DataField("initialSeverityRange")]
public (float, float) InitialSeverityRange = (0.1f, 0.5f);
/// <summary>
/// The particle type that increases the severity of the anomaly.
/// </summary>
[DataField("severityParticleType")]
public AnomalousParticleType SeverityParticleType;
/// <summary>
/// The amount that the <see cref="Severity"/> increases by when hit
/// of an anomalous particle of <seealso cref="SeverityParticleType"/>.
/// </summary>
[DataField("severityPerSeverityHit")]
public float SeverityPerSeverityHit = 0.025f;
/// <summary>
/// The particle type that destabilizes the anomaly.
/// </summary>
[DataField("destabilizingParticleType")]
public AnomalousParticleType DestabilizingParticleType;
/// <summary>
/// The amount that the <see cref="Stability"/> increases by when hit
/// of an anomalous particle of <seealso cref="DestabilizingParticleType"/>.
/// </summary>
[DataField("stabilityPerDestabilizingHit")]
public float StabilityPerDestabilizingHit = 0.04f;
/// <summary>
/// The particle type that weakens the anomalys health.
/// </summary>
[DataField("weakeningParticleType")]
public AnomalousParticleType WeakeningParticleType;
/// <summary>
/// The amount that the <see cref="Stability"/> increases by when hit
/// of an anomalous particle of <seealso cref="DestabilizingParticleType"/>.
/// </summary>
[DataField("healthPerWeakeningeHit")]
public float HealthPerWeakeningeHit = -0.05f;
/// <summary>
/// The amount that the <see cref="Stability"/> increases by when hit
/// of an anomalous particle of <seealso cref="DestabilizingParticleType"/>.
/// </summary>
[DataField("stabilityPerWeakeningeHit")]
public float StabilityPerWeakeningeHit = -0.02f;
#region Points and Vessels
/// <summary>
/// The vessel that the anomaly is connceted to. Stored so that multiple
/// vessels cannot connect to the same anomaly.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public EntityUid? ConnectedVessel;
/// <summary>
/// The minimum amount of research points generated per second
/// </summary>
[DataField("minPointsPerSecond")]
public int MinPointsPerSecond;
/// <summary>
/// The maximum amount of research points generated per second
/// This doesn't include the point bonus for being unstable.
/// </summary>
[DataField("maxPointsPerSecond")]
public int MaxPointsPerSecond = 100;
#endregion
}
[Serializable, NetSerializable]
public sealed class AnomalyComponentState : ComponentState
{
public float Severity;
public float Stability;
public float Health;
public TimeSpan NextPulseTime;
public AnomalyComponentState(float severity, float stability, float health, TimeSpan nextPulseTime)
{
Severity = severity;
Stability = stability;
Health = health;
NextPulseTime = nextPulseTime;
}
}
/// <summary>
/// Event raised at regular intervals on an anomaly to do whatever its effect is.
/// </summary>
/// <param name="Stabiltiy"></param>
/// <param name="Severity"></param>
[ByRefEvent]
public readonly record struct AnomalyPulseEvent(float Stabiltiy, float Severity)
{
public readonly float Stabiltiy = Stabiltiy;
public readonly float Severity = Severity;
}
/// <summary>
/// Event raised on an anomaly when it reaches a supercritical point.
/// </summary>
[ByRefEvent]
public readonly record struct AnomalySupercriticalEvent;
/// <summary>
/// Event broadcast after an anomaly goes supercritical
/// </summary>
/// <param name="Anomaly">The anomaly being shut down.</param>
/// <param name="Supercritical">Whether or not the anomaly shut down passively or via a supercritical event.</param>
[ByRefEvent]
public readonly record struct AnomalyShutdownEvent(EntityUid Anomaly, bool Supercritical);
/// <summary>
/// Event broadcast when an anomaly's severity is changed.
/// </summary>
/// <param name="Anomaly">The anomaly being changed</param>
[ByRefEvent]
public readonly record struct AnomalySeverityChangedEvent(EntityUid Anomaly, float Severity);
/// <summary>
/// Event broadcast when an anomaly's stability is changed.
/// </summary>
[ByRefEvent]
public readonly record struct AnomalyStabilityChangedEvent(EntityUid Anomaly, float Stability);
/// <summary>
/// Event broadcast when an anomaly's health is changed.
/// </summary>
/// <param name="Anomaly">The anomaly being changed</param>
[ByRefEvent]
public readonly record struct AnomalyHealthChangedEvent(EntityUid Anomaly, float Health);

View File

@@ -0,0 +1,22 @@
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
namespace Content.Shared.Anomaly.Components;
/// <summary>
/// This component tracks anomalies that are currently pulsing
/// </summary>
[RegisterComponent]
public sealed class AnomalyPulsingComponent : Component
{
/// <summary>
/// The time at which the pulse will be over.
/// </summary>
[DataField("endTime", customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)]
public TimeSpan EndTime = TimeSpan.MaxValue;
/// <summary>
/// How long the pulse visual lasts
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public TimeSpan PulseDuration = TimeSpan.FromSeconds(5);
}

View File

@@ -0,0 +1,37 @@
using Robust.Shared.GameStates;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
namespace Content.Shared.Anomaly.Components;
/// <summary>
/// Tracks anomalies going supercritical
/// </summary>
[RegisterComponent, NetworkedComponent]
public sealed class AnomalySupercriticalComponent : Component
{
/// <summary>
/// The time when the supercritical animation ends and it does whatever effect.
/// </summary>
[DataField("endTime", customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)]
public TimeSpan EndTime = TimeSpan.MaxValue;
/// <summary>
/// The length of the animation before it goes supercritical.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public TimeSpan SupercriticalDuration = TimeSpan.FromSeconds(10);
/// <summary>
/// The maximum size the anomaly scales to while going supercritical
/// </summary>
[DataField("maxScaleAmount")]
public float MaxScaleAmount = 3;
}
[Serializable, NetSerializable]
public sealed class AnomalySupercriticalComponentState : ComponentState
{
public TimeSpan EndTime;
public TimeSpan Duration;
}

View File

@@ -0,0 +1,14 @@
namespace Content.Shared.Anomaly.Effects.Components;
[RegisterComponent]
public sealed class ElectricityAnomalyComponent : Component
{
[DataField("maxElectrocutionRange"), ViewVariables(VVAccess.ReadWrite)]
public float MaxElectrocuteRange = 6f;
[DataField("maxElectrocuteDamage"), ViewVariables(VVAccess.ReadWrite)]
public float MaxElectrocuteDamage = 20f;
[DataField("maxElectrocuteDuration"), ViewVariables(VVAccess.ReadWrite)]
public TimeSpan MaxElectrocuteDuration = TimeSpan.FromSeconds(8);
}

View File

@@ -0,0 +1,53 @@
namespace Content.Shared.Anomaly.Effects.Components;
[RegisterComponent]
public sealed class GravityAnomalyComponent : Component
{
/// <summary>
/// The maximumum size the GravityWellComponent MaxRange can be.
/// Is scaled linearly with stability.
/// </summary>
[DataField("maxGravityWellRange"), ViewVariables(VVAccess.ReadWrite)]
public float MaxGravityWellRange = 8f;
/// <summary>
/// The maximum distance from which the anomaly
/// can throw you via a pulse.
/// </summary>
[DataField("maxThrowRange"), ViewVariables(VVAccess.ReadWrite)]
public float MaxThrowRange = 5f;
/// <summary>
/// The maximum strength the anomaly
/// can throw you via a pulse
/// </summary>
[DataField("maxThrowStrength"), ViewVariables(VVAccess.ReadWrite)]
public float MaxThrowStrength = 10;
/// <summary>
/// The maximum Intensity of the RadiationSourceComponent.
/// Is scaled linearly with stability.
/// </summary>
[DataField("maxRadiationIntensity"), ViewVariables(VVAccess.ReadWrite)]
public float MaxRadiationIntensity = 3f;
/// <summary>
/// The minimum acceleration value for GravityWellComponent
/// Is scaled linearly with stability.
/// </summary>
[DataField("minAccel"), ViewVariables(VVAccess.ReadWrite)]
public float MinAccel = 1f;
/// <summary>
/// The maximum acceleration value for GravityWellComponent
/// Is scaled linearly with stability.
/// </summary>
[DataField("maxAccel"), ViewVariables(VVAccess.ReadWrite)]
public float MaxAccel = 5f;
/// <summary>
/// The range around the anomaly that will be spaced on supercritical.
/// </summary>
[DataField("spaceRange"), ViewVariables(VVAccess.ReadWrite)]
public float SpaceRange = 3f;
}

View File

@@ -0,0 +1,54 @@
using Content.Shared.Atmos;
namespace Content.Shared.Anomaly.Effects.Components;
[RegisterComponent]
public sealed class PyroclasticAnomalyComponent : Component
{
/// <summary>
/// The MAXIMUM amount of heat released per second.
/// This is scaled linearly with the Severity of the anomaly.
/// </summary>
/// <remarks>
/// I have no clue if this is balanced.
/// </remarks>
[DataField("heatPerSecond")]
public float HeatPerSecond = 50;
/// <summary>
/// The maximum distance from which you can be ignited by the anomaly.
/// </summary>
[DataField("maximumIgnitionRadius")]
public float MaximumIgnitionRadius = 8f;
/// <summary>
/// The minimum amount of severity required
/// before the anomaly becomes a hotspot.
/// </summary>
[DataField("anomalyHotspotThreshold")]
public float AnomalyHotspotThreshold = 0.6f;
/// <summary>
/// The temperature of the hotspot where the anomaly is
/// </summary>
[DataField("hotspotExposeTemperature")]
public float HotspotExposeTemperature = 1000;
/// <summary>
/// The volume of the hotspot where the anomaly is.
/// </summary>
[DataField("hotspotExposeVolume")]
public float HotspotExposeVolume = 50;
/// <summary>
/// Gas released when the anomaly goes supercritical.
/// </summary>
[DataField("supercriticalGas")]
public Gas SupercriticalGas = Gas.Plasma;
/// <summary>
/// The amount of gas released when the anomaly goes supercritical
/// </summary>
[DataField("supercriticalMoleAmount")]
public float SupercriticalMoleAmount = 50f;
}

View File

@@ -0,0 +1,65 @@
using System.Linq;
using Content.Shared.Anomaly.Components;
using Content.Shared.Anomaly.Effects.Components;
using Content.Shared.Construction.Components;
using Content.Shared.Construction.EntitySystems;
using Content.Shared.Throwing;
using Robust.Shared.Map;
namespace Content.Shared.Anomaly.Effects;
public abstract class SharedGravityAnomalySystem : EntitySystem
{
[Dependency] private readonly IMapManager _map = default!;
[Dependency] private readonly EntityLookupSystem _lookup = default!;
[Dependency] private readonly SharedAnchorableSystem _anchorable = default!;
[Dependency] private readonly ThrowingSystem _throwing = default!;
[Dependency] private readonly SharedTransformSystem _xform = default!;
/// <inheritdoc/>
public override void Initialize()
{
SubscribeLocalEvent<GravityAnomalyComponent, AnomalyPulseEvent>(OnAnomalyPulse);
SubscribeLocalEvent<GravityAnomalyComponent, AnomalySupercriticalEvent>(OnSupercritical);
}
private void OnAnomalyPulse(EntityUid uid, GravityAnomalyComponent component, ref AnomalyPulseEvent args)
{
var xform = Transform(uid);
var range = component.MaxThrowRange * args.Severity;
var strength = component.MaxThrowStrength * args.Severity;
var lookup = _lookup.GetEntitiesInRange(uid, range, LookupFlags.Dynamic | LookupFlags.Sundries);
foreach (var ent in lookup)
{
var tempXform = Transform(ent);
var foo = tempXform.MapPosition.Position - xform.MapPosition.Position;
_throwing.TryThrow(ent, foo.Normalized * 10, strength, uid, 0);
}
}
private void OnSupercritical(EntityUid uid, GravityAnomalyComponent component, ref AnomalySupercriticalEvent args)
{
var xform = Transform(uid);
if (!_map.TryGetGrid(xform.GridUid, out var grid))
return;
var worldPos = _xform.GetWorldPosition(xform);
var tileref = grid.GetTilesIntersecting(new Circle(worldPos, component.SpaceRange)).ToArray();
var tiles = tileref.Select(t => (t.GridIndices, Tile.Empty)).ToList();
grid.SetTiles(tiles);
var range = component.MaxThrowRange * 2;
var strength = component.MaxThrowStrength * 2;
var lookup = _lookup.GetEntitiesInRange(uid, range, LookupFlags.Dynamic | LookupFlags.Sundries);
foreach (var ent in lookup)
{
var tempXform = Transform(ent);
var foo = tempXform.MapPosition.Position - xform.MapPosition.Position;
Logger.Debug($"{ToPrettyString(ent)}: {foo}: {foo.Normalized}: {foo.Normalized * 10}");
_throwing.TryThrow(ent, foo * 5, strength, uid, 0);
}
}
}

View File

@@ -0,0 +1,97 @@
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
namespace Content.Shared.Anomaly;
[Serializable, NetSerializable]
public enum AnomalyVisuals : byte
{
IsPulsing,
Supercritical
}
[Serializable, NetSerializable]
public enum AnomalyVisualLayers : byte
{
Base,
Animated
}
/// <summary>
/// The types of anomalous particles used
/// for interfacing with anomalies.
/// </summary>
/// <remarks>
/// The only thought behind these names is that
/// they're a continuation of radioactive particles.
/// Yes i know detla+ waves exist, but they're not
/// common enough for me to care.
/// </remarks>
[Serializable, NetSerializable]
public enum AnomalousParticleType : byte
{
Delta,
Epsilon,
Zeta
}
[Serializable, NetSerializable]
public enum AnomalyVesselVisuals : byte
{
HasAnomaly
}
[Serializable, NetSerializable]
public enum AnomalyVesselVisualLayers : byte
{
Base
}
[Serializable, NetSerializable]
public enum AnomalyScannerUiKey : byte
{
Key
}
[Serializable, NetSerializable]
public sealed class AnomalyScannerUserInterfaceState : BoundUserInterfaceState
{
public FormattedMessage Message;
public TimeSpan? NextPulseTime;
public AnomalyScannerUserInterfaceState(FormattedMessage message, TimeSpan? nextPulseTime)
{
Message = message;
NextPulseTime = nextPulseTime;
}
}
[Serializable, NetSerializable]
public enum AnomalyGeneratorUiKey : byte
{
Key
}
[Serializable, NetSerializable]
public sealed class AnomalyGeneratorUserInterfaceState : BoundUserInterfaceState
{
public TimeSpan CooldownEndTime;
public int FuelAmount;
public int FuelCost;
public AnomalyGeneratorUserInterfaceState(TimeSpan cooldownEndTime, int fuelAmount, int fuelCost)
{
CooldownEndTime = cooldownEndTime;
FuelAmount = fuelAmount;
FuelCost = fuelCost;
}
}
[Serializable, NetSerializable]
public sealed class AnomalyGeneratorGenerateButtonPressedEvent : BoundUserInterfaceMessage
{
}

View File

@@ -0,0 +1,319 @@
using Content.Shared.Administration.Logs;
using Content.Shared.Anomaly.Components;
using Content.Shared.Database;
using Robust.Shared.GameStates;
using Robust.Shared.Network;
using Robust.Shared.Random;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Content.Shared.Anomaly;
public abstract class SharedAnomalySystem : EntitySystem
{
[Dependency] protected readonly IGameTiming Timing = default!;
[Dependency] private readonly INetManager _net = default!;
[Dependency] protected readonly IRobustRandom Random = default!;
[Dependency] protected readonly ISharedAdminLogManager Log = default!;
[Dependency] protected readonly SharedAudioSystem Audio = default!;
[Dependency] protected readonly SharedAppearanceSystem Appearance = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<AnomalyComponent, ComponentGetState>(OnAnomalyGetState);
SubscribeLocalEvent<AnomalyComponent, ComponentHandleState>(OnAnomalyHandleState);
SubscribeLocalEvent<AnomalySupercriticalComponent, ComponentGetState>(OnSupercriticalGetState);
SubscribeLocalEvent<AnomalySupercriticalComponent, ComponentHandleState>(OnSupercriticalHandleState);
SubscribeLocalEvent<AnomalyComponent, EntityUnpausedEvent>(OnAnomalyUnpause);
SubscribeLocalEvent<AnomalyPulsingComponent, EntityUnpausedEvent>(OnPulsingUnpause);
SubscribeLocalEvent<AnomalySupercriticalComponent, EntityUnpausedEvent>(OnSupercriticalUnpause);
}
private void OnAnomalyGetState(EntityUid uid, AnomalyComponent component, ref ComponentGetState args)
{
args.State = new AnomalyComponentState(
component.Severity,
component.Stability,
component.Health,
component.NextPulseTime);
}
private void OnAnomalyHandleState(EntityUid uid, AnomalyComponent component, ref ComponentHandleState args)
{
if (args.Current is not AnomalyComponentState state)
return;
component.Severity = state.Severity;
component.Stability = state.Stability;
component.Health = state.Health;
component.NextPulseTime = state.NextPulseTime;
}
private void OnSupercriticalGetState(EntityUid uid, AnomalySupercriticalComponent component, ref ComponentGetState args)
{
args.State = new AnomalySupercriticalComponentState
{
EndTime = component.EndTime,
Duration = component.SupercriticalDuration
};
}
private void OnSupercriticalHandleState(EntityUid uid, AnomalySupercriticalComponent component, ref ComponentHandleState args)
{
if (args.Current is not AnomalySupercriticalComponentState state)
return;
component.EndTime = state.EndTime;
component.SupercriticalDuration = state.Duration;
}
private void OnAnomalyUnpause(EntityUid uid, AnomalyComponent component, ref EntityUnpausedEvent args)
{
component.NextPulseTime += args.PausedTime;
Dirty(component);
}
private void OnPulsingUnpause(EntityUid uid, AnomalyPulsingComponent component, ref EntityUnpausedEvent args)
{
component.EndTime += args.PausedTime;
}
private void OnSupercriticalUnpause(EntityUid uid, AnomalySupercriticalComponent component, ref EntityUnpausedEvent args)
{
component.EndTime += args.PausedTime;
Dirty(component);
}
public void DoAnomalyPulse(EntityUid uid, AnomalyComponent? component = null)
{
if (!Resolve(uid, ref component))
return;
var variation = Random.NextFloat(-component.PulseVariation, component.PulseVariation) + 1;
component.NextPulseTime = Timing.CurTime + GetPulseLength(component) * variation;
// if we are above the growth threshold, then grow before the pulse
if (component.Stability > component.GrowthThreshold)
{
ChangeAnomalySeverity(uid, GetSeverityIncreaseFromGrowth(component), component);
}
else
{
// just doing this to update the scanner ui
// as they hook into these events
ChangeAnomalySeverity(uid, 0);
}
Log.Add(LogType.Anomaly, LogImpact.Medium, $"Anomaly {ToPrettyString(uid)} pulsed with severity {component.Severity}.");
Audio.PlayPvs(component.PulseSound, uid);
var pulse = EnsureComp<AnomalyPulsingComponent>(uid);
pulse.EndTime = Timing.CurTime + pulse.PulseDuration;
Appearance.SetData(uid, AnomalyVisuals.IsPulsing, true);
var ev = new AnomalyPulseEvent(component.Stability, component.Severity);
RaiseLocalEvent(uid, ref ev);
}
/// <summary>
/// Begins the animation for going supercritical
/// </summary>
/// <param name="uid"></param>
public void StartSupercriticalEvent(EntityUid uid)
{
// don't restart it if it's already begun
if (HasComp<AnomalySupercriticalComponent>(uid))
return;
Log.Add(LogType.Anomaly, LogImpact.High, $"Anomaly {ToPrettyString(uid)} began to go supercritical.");
var super = EnsureComp<AnomalySupercriticalComponent>(uid);
super.EndTime = Timing.CurTime + super.SupercriticalDuration;
Appearance.SetData(uid, AnomalyVisuals.Supercritical, true);
Dirty(super);
}
/// <summary>
/// Does the supercritical event for the anomaly.
/// This isn't called once the anomaly reaches the point, but
/// after the animation for it going supercritical
/// </summary>
/// <param name="uid"></param>
/// <param name="component"></param>
public void DoAnomalySupercriticalEvent(EntityUid uid, AnomalyComponent? component = null)
{
if (!Resolve(uid, ref component))
return;
Audio.PlayPvs(component.SupercriticalSound, uid);
var ev = new AnomalySupercriticalEvent();
RaiseLocalEvent(uid, ref ev);
EndAnomaly(uid, component, true);
}
/// <summary>
/// Ends an anomaly, cleaning up all entities that may be associated with it.
/// </summary>
/// <param name="uid">The anomaly being shut down</param>
/// <param name="component"></param>
/// <param name="supercritical">Whether or not the anomaly ended via supercritical event</param>
public void EndAnomaly(EntityUid uid, AnomalyComponent? component = null, bool supercritical = false)
{
if (!Resolve(uid, ref component))
return;
var ev = new AnomalyShutdownEvent(uid, supercritical);
RaiseLocalEvent(uid, ref ev, true);
Log.Add(LogType.Anomaly, LogImpact.Extreme, $"Anomaly {ToPrettyString(uid)} went supercritical.");
if (Terminating(uid) || _net.IsClient)
return;
Del(uid);
}
/// <summary>
/// Changes the stability of the anomaly.
/// </summary>
/// <param name="uid"></param>
/// <param name="change"></param>
/// <param name="component"></param>
public void ChangeAnomalyStability(EntityUid uid, float change, AnomalyComponent? component = null)
{
if (!Resolve(uid, ref component))
return;
var newVal = component.Stability + change;
component.Stability = Math.Clamp(newVal, 0, 1);
Dirty(component);
var ev = new AnomalyStabilityChangedEvent(uid, component.Stability);
RaiseLocalEvent(uid, ref ev, true);
}
/// <summary>
/// Changes the severity of an anomaly, going supercritical if it exceeds 1.
/// </summary>
/// <param name="uid"></param>
/// <param name="change"></param>
/// <param name="component"></param>
public void ChangeAnomalySeverity(EntityUid uid, float change, AnomalyComponent? component = null)
{
if (!Resolve(uid, ref component))
return;
var newVal = component.Severity + change;
if (newVal >= 1)
StartSupercriticalEvent(uid);
component.Severity = Math.Clamp(newVal, 0, 1);
Dirty(component);
var ev = new AnomalySeverityChangedEvent(uid, component.Severity);
RaiseLocalEvent(uid, ref ev, true);
}
/// <summary>
/// Changes the health of an anomaly, ending it if it's less than 0.
/// </summary>
/// <param name="uid"></param>
/// <param name="change"></param>
/// <param name="component"></param>
public void ChangeAnomalyHealth(EntityUid uid, float change, AnomalyComponent? component = null)
{
if (!Resolve(uid, ref component))
return;
var newVal = component.Health + change;
if (newVal < 0)
{
EndAnomaly(uid, component);
return;
}
component.Health = Math.Clamp(newVal, 0, 1);
Dirty(component);
var ev = new AnomalyHealthChangedEvent(uid, component.Health);
RaiseLocalEvent(uid, ref ev, true);
}
/// <summary>
/// Gets the length of time between each pulse
/// for an anomaly based on its current stability.
/// </summary>
/// <remarks>
/// For anomalies under the instability theshold, this will return the maximum length.
/// For those over the theshold, they will return an amount between the maximum and
/// minium value based on a linear relationship with the stability.
/// </remarks>
/// <param name="component"></param>
/// <returns>The length of time as a TimeSpan, not including random variation.</returns>
public TimeSpan GetPulseLength(AnomalyComponent component)
{
DebugTools.Assert(component.MaxPulseLength > component.MinPulseLength);
var modifier = Math.Clamp((component.Stability - component.GrowthThreshold) / component.GrowthThreshold, 0, 1);
return (component.MaxPulseLength - component.MinPulseLength) * modifier + component.MinPulseLength;
}
/// <summary>
/// Gets the increase in an anomaly's severity due
/// to being above its growth threshold
/// </summary>
/// <param name="component"></param>
/// <returns>The increase in severity for this anomaly</returns>
private float GetSeverityIncreaseFromGrowth(AnomalyComponent component)
{
var score = 1 + Math.Max(component.Stability - component.GrowthThreshold, 0) * 10;
return score * component.SeverityGrowthCoefficient;
}
public override void Update(float frameTime)
{
base.Update(frameTime);
foreach (var anomaly in EntityQuery<AnomalyComponent>())
{
var ent = anomaly.Owner;
// if the stability is under the death threshold,
// update it every second to start killing it slowly.
if (anomaly.Stability < anomaly.DecayThreshold)
{
ChangeAnomalyHealth(ent, anomaly.HealthChangePerSecond * frameTime, anomaly);
}
if (Timing.CurTime > anomaly.NextPulseTime)
{
DoAnomalyPulse(ent, anomaly);
}
}
foreach (var pulse in EntityQuery<AnomalyPulsingComponent>())
{
var ent = pulse.Owner;
if (Timing.CurTime > pulse.EndTime)
{
Appearance.SetData(ent, AnomalyVisuals.IsPulsing, false);
RemComp(ent, pulse);
}
}
foreach (var (super, anom) in EntityQuery<AnomalySupercriticalComponent, AnomalyComponent>())
{
var ent = anom.Owner;
if (Timing.CurTime <= super.EndTime)
continue;
DoAnomalySupercriticalEvent(ent, anom);
RemComp(ent, super);
}
}
}