Anomalies (#13371)
This commit is contained in:
268
Content.Shared/Anomaly/Components/AnomalyComponent.cs
Normal file
268
Content.Shared/Anomaly/Components/AnomalyComponent.cs
Normal 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);
|
||||
22
Content.Shared/Anomaly/Components/AnomalyPulsingComponent.cs
Normal file
22
Content.Shared/Anomaly/Components/AnomalyPulsingComponent.cs
Normal 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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
65
Content.Shared/Anomaly/Effects/SharedGravityAnomalySystem.cs
Normal file
65
Content.Shared/Anomaly/Effects/SharedGravityAnomalySystem.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
97
Content.Shared/Anomaly/SharedAnomaly.cs
Normal file
97
Content.Shared/Anomaly/SharedAnomaly.cs
Normal 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
|
||||
{
|
||||
|
||||
}
|
||||
319
Content.Shared/Anomaly/SharedAnomalySystem.cs
Normal file
319
Content.Shared/Anomaly/SharedAnomalySystem.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user