--- /dev/null
+using Content.Shared.Explosion;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
+
+namespace Content.Server.Anomaly.Components;
+
+[RegisterComponent]
+public sealed class ExplosionAnomalyComponent : Component
+{
+ /// <summary>
+ /// The explosion prototype to spawn
+ /// </summary>
+ [DataField("supercriticalExplosion", required: true, customTypeSerializer: typeof(PrototypeIdSerializer<ExplosionPrototype>))]
+ public string ExplosionPrototype = default!;
+
+ /// <summary>
+ /// The total amount of intensity an explosion can achieve
+ /// </summary>
+ [DataField("explosionTotalIntensity")]
+ public float TotalIntensity = 100f;
+
+ /// <summary>
+ /// How quickly does the explosion's power slope? Higher = smaller area and more concentrated damage, lower = larger area and more spread out damage
+ /// </summary>
+ [DataField("explosionDropoff")]
+ public float Dropoff = 10f;
+
+ /// <summary>
+ /// How much intensity can be applied per tile?
+ /// </summary>
+ [DataField("explosionMaxTileIntensity")]
+ public float MaxTileIntensity = 10f;
+}
--- /dev/null
+using Content.Shared.Atmos;
+
+namespace Content.Server.Anomaly.Components;
+
+/// <summary>
+/// This component is used for handling gas producing anomalies
+/// </summary>
+[RegisterComponent]
+public sealed class GasProducerAnomalyComponent : Component
+{
+ /// <summary>
+ /// Should this gas be released when an anomaly reaches max severity?
+ /// </summary>
+ [DataField("releaseOnMaxSeverity")]
+ public bool ReleaseOnMaxSeverity = false;
+
+ /// <summary>
+ /// Should this gas be released over time?
+ /// </summary>
+ [DataField("releasePassively")]
+ public bool ReleasePassively = false; // In case there are any future anomalies that release gas passively
+
+ /// <summary>
+ /// The gas to release
+ /// </summary>
+ [DataField("releasedGas", required: true)]
+ public Gas ReleasedGas = Gas.WaterVapor; // There is no entry for none, and Gas cannot be null
+
+ /// <summary>
+ /// The amount of gas released when the anomaly reaches max severity
+ /// </summary>
+ [DataField("criticalMoleAmount")]
+ public float SuperCriticalMoleAmount = 150f;
+
+ /// <summary>
+ /// The amount of gas released passively
+ /// </summary>
+ [DataField("passiveMoleAmount")]
+ public float PassiveMoleAmount = 1f;
+}
--- /dev/null
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
+
+namespace Content.Server.Anomaly.Components;
+
+[RegisterComponent]
+public sealed class ProjectileAnomalyComponent : Component
+{
+ /// <sumarry>
+ /// The prototype of the projectile that will be shot when the anomaly pulses
+ /// </summary>
+ [DataField("projectilePrototype", required: true, customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
+ public string ProjectilePrototype = default!;
+
+ /// <summary>
+ /// The MAXIMUM speed <see cref="ProjectilePrototype"/> can travel
+ /// </summary>
+ [DataField("maxProjectileSpeed")]
+ public float MaxProjectileSpeed = 30f;
+
+ /// <summary>
+ /// The MAXIMUM number of projectiles shot per pulse
+ /// </summary>
+ [DataField("maxProjectiles")]
+ public int MaxProjectiles = 5;
+
+ /// <summary>
+ /// The MAXIMUM range for targeting entities
+ /// </summary>
+ [DataField("projectileRange")]
+ public float ProjectileRange = 50f;
+
+ /// <summary>
+ /// Chance that a non sentient entity will be targeted, value must be between 0.0-1.0
+ /// </summary>
+ [DataField("targetNonSentientChance")]
+ public float TargetNonSentientChance = 0.5f;
+}
--- /dev/null
+namespace Content.Server.Anomaly.Components;
+
+/// <summary>
+/// This component is used for handling anomalies that affect the temperature
+/// </summary>
+[RegisterComponent]
+public sealed class TempAffectingAnomalyComponent : Component
+{
+
+ /// <summary>
+ /// The the amount the tempurature should be modified by (negative for decreasing temp)
+ /// </summary>
+ [DataField("tempChangePerSecond")]
+ public float TempChangePerSecond = 0;
+
+ /// <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 = 0;
+
+ /// <summary>
+ /// The volume of the hotspot where the anomaly is.
+ /// </summary>
+ [DataField("hotspotExposeVolume")]
+ public float HotspotExposeVolume = 50;
+}
--- /dev/null
+using Content.Server.Explosion.EntitySystems;
+using Content.Server.Anomaly.Components;
+using Content.Shared.Anomaly.Components;
+
+namespace Content.Server.Anomaly.Effects;
+
+/// <summary>
+/// This handles <see cref="ExplosionAnomalyComponent"/>
+/// </summary>
+public sealed class ExplosionAnomalySystem : EntitySystem
+{
+ [Dependency] private readonly ExplosionSystem _boom = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+ SubscribeLocalEvent<ExplosionAnomalyComponent, AnomalySupercriticalEvent>(OnSupercritical);
+ }
+
+ private void OnSupercritical(EntityUid uid, ExplosionAnomalyComponent component, ref AnomalySupercriticalEvent args)
+ {
+ _boom.QueueExplosion(
+ uid,
+ component.ExplosionPrototype,
+ component.TotalIntensity,
+ component.Dropoff,
+ component.MaxTileIntensity
+ );
+ }
+}
--- /dev/null
+using Content.Server.Atmos.EntitySystems;
+using Content.Server.Anomaly.Components;
+using Content.Shared.Anomaly.Components;
+using Content.Shared.Atmos;
+using Robust.Server.GameObjects;
+
+namespace Content.Server.Anomaly.Effects;
+
+/// <summary>
+/// This handles <see cref="GasProducerAnomalyComponent"/> and the events from <seealso cref="AnomalySystem"/>
+/// </summary>
+public sealed class GasProducerAnomalySystem : EntitySystem
+{
+ [Dependency] private readonly AtmosphereSystem _atmosphere = default!;
+ [Dependency] private readonly TransformSystem _xform = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+ SubscribeLocalEvent<GasProducerAnomalyComponent, AnomalySupercriticalEvent>(OnSupercritical);
+ }
+
+ private void OnSupercritical(EntityUid uid, GasProducerAnomalyComponent component, ref AnomalySupercriticalEvent args)
+ {
+ if (!component.ReleaseOnMaxSeverity)
+ return;
+
+ ReleaseGas(uid, component.ReleasedGas, component.SuperCriticalMoleAmount);
+ }
+
+ public override void Update(float frameTime)
+ {
+ base.Update(frameTime);
+
+ var query = EntityQueryEnumerator<GasProducerAnomalyComponent>();
+ while (query.MoveNext(out var ent, out var comp))
+ {
+ if (!comp.ReleasePassively)
+ continue;
+
+ // Yes this is unused code since there are no anomalies that
+ // release gas passively *yet*, but since I'm here I figured
+ // I'd save someone some time and just add it for the future
+ ReleaseGas(ent, comp.ReleasedGas, comp.PassiveMoleAmount * frameTime);
+ }
+ }
+
+ private void ReleaseGas(EntityUid uid, Gas gas, float amount)
+ {
+ var xform = Transform(uid);
+ var grid = xform.GridUid;
+ var map = xform.MapUid;
+
+ var indices = _xform.GetGridOrMapTilePosition(uid, xform);
+ var mixture = _atmosphere.GetTileMixture(grid, map, indices, true);
+
+ if (mixture == null)
+ return;
+
+ mixture.AdjustMoles(gas, amount);
+
+ if (grid is { })
+ {
+ foreach (var ind in _atmosphere.GetAdjacentTiles(grid.Value, indices))
+ {
+ var mix = _atmosphere.GetTileMixture(grid, map, ind, true);
+
+ if (mix is not { })
+ continue;
+
+ mix.AdjustMoles(gas, amount);
+ }
+ }
+ }
+}
--- /dev/null
+using Content.Server.Anomaly.Components;
+using Content.Server.Mind.Components;
+using Content.Server.Weapons.Ranged.Systems;
+using Content.Shared.Anomaly.Components;
+using Content.Shared.Projectiles;
+using Robust.Server.GameObjects;
+using Robust.Shared.Map;
+using Robust.Shared.Random;
+
+namespace Content.Server.Anomaly.Effects;
+
+/// <summary>
+/// This handles <see cref="ProjectileAnomalyComponent"/> and the events from <seealso cref="AnomalySystem"/>
+/// </summary>
+public sealed class ProjectileAnomalySystem : EntitySystem
+{
+ [Dependency] private readonly TransformSystem _xform = default!;
+ [Dependency] private readonly EntityLookupSystem _lookup = default!;
+ [Dependency] private readonly IRobustRandom _random = default!;
+ [Dependency] private readonly IMapManager _mapManager = default!;
+ [Dependency] private readonly GunSystem _gunSystem = default!;
+
+ public override void Initialize()
+ {
+ SubscribeLocalEvent<ProjectileAnomalyComponent, AnomalyPulseEvent>(OnPulse);
+ SubscribeLocalEvent<ProjectileAnomalyComponent, AnomalySupercriticalEvent>(OnSupercritical);
+ }
+
+ private void OnPulse(EntityUid uid, ProjectileAnomalyComponent component, ref AnomalyPulseEvent args)
+ {
+ ShootProjectilesAtEntities(uid, component, args.Severity);
+ }
+
+ private void OnSupercritical(EntityUid uid, ProjectileAnomalyComponent component, ref AnomalySupercriticalEvent args)
+ {
+ ShootProjectilesAtEntities(uid, component, 1.0f);
+ }
+
+ private void ShootProjectilesAtEntities(EntityUid uid, ProjectileAnomalyComponent component, float severity)
+ {
+ var xform = Transform(uid);
+ var projectilesShot = 0;
+ var range = component.ProjectileRange * severity;
+ var mobQuery = GetEntityQuery<MindComponent>();
+
+ foreach (var entity in _lookup.GetEntitiesInRange(uid, range, LookupFlags.Dynamic))
+ {
+ if (projectilesShot >= component.MaxProjectiles * severity)
+ return;
+
+ // Sentient entities are more likely to be shot at than non sentient
+ if (!mobQuery.HasComponent(entity) && !_random.Prob(component.TargetNonSentientChance))
+ continue;
+
+ var targetCoords = Transform(entity).Coordinates.Offset(_random.NextVector2(-1, 1));
+
+ ShootProjectile(
+ uid, component,
+ xform.Coordinates,
+ targetCoords,
+ severity
+ );
+ projectilesShot++;
+ }
+ }
+
+ private void ShootProjectile(
+ EntityUid uid,
+ ProjectileAnomalyComponent component,
+ EntityCoordinates coords,
+ EntityCoordinates targetCoords,
+ float severity
+ )
+ {
+ var mapPos = coords.ToMap(EntityManager, _xform);
+
+ var spawnCoords = _mapManager.TryFindGridAt(mapPos, out var grid)
+ ? coords.WithEntityId(grid.Owner, EntityManager)
+ : new(_mapManager.GetMapEntityId(mapPos.MapId), mapPos.Position);
+
+ var ent = Spawn(component.ProjectilePrototype, spawnCoords);
+ var direction = targetCoords.ToMapPos(EntityManager, _xform) - mapPos.Position;
+
+ if (!TryComp<ProjectileComponent>(ent, out var comp))
+ return;
+
+ comp.Damage *= severity;
+
+ _gunSystem.ShootProjectile(ent, direction, Vector2.Zero, uid, component.MaxProjectileSpeed * severity);
+ }
+}
using Content.Server.Interaction;
using Content.Shared.Anomaly.Components;
using Content.Shared.Anomaly.Effects.Components;
-using Robust.Server.GameObjects;
using Robust.Shared.Map;
namespace Content.Server.Anomaly.Effects;
/// </summary>
public sealed class PyroclasticAnomalySystem : EntitySystem
{
- [Dependency] private readonly AtmosphereSystem _atmosphere = default!;
[Dependency] private readonly EntityLookupSystem _lookup = default!;
[Dependency] private readonly FlammableSystem _flammable = default!;
[Dependency] private readonly InteractionSystem _interaction = default!;
- [Dependency] private readonly TransformSystem _xform = default!;
/// <inheritdoc/>
public override void Initialize()
private void OnSupercritical(EntityUid uid, PyroclasticAnomalyComponent component, ref AnomalySupercriticalEvent args)
{
var xform = Transform(uid);
- var grid = xform.GridUid;
- var map = xform.MapUid;
-
- var indices = _xform.GetGridOrMapTilePosition(uid, xform);
- var mixture = _atmosphere.GetTileMixture(grid, map, indices, true);
-
- if (mixture == null)
- return;
- mixture.AdjustMoles(component.SupercriticalGas, component.SupercriticalMoleAmount);
- if (grid is { })
- {
- foreach (var ind in _atmosphere.GetAdjacentTiles(grid.Value, indices))
- {
- var mix = _atmosphere.GetTileMixture(grid, map, ind, true);
- if (mix is not { })
- continue;
-
- mix.AdjustMoles(component.SupercriticalGas, component.SupercriticalMoleAmount);
- mix.Temperature += component.HotspotExposeTemperature;
- _atmosphere.HotspotExpose(grid.Value, indices, component.HotspotExposeTemperature, mix.Volume, uid, true);
- }
- }
IgniteNearby(xform.Coordinates, 1, component.MaximumIgnitionRadius * 2);
}
- public override void Update(float frameTime)
- {
- base.Update(frameTime);
-
- var query = EntityQueryEnumerator<PyroclasticAnomalyComponent, AnomalyComponent, TransformComponent>();
- while (query.MoveNext(out var ent, out var pyro, out var anom, out var xform))
- {
- var grid = xform.GridUid;
- var map = xform.MapUid;
- var indices = _xform.GetGridOrMapTilePosition(ent, xform);
- var mixture = _atmosphere.GetTileMixture(grid, map, indices, true);
- if (mixture is { })
- {
- mixture.Temperature += pyro.HeatPerSecond * anom.Severity * frameTime;
- }
-
- if (grid != null && anom.Severity > pyro.AnomalyHotspotThreshold)
- {
- _atmosphere.HotspotExpose(grid.Value, indices, pyro.HotspotExposeTemperature, pyro.HotspotExposeVolume, ent, true);
- }
- }
- }
-
public void IgniteNearby(EntityCoordinates coordinates, float severity, float radius)
{
foreach (var flammable in _lookup.GetComponentsInRange<FlammableComponent>(coordinates, radius))
--- /dev/null
+using Content.Server.Atmos.EntitySystems;
+using Content.Server.Anomaly.Components;
+using Content.Shared.Anomaly.Components;
+using Robust.Server.GameObjects;
+
+namespace Content.Server.Anomaly.Effects;
+
+/// <summary>
+/// This handles <see cref="TempAffectingAnomalyComponent"/>
+/// </summary>
+public sealed class TempAffectingAnomalySystem : EntitySystem
+{
+ [Dependency] private readonly AtmosphereSystem _atmosphere = default!;
+ [Dependency] private readonly TransformSystem _xform = default!;
+
+ public override void Update(float frameTime)
+ {
+ base.Update(frameTime);
+
+ var query = EntityQueryEnumerator<TempAffectingAnomalyComponent, AnomalyComponent, TransformComponent>();
+ while (query.MoveNext(out var ent, out var comp, out var anom, out var xform))
+ {
+ var grid = xform.GridUid;
+ var map = xform.MapUid;
+ var indices = _xform.GetGridOrMapTilePosition(ent, xform);
+ var mixture = _atmosphere.GetTileMixture(grid, map, indices, true);
+
+ if (mixture is { })
+ {
+ mixture.Temperature += comp.TempChangePerSecond * anom.Severity * frameTime;
+ }
+
+ if (grid != null && anom.Severity > comp.AnomalyHotSpotThreshold)
+ {
+ _atmosphere.HotspotExpose(grid.Value, indices, comp.HotspotExposeTemperature, comp.HotspotExposeVolume, ent, true);
+ }
+ }
+ }
+}
[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 = 25;
/// <summary>
/// The maximum distance from which you can be ignited by the anomaly.
[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>
- AnomalyElectricity
- AnomalyFlesh
- AnomalyBluespace
+ - AnomalyIce
chance: 1
whitelist:
components:
- Body
+
+- type: entity
+ id: ProjectileIcicle
+ parent: BaseBulletHighVelocity
+ name: Icicle
+ description: Brrrrr.
+ components:
+ - type: Sprite
+ sprite: Structures/Specific/Anomalies/ice_anom.rsi
+ - type: Projectile
+ damage:
+ types:
+ Piercing: 20
+ Cold: 20
+ Structural: 40
+
guides:
- AnomalousResearch
- type: EmitSoundOnSpawn
- sound:
+ sound:
path: /Audio/Effects/teleport_arrival.ogg
- type: entity
color: "#fca3c0"
castShadows: false
- type: PyroclasticAnomaly
+ - type: TempAffectingAnomaly
+ tempChangePerSecond: 25
+ hotspotExposeTemperature: 1000
+ - type: GasProducerAnomaly
+ releasedGas: 3
+ releaseOnMaxSeverity: true
- type: entity
id: AnomalyGravity
anomalyContactDamage:
types:
Radiation: 10
+
+
+- type: entity
+ id: AnomalyIce
+ parent: BaseAnomaly
+ suffix: Ice
+ components:
+ - type: Sprite
+ sprite: Structures/Specific/Anomalies/ice_anom.rsi
+ layers:
+ - state: anom
+ map: ["enum.AnomalyVisualLayers.Base"]
+ - state: pulse
+ map: ["enum.AnomalyVisualLayers.Animated"]
+ visible: false
+ - type: PointLight
+ radius: 2.0
+ energy: 2.5
+ color: "#befaff"
+ castShadows: false
+ - type: Anomaly
+ anomalyContactDamage:
+ types:
+ Cold: 10
+ - type: ExplosionAnomaly
+ supercriticalExplosion: Cryo
+ explosionTotalIntensity: 1000
+ explosionDropoff: 1
+ explosionMaxTileIntensity: 10
+ - type: ProjectileAnomaly
+ projectilePrototype: ProjectileIcicle
+ targetNonSentientChance: 0.1
+ - type: TempAffectingAnomaly
+ tempChangePerSecond: -25
+ hotspotExposeTemperature: -1000
+ - type: GasProducerAnomaly
+ releasedGas: 8 # Frezon. Please replace if there is a better way to specify this
+ releaseOnMaxSeverity: true
types:
Cold: 5
Blunt: 2
+ Structural: 20
tileBreakChance: [0]
tileBreakIntensity: [0]
lightColor: Blue
--- /dev/null
+{
+ "version": 1,
+ "license": "CC0-1.0",
+ "copyright": "Created by EmoGarbage404 (github) for ss14",
+ "size": {
+ "x": 32,
+ "y": 32
+ },
+ "states": [
+ {
+ "name": "anom"
+ },
+ {
+ "name": "pulse",
+ "delays": [
+ [
+ 0.15625,
+ 0.15625,
+ 0.15625,
+ 0.15625,
+ 0.15625,
+ 0.15625
+ ]
+ ]
+ },
+ {
+ "name": "bullet"
+ }
+ ]
+}